Lomiri
TextPrompt.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 
18 import QtQuick 2.12
19 import Lomiri.Components 1.3
20 import "../Components"
21 
22 FocusScope {
23  id: root
24  objectName: "promptPassword"
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 
33  signal clicked()
34  signal canceled()
35  signal accepted(string response)
36 
37  StyledItem {
38  id: d
39 
40  readonly property color textColor: passwordInput.enabled ? theme.palette.normal.raisedText
41  : theme.palette.disabled.raisedText
42  readonly property color selectedColor: passwordInput.enabled ? theme.palette.normal.raised
43  : theme.palette.disabled.raised
44  readonly property color drawColor: passwordInput.enabled ? theme.palette.normal.raisedSecondaryText
45  : theme.palette.disabled.raisedSecondaryText
46  readonly property color errorColor: passwordInput.enabled ? theme.palette.normal.negative
47  : theme.palette.disabled.negative
48  }
49 
50  Rectangle {
51  anchors.fill: parent
52  radius: units.gu(0.5)
53  color: "#7A111111"
54  Behavior on border.color {
55  ColorAnimation{}
56  }
57  border {
58  color: root.loginError ? d.errorColor : d.drawColor
59  width: root.loginError ? units.dp(2): units.dp(1)
60  }
61  }
62 
63  TextField {
64  id: passwordInput
65  objectName: "promptField"
66  anchors.fill: parent
67  focus: root.focus
68 
69  opacity: fakeLabel.visible ? 0 : 1
70  activeFocusOnTab: true
71 
72  onSelectedTextChanged: passwordInput.deselect()
73 
74  validator: RegExpValidator {
75  regExp: /^.*$/
76  }
77 
78  inputMethodHints: Qt.ImhSensitiveData | Qt.ImhNoPredictiveText |
79  Qt.ImhMultiLine // so OSK doesn't close on Enter
80  echoMode: root.isSecret ? TextInput.Password : TextInput.Normal
81  hasClearButton: false
82 
83  passwordCharacter: "●"
84  color: d.drawColor
85 
86  readonly property real frameSpacing: units.gu(1)
87 
88  style: StyledItem {
89  anchors.fill: parent
90  styleName: "FocusShape"
91 
92  // Properties needed by TextField
93  readonly property color color: d.textColor
94  readonly property color selectedTextColor: d.selectedColor
95  readonly property color selectionColor: d.textColor
96  readonly property color borderColor: "transparent"
97  readonly property color backgroundColor: "transparent"
98  readonly property color errorColor: d.errorColor
99  readonly property real frameSpacing: styledItem.frameSpacing
100 
101  // Properties needed by FocusShape
102  readonly property bool enabled: styledItem.enabled
103  readonly property bool keyNavigationFocus: styledItem.keyNavigationFocus
104  property bool activeFocusOnTab
105  }
106 
107  secondaryItem: [
108  Row {
109  id: extraIcons
110  spacing: passwordInput.frameSpacing
111  anchors.verticalCenter: parent.verticalCenter
112  Icon {
113  name: "keyboard-caps-enabled"
114  height: units.gu(3)
115  width: units.gu(3)
116  color: d.drawColor
117  visible: root.isSecret && false // TODO: detect when caps lock is on
118  anchors.verticalCenter: parent.verticalCenter
119  }
120  Icon {
121  objectName: "greeterPromptKeyboardButton"
122  name: "input-keyboard-symbolic"
123  height: units.gu(3)
124  width: units.gu(3)
125  color: d.drawColor
126  visible: !lomiriSettings.alwaysShowOsk && root.hasKeyboard
127  anchors.verticalCenter: parent.verticalCenter
128  MouseArea {
129  anchors.fill: parent
130  onClicked: lomiriSettings.alwaysShowOsk = true
131  }
132  }
133  Icon {
134  name: "dialog-warning-symbolic"
135  height: units.gu(3)
136  width: units.gu(3)
137  color: d.drawColor
138  visible: root.loginError
139  anchors.verticalCenter: parent.verticalCenter
140  }
141  Icon {
142  name: "toolkit_chevron-ltr_2gu"
143  height: units.gu(2.5)
144  width: units.gu(2.5)
145  color: d.drawColor
146  visible: !root.loginError
147  anchors.verticalCenter: parent.verticalCenter
148  MouseArea {
149  anchors.fill: parent
150  onClicked: root.accepted(passwordInput.text)
151  }
152  }
153  }
154  ]
155 
156  onDisplayTextChanged: {
157  // We use onDisplayTextChanged instead of onTextChanged because
158  // displayText changes after text and if we did this before it
159  // updated, we would use the wrong displayText for fakeLabel.
160  root.loginError = false;
161  }
162 
163  onAccepted: respond()
164 
165  function respond() {
166  if (root.interactive) {
167  root.accepted(passwordInput.text);
168  }
169  }
170 
171  Keys.onEscapePressed: {
172  root.canceled();
173  event.accepted = true;
174  }
175  }
176 
177  // We use our own custom placeholder label instead of the standard
178  // TextField one because the standard one hardcodes baseText as the
179  // palette color, whereas we want raisedSecondaryText.
180  Label {
181  id: passwordHint
182  objectName: "promptHint"
183  anchors {
184  left: passwordInput ? passwordInput.left : undefined
185  right: passwordInput ? passwordInput.right : undefined
186  verticalCenter: passwordInput ? passwordInput.verticalCenter : undefined
187  leftMargin: units.gu(2)
188  rightMargin: anchors.leftMargin + extraIcons.width
189  }
190  text: root.text
191  visible: passwordInput.text == "" && !passwordInput.inputMethodComposing
192  enabled: visible
193  color: d.drawColor
194  elide: Text.ElideRight
195  }
196 
197  // Have a fake label that covers the text field after the user presses
198  // enter. What we *really* want is a disabled mode that doesn't lose OSK
199  // focus. Because our goal here is simply to keep the OSK up while
200  // we wait for PAM to get back to us, and while waiting, we don't want
201  // the user to be able to edit the field (simply because it would look
202  // weird if we allowed that). But until we have such a disabled mode,
203  // we'll fake it by covering the real text field with a label.
204  FadingLabel {
205  id: fakeLabel
206  anchors.verticalCenter: parent ? parent.verticalCenter : undefined
207  anchors.left: parent ? parent.left : undefined
208  anchors.right: parent ? parent.right : undefined
209  anchors.leftMargin: passwordInput.frameSpacing * 2
210  anchors.rightMargin: passwordInput.frameSpacing * 2 + extraIcons.width
211  color: d.drawColor
212  text: passwordInput.displayText
213  visible: !root.interactive
214  enabled: visible
215  }
216 }