/* * Copyright (C) 2013 Canonical, Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.4 import Ubuntu.Components 1.3 import "../Components" Item { id: root property alias model: userList.model property bool alphanumeric: true property int currentIndex property bool locked readonly property int numAboveBelow: 4 readonly property int cellHeight: units.gu(5) readonly property int highlightedHeight: units.gu(10) readonly property int moveDuration: 200 readonly property string currentUser: userList.currentItem.username property bool wasPrompted: false signal selected(int index) signal responded(string response) function tryToUnlock() { if (wasPrompted) { passwordInput.forceActiveFocus(); } else { if (root.locked) { root.selected(currentIndex); } else { root.responded(""); } } } function showMessage(html) { if (infoLabel.text === "") { infoLabel.text = html; } else { infoLabel.text += "
" + html; } } function showPrompt(text, isSecret, isDefaultPrompt) { passwordInput.text = ""; passwordInput.promptText = text; passwordInput.enabled = true; passwordInput.echoMode = isSecret ? TextInput.Password : TextInput.Normal; if (wasPrompted) // stay in text field if second prompt passwordInput.focus = true; wasPrompted = true; } function showError() { wrongPasswordAnimation.start(); root.resetAuthentication(); if (wasPrompted) { passwordInput.focus = true; } } function reset() { root.resetAuthentication(); } Keys.onEscapePressed: { selected(currentIndex); } onCurrentIndexChanged: { userList.currentIndex = currentIndex; } Rectangle { id: highlightItem anchors { left: parent.left right: parent.right verticalCenter: parent.verticalCenter } height: root.highlightedHeight color: Qt.rgba(0.1, 0.1, 0.1, 0.4) border.color: Qt.rgba(0.4, 0.4, 0.4, 0.4) border.width: units.dp(1) radius: units.gu(1.5) antialiasing: true } ListView { id: userList objectName: "userList" anchors.fill: parent preferredHighlightBegin: userList.height / 2 - root.highlightedHeight / 2 preferredHighlightEnd: userList.height / 2 - root.highlightedHeight / 2 highlightRangeMode: ListView.StrictlyEnforceRange highlightMoveDuration: root.moveDuration flickDeceleration: 10000 readonly property bool movingInternally: moveTimer.running || userList.moving onMovingInternallyChanged: { if (!movingInternally) { root.selected(currentIndex); } } onCurrentIndexChanged: { root.resetAuthentication(); moveTimer.start(); } delegate: Item { width: parent.width height: root.cellHeight readonly property bool belowHighlight: (userList.currentIndex < 0 && index > 0) || (userList.currentIndex >= 0 && index > userList.currentIndex) readonly property int belowOffset: root.highlightedHeight - root.cellHeight readonly property string username: name opacity: { // The goal here is to make names less and less opaque as they // leave the highlight area. Can't simply use index, because // that can change quickly if the user clicks at edges of // list. So we use actual pixel distance. var highlightDist = 0; var realY = y - userList.contentY; if (belowHighlight) realY += belowOffset; if (realY + height <= highlightItem.y) highlightDist = realY + height - highlightItem.y; else if (realY >= highlightItem.y + root.highlightedHeight) highlightDist = realY - highlightItem.y - root.highlightedHeight; else return 1; return 1 - Math.min(1, (Math.abs(highlightDist) + root.cellHeight) / ((root.numAboveBelow + 1) * root.cellHeight)) } Label { objectName: "username" + index anchors { left: parent.left leftMargin: units.gu(2) right: parent.right rightMargin: units.gu(2) top: parent.top // Add an offset to topMargin for any items below the highlight topMargin: units.gu(1) + (parent.belowHighlight ? parent.belowOffset : 0) } text: realName color: "white" elide: Text.ElideRight Behavior on anchors.topMargin { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } } } MouseArea { anchors { left: parent.left right: parent.right top: parent.top // Add an offset to topMargin for any items below the highlight topMargin: parent.belowHighlight ? parent.belowOffset : 0 } height: parent.height enabled: userList.currentIndex !== index && parent.opacity > 0 onClicked: root.selected(index) Behavior on anchors.topMargin { NumberAnimation { duration: root.moveDuration; easing.type: Easing.InOutQuad; } } } } // This is needed because ListView.moving is not true if the ListView // moves because of an internal event (e.g. currentIndex has changed) Timer { id: moveTimer running: false repeat: false interval: root.moveDuration } } Label { id: infoLabel objectName: "infoLabel" anchors { bottom: passwordInput.top left: parent.left topMargin: units.gu(1) bottomMargin: units.gu(1) leftMargin: units.gu(2) rightMargin: units.gu(1) } color: "white" width: root.width - anchors.leftMargin - anchors.rightMargin fontSize: "small" textFormat: Text.StyledText clip: true opacity: (userList.movingInternally || text == "") ? 0 : 1 Behavior on opacity { NumberAnimation { duration: 100 } } } TextField { id: passwordInput objectName: "passwordInput" anchors { bottom: highlightItem.bottom horizontalCenter: parent.horizontalCenter margins: units.gu(1) } height: units.gu(4.5) width: parent.width - anchors.margins * 2 opacity: userList.movingInternally ? 0 : 1 inputMethodHints: root.alphanumeric ? Qt.ImhNone : Qt.ImhDigitsOnly property string promptText placeholderText: root.wasPrompted ? promptText : (root.locked ? i18n.tr("Retry") : i18n.tr("Tap to unlock")) Behavior on opacity { NumberAnimation { duration: 100 } } onAccepted: { if (!enabled) return; root.focus = true; // so that it can handle Escape presses for us enabled = false; root.responded(text); } Keys.onEscapePressed: { root.selected(currentIndex); } Image { anchors { right: parent.right rightMargin: units.gu(2) verticalCenter: parent.verticalCenter } visible: !root.wasPrompted source: "graphics/icon_arrow.png" } WrongPasswordAnimation { id: wrongPasswordAnimation target: passwordInput } Connections { target: Qt.inputMethod onVisibleChanged: { if (!Qt.inputMethod.visible) { passwordInput.focus = false; } } } } MouseArea { id: passwordMouseArea objectName: "passwordMouseArea" anchors.fill: passwordInput enabled: !root.wasPrompted onClicked: root.tryToUnlock() } function resetAuthentication() { if (!userList.currentItem) { return; } infoLabel.text = ""; passwordInput.promptText = ""; passwordInput.text = ""; passwordInput.focus = false; passwordInput.enabled = true; root.wasPrompted = false; } }