Unity 8
GreeterPrompt.qml
1 /*
2  * Copyright (C) 2016 Canonical, Ltd.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; version 3.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  */
16 
17 import QtQuick 2.4
18 import Ubuntu.Components 1.3
19 import GSettings 1.0
20 import "../Components"
21 
22 FocusScope {
23  id: root
24  implicitHeight: units.gu(5)
25  focus: true
26 
27  property bool isPrompt
28  property bool isAlphanumeric
29  property string text
30  property bool isSecret
31  property bool interactive: true
32  readonly property alias enteredText: passwordInput.text
33 
34  signal clicked()
35  signal canceled()
36  signal accepted()
37 
38  function showFakePassword() {
39  // Just a silly hack for looking like 4 pin numbers got entered, if
40  // a fingerprint was used and we happen to be using a pin. This was
41  // a request from Design.
42  if (isSecret && isPrompt && !isAlphanumeric) {
43  passwordInput.text = "...."; // actual text doesn't matter
44  }
45  }
46 
47  GSettings {
48  id: unity8Settings
49  schema.id: "com.canonical.Unity8"
50  }
51 
52  StyledItem {
53  id: d
54 
55  readonly property color textColor: passwordInput.enabled ? theme.palette.normal.raisedText
56  : theme.palette.disabled.raisedText
57  readonly property color selectedColor: passwordInput.enabled ? theme.palette.normal.raised
58  : theme.palette.disabled.raised
59  readonly property color drawColor: passwordInput.enabled ? theme.palette.normal.raisedSecondaryText
60  : theme.palette.disabled.raisedSecondaryText
61  readonly property color errorColor: passwordInput.enabled ? theme.palette.normal.negative
62  : theme.palette.disabled.negative
63  }
64 
65  Rectangle {
66  anchors.fill: parent
67  border.width: units.dp(1)
68  border.color: d.drawColor
69  radius: units.gu(0.5)
70  color: "transparent"
71  }
72 
73  Component.onCompleted: updateFocus()
74  onIsPromptChanged: updateFocus()
75  function updateFocus() {
76  if (root.isPrompt) {
77  passwordInput.focus = true;
78  } else {
79  promptButton.focus = true;
80  }
81  }
82 
83  StyledItem {
84  id: promptButton
85  objectName: "promptButton"
86  anchors.fill: parent
87  visible: !root.isPrompt
88  activeFocusOnTab: true
89 
90  styleName: "FocusShape"
91 
92  function triggered() {
93  if (root.interactive) {
94  root.clicked();
95  }
96  }
97 
98  Rectangle {
99  height: parent.height;
100  width: parent.width
101  color: "transparent"
102  border {
103  color: d.textColor
104  width: units.dp(1)
105  }
106  }
107 
108  Keys.onSpacePressed: triggered();
109  Keys.onReturnPressed: triggered();
110  Keys.onEnterPressed: triggered();
111  MouseArea {
112  anchors.fill: parent
113  onClicked: parent.triggered();
114  }
115 
116  Label {
117  anchors.centerIn: parent
118  color: d.textColor
119  text: root.text
120  }
121  }
122 
123  TextField {
124  id: passwordInput
125  objectName: "promptField"
126  anchors.fill: parent
127  visible: root.isPrompt
128  opacity: fakeLabel.visible ? 0 : 1
129  activeFocusOnTab: true
130 
131  validator: RegExpValidator {
132  regExp: root.isAlphanumeric ? /^.*$/ : /^\d{4}$/
133  }
134 
135  inputMethodHints: Qt.ImhSensitiveData | Qt.ImhNoPredictiveText |
136  Qt.ImhMultiLine | // so OSK doesn't close on Enter
137  (root.isAlphanumeric ? Qt.ImhNone : Qt.ImhDigitsOnly)
138  echoMode: root.isSecret ? TextInput.Password : TextInput.Normal
139  hasClearButton: false
140 
141  readonly property real frameSpacing: units.gu(0.5)
142 
143  style: StyledItem {
144  anchors.fill: parent
145  styleName: "FocusShape"
146 
147  // Properties needed by TextField
148  readonly property color color: d.textColor
149  readonly property color selectedTextColor: d.selectedColor
150  readonly property color selectionColor: d.textColor
151  readonly property color borderColor: "transparent"
152  readonly property color backgroundColor: "transparent"
153  readonly property color errorColor: d.errorColor
154  readonly property real frameSpacing: styledItem.frameSpacing
155 
156  // Properties needed by FocusShape
157  readonly property bool enabled: styledItem.enabled
158  readonly property bool keyNavigationFocus: styledItem.keyNavigationFocus
159  property bool activeFocusOnTab
160  }
161 
162  secondaryItem: [
163  Row {
164  id: extraIcons
165  spacing: passwordInput.frameSpacing
166  Icon {
167  name: "keyboard-caps-enabled"
168  height: units.gu(3)
169  width: units.gu(3)
170  color: d.textColor
171  visible: root.isSecret && false // TODO: detect when caps lock is on
172  }
173  Icon {
174  name: "input-keyboard-symbolic"
175  height: units.gu(3)
176  width: units.gu(3)
177  color: d.textColor
178  visible: !unity8Settings.alwaysShowOsk
179  MouseArea {
180  anchors.fill: parent
181  onClicked: unity8Settings.alwaysShowOsk = true
182  }
183  }
184  }
185  ]
186 
187  onDisplayTextChanged: {
188  // We use onDisplayTextChanged instead of onTextChanged because
189  // displayText changes after text and if we did this before it
190  // updated, we would use the wrong displayText for fakeLabel.
191  if (!isAlphanumeric && text.length >= 4) {
192  // hard limit of 4 for passcodes right now
193  respond();
194  }
195  }
196 
197  onAccepted: respond()
198 
199  function respond() {
200  if (root.interactive) {
201  root.accepted();
202  }
203  }
204 
205  Keys.onEscapePressed: {
206  root.canceled();
207  event.accepted = true;
208  }
209 
210  // We use our own custom placeholder label instead of the standard
211  // TextField one because the standard one hardcodes baseText as the
212  // palette color, whereas we want raisedSecondaryText.
213  Label {
214  id: hint
215  objectName: "promptHint"
216  anchors {
217  left: parent ? parent.left : undefined
218  right: parent ? parent.right : undefined
219  verticalCenter: parent ? parent.verticalCenter : undefined
220  leftMargin: units.gu(1.5)
221  rightMargin: anchors.leftMargin + extraIcons.width
222  }
223  text: root.text
224  visible: passwordInput.text == "" && !passwordInput.inputMethodComposing
225  enabled: visible
226  color: d.drawColor
227  elide: Text.ElideRight
228  }
229  }
230 
231  // Have a fake label that covers the text field after the user presses
232  // enter. What we *really* want is a disabled mode that doesn't lose OSK
233  // focus. Because our goal here is simply to keep the OSK up while
234  // we wait for PAM to get back to us, and while waiting, we don't want
235  // the user to be able to edit the field (simply because it would look
236  // weird if we allowed that). But until we have such a disabled mode,
237  // we'll fake it by covering the real text field with a label.
238  FadingLabel {
239  id: fakeLabel
240  anchors.verticalCenter: parent ? parent.verticalCenter : undefined
241  anchors.left: parent ? parent.left : undefined
242  anchors.right: parent ? parent.right : undefined
243  anchors.leftMargin: passwordInput.frameSpacing * 2
244  anchors.rightMargin: passwordInput.frameSpacing * 2 + extraIcons.width
245  color: d.drawColor
246  text: passwordInput.displayText
247  visible: root.isPrompt && !root.interactive
248  enabled: visible
249  }
250 }