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 "../Components"
20 
21 FocusScope {
22  id: root
23  implicitHeight: units.gu(5)
24  focus: true
25 
26  property bool isPrompt
27  property bool isAlphanumeric
28  property string text
29  property bool isSecret
30 
31  signal clicked()
32  signal canceled()
33  signal responded(string text)
34 
35  function reset() {
36  passwordInput.text = "";
37  fakeLabel.text = "";
38  d.enabled = true;
39  }
40 
41  StyledItem {
42  id: d
43 
44  property bool enabled: true
45  readonly property color textColor: passwordInput.enabled ? theme.palette.normal.raisedText
46  : theme.palette.disabled.raisedText
47  readonly property color selectedColor: passwordInput.enabled ? theme.palette.normal.raised
48  : theme.palette.disabled.raised
49  readonly property color drawColor: passwordInput.enabled ? theme.palette.normal.raisedSecondaryText
50  : theme.palette.disabled.raisedSecondaryText
51  readonly property color errorColor: passwordInput.enabled ? theme.palette.normal.negative
52  : theme.palette.disabled.negative
53  }
54 
55  Rectangle {
56  anchors.fill: parent
57  border.width: units.dp(1)
58  border.color: d.drawColor
59  radius: units.gu(0.5)
60  color: "transparent"
61  }
62 
63  Component.onCompleted: updateFocus()
64  onIsPromptChanged: updateFocus()
65  function updateFocus() {
66  if (root.isPrompt) {
67  passwordInput.focus = true;
68  } else {
69  promptButton.focus = true;
70  }
71  }
72 
73  AbstractButton {
74  id: promptButton
75  objectName: "promptButton"
76  anchors.fill: parent
77  visible: !root.isPrompt
78 
79  onClicked: {
80  if (d.enabled) {
81  d.enabled = false;
82  root.clicked();
83  }
84  }
85 
86  Label {
87  anchors.centerIn: parent
88  color: d.textColor
89  text: root.text
90  }
91  }
92 
93  TextField {
94  id: passwordInput
95  objectName: "promptField"
96  anchors.fill: parent
97  visible: root.isPrompt
98  opacity: fakeLabel.visible ? 0 : 1
99 
100  validator: RegExpValidator {
101  regExp: root.isAlphanumeric ? /^.*$/ : /^\d{4}$/
102  }
103 
104  inputMethodHints: Qt.ImhSensitiveData | Qt.ImhNoPredictiveText |
105  Qt.ImhMultiLine | // so OSK doesn't close on Enter
106  (root.isAlphanumeric ? Qt.ImhNone : Qt.ImhDigitsOnly)
107  echoMode: root.isSecret ? TextInput.Password : TextInput.Normal
108  hasClearButton: false
109 
110  readonly property real frameSpacing: units.gu(0.5)
111 
112  style: Item {
113  property color color: d.textColor
114  property color selectedTextColor: d.selectedColor
115  property color selectionColor: d.textColor
116  property color borderColor: "transparent"
117  property color backgroundColor: "transparent"
118  property color errorColor: d.errorColor
119  property real frameSpacing: passwordInput.frameSpacing
120  anchors.fill: parent
121  }
122 
123  secondaryItem: [
124  Icon {
125  id: capsIcon
126  name: "keyboard-caps-enabled"
127  height: units.gu(3)
128  width: units.gu(3)
129  color: d.textColor
130  visible: root.isSecret && false // TODO: detect when caps lock is on
131  readonly property real visibleWidth: visible ? width + passwordInput.frameSpacing : 0
132  }
133  ]
134 
135  onDisplayTextChanged: {
136  // We use onDisplayTextChanged instead of onTextChanged because
137  // displayText changes after text and if we did this before it
138  // updated, we would use the wrong displayText for fakeLabel.
139  if (!isAlphanumeric && text.length >= 4) {
140  // hard limit of 4 for passcodes right now
141  respond();
142  }
143  }
144 
145  onAccepted: {
146  if (d.enabled)
147  respond();
148  }
149 
150  function respond() {
151  d.enabled = false;
152  fakeLabel.text = displayText;
153  root.responded(text);
154  }
155 
156  Keys.onEscapePressed: {
157  root.canceled();
158  event.accepted = true;
159  }
160 
161  // We use our own custom placeholder label instead of the standard
162  // TextField one because the standard one hardcodes baseText as the
163  // palette color, whereas we want raisedSecondaryText.
164  Label {
165  id: hint
166  anchors {
167  left: parent.left
168  right: parent.right
169  verticalCenter: parent.verticalCenter
170  leftMargin: units.gu(1.5)
171  rightMargin: anchors.leftMargin + capsIcon.visibleWidth
172  }
173  text: root.text
174  visible: passwordInput.text == "" && !passwordInput.inputMethodComposing
175  color: d.drawColor
176  elide: Text.ElideRight
177  }
178  }
179 
180  // Have a fake label that covers the text field after the user presses
181  // enter. What we *really* want is a disabled mode that doesn't lose OSK
182  // focus. Because our goal here is simply to keep the OSK up while
183  // we wait for PAM to get back to us, and while waiting, we don't want
184  // the user to be able to edit the field (simply because it would look
185  // weird if we allowed that). But until we have such a disabled mode,
186  // we'll fake it by covering the real text field with a label.
187  FadingLabel {
188  id: fakeLabel
189  anchors.verticalCenter: parent.verticalCenter
190  anchors.left: parent.left
191  anchors.right: parent.right
192  anchors.leftMargin: passwordInput.frameSpacing * 2
193  anchors.rightMargin: passwordInput.frameSpacing * 2 + capsIcon.visibleWidth
194  color: d.drawColor
195  visible: root.isPrompt && !d.enabled
196  }
197 }