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.12
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
61 onSelectedTextChanged: passwordInput.deselect()
62 onCursorPositionChanged: cursorPosition = length
63
64 validator: RegExpValidator {
65 regExp: /^\d{4,}$/
66 }
67
68 inputMethodHints: Qt.ImhSensitiveData | Qt.ImhNoPredictiveText |
69 Qt.ImhMultiLine | // so OSK doesn't close on Enter
70 Qt.ImhDigitsOnly
71 echoMode: TextInput.Password
72 hasClearButton: false
73
74 cursorDelegate: Item {}
75
76 passwordCharacter: "●"
77 color: d.drawColor
78
79 readonly property real letterSpacing: units.gu(1.75)
80 readonly property real frameSpacing: letterSpacing
81
82 font.pixelSize: units.gu(3)
83 font.letterSpacing: letterSpacing
84
85 style: StyledItem {
86 anchors.fill: parent
87 styleName: "FocusShape"
88
89 // Properties needed by TextField
90 readonly property color color: d.textColor
91 readonly property color selectedTextColor: d.selectedColor
92 readonly property color selectionColor: d.textColor
93 readonly property color borderColor: "transparent"
94 readonly property color backgroundColor: "transparent"
95 readonly property color errorColor: d.errorColor
96 readonly property real frameSpacing: 0
97
98 // Properties needed by FocusShape
99 readonly property bool enabled: styledItem.enabled
100 readonly property bool keyNavigationFocus: styledItem.keyNavigationFocus
101 property bool activeFocusOnTab
102 }
103
104 onDisplayTextChanged: {
105 // We use onDisplayTextChanged instead of onTextChanged because
106 // displayText changes after text and if we did this before it
107 // updated, we would use the wrong displayText for fakeLabel.
108 root.loginError = false;
109 if (text.length === root.pincodeLength) {
110 respond();
111 }
112 }
113
114 onAccepted: respond()
115
116 function respond() {
117 if (root.interactive) {
118 root.accepted(passwordInput.text);
119 }
120 }
121
122 Keys.onEscapePressed: {
123 root.canceled();
124 event.accepted = true;
125 }
126 }
127
128 Row {
129 id: extraIcons
130 spacing: passwordInput.frameSpacing
131 anchors {
132 horizontalCenter: parent ? parent.horizontalCenter : undefined
133 horizontalCenterOffset: passwordInput.letterSpacing / 2
134 verticalCenter: passwordInput ? passwordInput.verticalCenter : undefined
135 }
136
137 Label {
138 id: pinHint
139 objectName: "promptPinHint"
140
141 text: Array(root.pincodeLength).fill('○').join("")
142 enabled: visible
143 color: d.drawColor
144 font {
145 pixelSize: units.gu(3)
146 letterSpacing: units.gu(1.75)
147 }
148 elide: Text.ElideRight
149 }
150 Icon {
151 name: "keyboard-caps-enabled"
152 height: units.gu(3)
153 width: height
154 color: d.drawColor
155 visible: false // TODO: detect when caps lock is on
156 anchors.verticalCenter: parent.verticalCenter
157 }
158 Icon {
159 objectName: "greeterPromptKeyboardButton"
160 name: "input-keyboard-symbolic"
161 height: units.gu(3)
162 width: height
163 color: d.drawColor
164 visible: !lomiriSettings.alwaysShowOsk && root.hasKeyboard
165 anchors.verticalCenter: parent.verticalCenter
166 MouseArea {
167 anchors.fill: parent
168 onClicked: lomiriSettings.alwaysShowOsk = true
169 }
170 }
171 Icon {
172 name: "dialog-warning-symbolic"
173 height: units.gu(3)
174 width: height
175 color: d.drawColor
176 visible: root.loginError
177 anchors.verticalCenter: parent.verticalCenter
178 }
179 }
180
181 // Have a fake label that covers the text field after the user presses
182 // enter. What we *really* want is a disabled mode that doesn't lose OSK
183 // focus. Because our goal here is simply to keep the OSK up while
184 // we wait for PAM to get back to us, and while waiting, we don't want
185 // the user to be able to edit the field (simply because it would look
186 // weird if we allowed that). But until we have such a disabled mode,
187 // we'll fake it by covering the real text field with a label.
188 Label {
189 id: fakeLabel
190 anchors.verticalCenter: extraIcons ? extraIcons.verticalCenter : undefined
191 anchors.left: extraIcons ? extraIcons.left : undefined
192 anchors.right: parent ? parent.right : undefined
193 anchors.rightMargin: passwordInput.frameSpacing * 2 + extraIcons.width
194 color: d.drawColor
195 font {
196 pixelSize: pinHint.font.pixelSize
197 letterSpacing: pinHint.font.letterSpacing
198 }
199 text: passwordInput.displayText
200 visible: !root.interactive
201 enabled: visible
202 }
203}