Lomiri
Loading...
Searching...
No Matches
PinPrompt.qml
1/*
2 * Copyright (C) 2021 Capsia
3 * Copyright (C) 2016 Canonical, Ltd.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; version 3.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18import QtQuick 2.15
19import AccountsService 0.1
20import Lomiri.Components 1.3
21import "../Components"
22
23FocusScope {
24 id: root
25
26 property string text
27 property bool isSecret
28 property bool interactive: true
29 property bool loginError: false
30 property bool hasKeyboard: false
31 property alias enteredText: passwordInput.text
32 property int pincodeLength: AccountsService.pincodeLength
33
34 signal clicked()
35 signal canceled()
36 signal accepted(string response)
37
38 StyledItem {
39 id: d
40
41 readonly property color textColor: passwordInput.enabled ? theme.palette.normal.raisedText
42 : theme.palette.disabled.raisedText
43 readonly property color selectedColor: passwordInput.enabled ? theme.palette.normal.raised
44 : theme.palette.disabled.raised
45 readonly property color drawColor: passwordInput.enabled ? theme.palette.normal.raisedSecondaryText
46 : theme.palette.disabled.raisedSecondaryText
47 readonly property color errorColor: passwordInput.enabled ? theme.palette.normal.negative
48 : theme.palette.disabled.negative
49 }
50
51 TextField {
52 id: passwordInput
53 objectName: "promptField"
54 anchors.left: extraIcons.left
55 anchors.right: extraIcons.right
56 focus: root.focus
57
58 opacity: fakeLabel.visible ? 0 : 1
59 activeFocusOnTab: true
60 onActiveFocusChanged: if (activeFocus) Qt.inputMethod.show()
61
62 onSelectedTextChanged: passwordInput.deselect()
63 onCursorPositionChanged: cursorPosition = length
64
65 validator: RegExpValidator {
66 regExp: /^\d{4,}$/
67 }
68
69 inputMethodHints: Qt.ImhSensitiveData | Qt.ImhNoPredictiveText |
70 Qt.ImhMultiLine | // so OSK doesn't close on Enter
71 Qt.ImhDigitsOnly
72 echoMode: TextInput.Password
73 hasClearButton: false
74
75 cursorDelegate: Item {}
76
77 passwordCharacter: "●"
78 color: d.drawColor
79
80 readonly property real letterSpacing: units.gu(1.75)
81 readonly property real frameSpacing: letterSpacing
82
83 font.pixelSize: units.gu(3)
84 font.letterSpacing: letterSpacing
85
86 style: StyledItem {
87 anchors.fill: parent
88 styleName: "FocusShape"
89
90 // Properties needed by TextField
91 readonly property color color: d.textColor
92 readonly property color selectedTextColor: d.selectedColor
93 readonly property color selectionColor: d.textColor
94 readonly property color borderColor: "transparent"
95 readonly property color backgroundColor: "transparent"
96 readonly property color errorColor: d.errorColor
97 readonly property real frameSpacing: 0
98
99 // Properties needed by FocusShape
100 readonly property bool enabled: styledItem.enabled
101 readonly property bool keyNavigationFocus: styledItem.keyNavigationFocus
102 property bool activeFocusOnTab
103 }
104
105 onDisplayTextChanged: {
106 // We use onDisplayTextChanged instead of onTextChanged because
107 // displayText changes after text and if we did this before it
108 // updated, we would use the wrong displayText for fakeLabel.
109 root.loginError = false;
110 if (text.length === root.pincodeLength) {
111 respond();
112 }
113 }
114
115 onAccepted: respond()
116
117 function respond() {
118 if (root.interactive) {
119 root.accepted(passwordInput.text);
120 }
121 }
122
123 Keys.onEscapePressed: {
124 lomiriSettings.alwaysShowOsk = false
125 root.canceled();
126 event.accepted = true;
127 }
128 }
129
130 Row {
131 id: extraIcons
132 spacing: passwordInput.frameSpacing
133 anchors {
134 horizontalCenter: parent ? parent.horizontalCenter : undefined
135 horizontalCenterOffset: passwordInput.letterSpacing / 2
136 verticalCenter: passwordInput ? passwordInput.verticalCenter : undefined
137 }
138
139 Label {
140 id: pinHint
141 objectName: "promptPinHint"
142
143 text: Array(root.pincodeLength).fill('○').join("")
144 enabled: visible
145 color: d.drawColor
146 font {
147 pixelSize: units.gu(3)
148 letterSpacing: units.gu(1.75)
149 }
150 elide: Text.ElideRight
151 }
152 Icon {
153 name: "keyboard-caps-enabled"
154 height: units.gu(3)
155 width: height
156 color: d.drawColor
157 visible: false // TODO: detect when caps lock is on
158 anchors.verticalCenter: parent.verticalCenter
159 }
160 Icon {
161 objectName: "greeterPromptKeyboardButton"
162 name: "input-keyboard-symbolic"
163 height: units.gu(3)
164 width: height
165 color: d.drawColor
166 visible: !lomiriSettings.alwaysShowOsk && root.hasKeyboard
167 anchors.verticalCenter: parent.verticalCenter
168 MouseArea {
169 anchors.fill: parent
170 onClicked: lomiriSettings.alwaysShowOsk = true
171 }
172 }
173 Icon {
174 name: "dialog-warning-symbolic"
175 height: units.gu(3)
176 width: height
177 color: d.drawColor
178 visible: root.loginError
179 anchors.verticalCenter: parent.verticalCenter
180 }
181 }
182
183 // Have a fake label that covers the text field after the user presses
184 // enter. What we *really* want is a disabled mode that doesn't lose OSK
185 // focus. Because our goal here is simply to keep the OSK up while
186 // we wait for PAM to get back to us, and while waiting, we don't want
187 // the user to be able to edit the field (simply because it would look
188 // weird if we allowed that). But until we have such a disabled mode,
189 // we'll fake it by covering the real text field with a label.
190 Label {
191 id: fakeLabel
192 anchors.verticalCenter: extraIcons ? extraIcons.verticalCenter : undefined
193 anchors.left: extraIcons ? extraIcons.left : undefined
194 anchors.right: parent ? parent.right : undefined
195 anchors.rightMargin: passwordInput.frameSpacing * 2 + extraIcons.width
196 color: d.drawColor
197 font {
198 pixelSize: pinHint.font.pixelSize
199 letterSpacing: pinHint.font.letterSpacing
200 }
201 text: passwordInput.displayText
202 visible: !root.interactive
203 enabled: visible
204 }
205}