on xenial help sign in
refactor UsersModel a bit to allow further future custom rows easier (though I don't know of any planned ones); add some tests
48cf7e6Michael Terry
plugins/LightDM/UsersModel.cpp+75−68
@@ -39,54 +39,54 @@ public:
39 QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
39 QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
40
40
41private:
41private:
42− int manualRow() const;
42+ struct CustomRow {
43− int guestRow() const;
43+ QString name;
44+ QString realName;
45+ };
44
46
45− void updateShowManual();
47+ void addCustomRow(const CustomRow &newRow);
48+ void removeCustomRow(const QString &rowName);
49+ void updateGuestRow();
50+ void updateManualRow();
46
51
47− bool m_showManual;
52+ QList<CustomRow> m_customRows;
48};
53};
49
54
50MangleModel::MangleModel(QObject* parent)
55MangleModel::MangleModel(QObject* parent)
51 : QIdentityProxyModel(parent)
56 : QIdentityProxyModel(parent)
52− , m_showManual(false)
53{
57{
54 if (!Greeter::instance()->hideUsersHint()) {
58 if (!Greeter::instance()->hideUsersHint()) {
55 setSourceModel(new QLightDM::UsersModel(this));
59 setSourceModel(new QLightDM::UsersModel(this));
56 }
60 }
57
61
58− updateShowManual();
62+ updateGuestRow();
63+ updateManualRow();
59
64
65+ // Would be nice if there were a rowCountChanged signal in the base class
60 connect(this, &QIdentityProxyModel::modelReset,
66 connect(this, &QIdentityProxyModel::modelReset,
61− this, &MangleModel::updateShowManual);
67+ this, &MangleModel::updateManualRow);
62 connect(this, &QIdentityProxyModel::rowsInserted,
68 connect(this, &QIdentityProxyModel::rowsInserted,
63− this, &MangleModel::updateShowManual);
69+ this, &MangleModel::updateManualRow);
64 connect(this, &QIdentityProxyModel::rowsRemoved,
70 connect(this, &QIdentityProxyModel::rowsRemoved,
65− this, &MangleModel::updateShowManual);
71+ this, &MangleModel::updateManualRow);
66}
72}
67
73
68QVariant MangleModel::data(const QModelIndex &index, int role) const
74QVariant MangleModel::data(const QModelIndex &index, int role) const
69{
75{
70 QVariant variantData;
76 QVariant variantData;
71
77
72− if (index.row() == manualRow() && index.column() == 0) {
78+ bool isCustomRow = index.row() >= sourceModel()->rowCount() &&
73− switch (role) {
79+ index.row() < rowCount();
74− case QLightDM::UsersModel::NameRole: return QStringLiteral("*other");
80+ if (isCustomRow && index.column() == 0) {
75− case QLightDM::UsersModel::RealNameRole: return gettext("Login");
81+ int customIndex = index.row() - sourceModel()->rowCount();
76− case QLightDM::UsersModel::LoggedInRole: return false;
77− case QLightDM::UsersModel::SessionRole: return Greeter::instance()->defaultSessionHint();
78− default: return QVariant();
79− }
80− } else if (index.row() == guestRow() && index.column() == 0) {
81− switch (role) {
82− case QLightDM::UsersModel::NameRole:
82+ if (role == QLightDM::UsersModel::NameRole) {
83− variantData = QStringLiteral("*guest"); break;
83+ variantData = m_customRows[customIndex].name;
84− case QLightDM::UsersModel::RealNameRole:
84+ } else if (role == QLightDM::UsersModel::RealNameRole) {
85− variantData = gettext("Guest Session"); break;
85+ variantData = m_customRows[customIndex].realName;
86− case QLightDM::UsersModel::LoggedInRole:
86+ } else if (role == QLightDM::UsersModel::LoggedInRole) {
87− variantData = false; break;
87+ variantData = false;
88− case QLightDM::UsersModel::SessionRole:
88+ } else if (role == QLightDM::UsersModel::SessionRole) {
89− variantData = Greeter::instance()->defaultSessionHint(); break;
89+ variantData = Greeter::instance()->defaultSessionHint();
90 }
90 }
91 } else {
91 } else {
92 variantData = QIdentityProxyModel::data(index, role);
92 variantData = QIdentityProxyModel::data(index, role);
@@ -94,7 +94,7 @@ QVariant MangleModel::data(const QModelIndex &index, int role) const
94
94
95 // If user's real name is empty, switch to unix name
95 // If user's real name is empty, switch to unix name
96 if (role == QLightDM::UsersModel::RealNameRole && variantData.toString().isEmpty()) {
96 if (role == QLightDM::UsersModel::RealNameRole && variantData.toString().isEmpty()) {
97− variantData = QIdentityProxyModel::data(index, QLightDM::UsersModel::NameRole);
97+ variantData = data(index, QLightDM::UsersModel::NameRole);
98 } else if (role == QLightDM::UsersModel::BackgroundPathRole && variantData.toString().startsWith('#')) {
98 } else if (role == QLightDM::UsersModel::BackgroundPathRole && variantData.toString().startsWith('#')) {
99 const QString stringData = "data:image/svg+xml,<svg><rect width='100%' height='100%' fill='" + variantData.toString() + "'/></svg>";
99 const QString stringData = "data:image/svg+xml,<svg><rect width='100%' height='100%' fill='" + variantData.toString() + "'/></svg>";
100 variantData = stringData;
100 variantData = stringData;
@@ -103,64 +103,67 @@ QVariant MangleModel::data(const QModelIndex &index, int role) const
103 return variantData;
103 return variantData;
104}
104}
105
105
106−void MangleModel::updateShowManual()
106+void MangleModel::addCustomRow(const CustomRow &newRow)
107{
107{
108− // Show manual login if we are asked to OR if no other entry exists
108+ for (int i = 0; i < m_customRows.size(); i++) {
109− bool showManual = Greeter::instance()->showManualLoginHint() ||
109+ if (m_customRows[i].name == newRow.name) {
110− (QIdentityProxyModel::rowCount() == 0 &&
110+ return; // we don't have custom rows that change content yet
111− !Greeter::instance()->hasGuestAccount());
111+ }
112−
113− if (m_showManual != showManual) {
114− int row = QIdentityProxyModel::rowCount();
115− if (showManual)
116− beginInsertRows(QModelIndex(), row, row);
117− else
118− beginRemoveRows(QModelIndex(), row, row);
119−
120− m_showManual = showManual;
121−
122− if (showManual)
123− endInsertRows();
124− else
125− endRemoveRows();
126 }
112 }
113+
114+ beginInsertRows(QModelIndex(), rowCount(), rowCount());
115+ m_customRows << newRow;
116+ endInsertRows();
127}
117}
128
118
129−int MangleModel::manualRow() const
119+void MangleModel::removeCustomRow(const QString &rowName)
130{
120{
131− if (!m_showManual)
121+ for (int i = 0; i < m_customRows.size(); i++) {
132− return -1;
122+ if (m_customRows[i].name == rowName) {
133−
123+ int rowNum = sourceModel()->rowCount() + i;
134− return QIdentityProxyModel::rowCount();
124+ beginRemoveRows(QModelIndex(), rowNum, rowNum);
125+ m_customRows.removeAt(i);
126+ endRemoveRows();
127+ break;
128+ }
129+ }
135}
130}
136
131
137−int MangleModel::guestRow() const
132+void MangleModel::updateManualRow()
138{
133{
139− if (!Greeter::instance()->hasGuestAccount())
134+ bool hasAnotherEntry = sourceModel()->rowCount() > 0;
140− return -1;
135+ for (int i = 0; !hasAnotherEntry && i < m_customRows.size(); i++) {
136+ if (m_customRows[i].name != QStringLiteral("*other")) {
137+ hasAnotherEntry = true;
138+ }
139+ }
141
140
142− int row = QIdentityProxyModel::rowCount();
141+ // Show manual login if we are asked to OR if no other entry exists
143− if (m_showManual)
142+ if (Greeter::instance()->showManualLoginHint() || !hasAnotherEntry)
144− row++;
143+ addCustomRow({QStringLiteral("*other"), gettext("Login")});
144+ else
145+ removeCustomRow(QStringLiteral("*other"));
146+}
145
147
146− return row;
148+void MangleModel::updateGuestRow()
149+{
150+ if (Greeter::instance()->hasGuestAccount())
151+ addCustomRow({QStringLiteral("*guest"), gettext("Guest Session")});
152+ else
153+ removeCustomRow(QStringLiteral("*guest"));
147}
154}
148
155
149int MangleModel::rowCount(const QModelIndex &parent) const
156int MangleModel::rowCount(const QModelIndex &parent) const
150{
157{
151− auto count = QIdentityProxyModel::rowCount(parent);
158+ return QIdentityProxyModel::rowCount(parent) +
152−
153− if (m_showManual && !parent.isValid())
159+ (parent.isValid() ? 0 : m_customRows.size());
154− count++;
155− if (Greeter::instance()->hasGuestAccount() && !parent.isValid())
156− count++;
157−
158− return count;
159}
160}
160
161
161QModelIndex MangleModel::index(int row, int column, const QModelIndex &parent) const
162QModelIndex MangleModel::index(int row, int column, const QModelIndex &parent) const
162{
163{
164+ bool isCustomRow = row >= sourceModel()->rowCount() &&
165+ row < rowCount();
163− if ((row == manualRow() || row == guestRow()) && !parent.isValid()) {
166+ if (isCustomRow && !parent.isValid()) {
164 return createIndex(row, column);
167 return createIndex(row, column);
165 } else {
168 } else {
166 return QIdentityProxyModel::index(row, column, parent);
169 return QIdentityProxyModel::index(row, column, parent);
@@ -188,6 +191,10 @@ bool UsersModel::lessThan(const QModelIndex &source_left, const QModelIndex &sou
188 return false;
191 return false;
189 if (rightName == QStringLiteral("*guest"))
192 if (rightName == QStringLiteral("*guest"))
190 return true;
193 return true;
194+ if (leftName == QStringLiteral("*other"))
195+ return false;
196+ if (rightName == QStringLiteral("*other"))
197+ return true;
191
198
192 return UnitySortFilterProxyModelQML::lessThan(source_left, source_right);
199 return UnitySortFilterProxyModelQML::lessThan(source_left, source_right);
193}
200}
qml/Greeter/GreeterPrompt.qml+1
@@ -194,6 +194,7 @@ FocusScope {
194 // palette color, whereas we want raisedSecondaryText.
194 // palette color, whereas we want raisedSecondaryText.
195 Label {
195 Label {
196 id: hint
196 id: hint
197+ objectName: "promptHint"
197 anchors {
198 anchors {
198 left: parent.left
199 left: parent.left
199 right: parent.right
200 right: parent.right
tests/mocks/liblightdm/MockUsersModel.cpp+2
@@ -67,6 +67,8 @@ UsersModel::UsersModel(QObject *parent)
67
67
68 connect(MockController::instance(), &MockController::hasGuestAccountHintChanged,
68 connect(MockController::instance(), &MockController::hasGuestAccountHintChanged,
69 this, &UsersModel::resetEntries);
69 this, &UsersModel::resetEntries);
70+ connect(MockController::instance(), &MockController::showManualLoginHintChanged,
71+ this, &UsersModel::resetEntries);
70 connect(MockController::instance(), &MockController::userModeChanged,
72 connect(MockController::instance(), &MockController::userModeChanged,
71 this, &UsersModel::resetEntries);
73 this, &UsersModel::resetEntries);
72 resetEntries();
74 resetEntries();
tests/plugins/LightDM/IntegratedLightDM/usersmodel.cpp+53−2
@@ -79,11 +79,9 @@ private Q_SLOTS:
79 QCOMPARE(findName(model, QStringLiteral("*guest")), -1);
79 QCOMPARE(findName(model, QStringLiteral("*guest")), -1);
80
80
81 QLightDM::MockController::instance()->setHasGuestAccountHint(true);
81 QLightDM::MockController::instance()->setHasGuestAccountHint(true);
82− recreateModel();
83
82
84 int i = findName(model, QStringLiteral("*guest"));
83 int i = findName(model, QStringLiteral("*guest"));
85 QVERIFY(i >= 0);
84 QVERIFY(i >= 0);
86− QCOMPARE(i, model->count() - 1);
87
85
88 auto realName = model->data(i, QLightDM::UsersModel::RealNameRole).toString();
86 auto realName = model->data(i, QLightDM::UsersModel::RealNameRole).toString();
89 QCOMPARE(realName, QStringLiteral("Guest Session"));
87 QCOMPARE(realName, QStringLiteral("Guest Session"));
@@ -95,6 +93,59 @@ private Q_SLOTS:
95 QCOMPARE(session, Greeter::instance()->defaultSessionHint());
93 QCOMPARE(session, Greeter::instance()->defaultSessionHint());
96 }
94 }
97
95
96+ void testHasManual()
97+ {
98+ // sanity check that it doesn't start already present
99+ QCOMPARE(findName(model, QStringLiteral("*other")), -1);
100+
101+ QLightDM::MockController::instance()->setShowManualLoginHint(true);
102+
103+ int i = findName(model, QStringLiteral("*other"));
104+ QVERIFY(i >= 0);
105+
106+ auto realName = model->data(i, QLightDM::UsersModel::RealNameRole).toString();
107+ QCOMPARE(realName, QStringLiteral("Login"));
108+
109+ auto loggedIn = model->data(i, QLightDM::UsersModel::LoggedInRole).toBool();
110+ QVERIFY(!loggedIn);
111+
112+ auto session = model->data(i, QLightDM::UsersModel::SessionRole).toString();
113+ QCOMPARE(session, Greeter::instance()->defaultSessionHint());
114+ }
115+
116+ void testHideUsers()
117+ {
118+ QLightDM::MockController::instance()->setHideUsersHint(true);
119+
120+ QCOMPARE(model->count(), 1);
121+ auto name = model->data(0, QLightDM::UsersModel::NameRole).toString();
122+ QCOMPARE(name, QStringLiteral("*other"));
123+ }
124+
125+ void testHideUsersWithGuest()
126+ {
127+ QLightDM::MockController::instance()->setHideUsersHint(true);
128+ QLightDM::MockController::instance()->setHasGuestAccountHint(true);
129+
130+ QCOMPARE(model->count(), 1);
131+ auto name = model->data(0, QLightDM::UsersModel::NameRole).toString();
132+ QCOMPARE(name, QStringLiteral("*guest"));
133+ }
134+
135+ void testCustomEntriesAreLast()
136+ {
137+ QLightDM::MockController::instance()->setHasGuestAccountHint(true);
138+ QLightDM::MockController::instance()->setShowManualLoginHint(true);
139+
140+ QVERIFY(model->count() > 2);
141+
142+ int manualIndex = findName(model, QStringLiteral("*other"));
143+ QCOMPARE(manualIndex, model->count() - 2);
144+
145+ int guestIndex = findName(model, QStringLiteral("*guest"));
146+ QCOMPARE(guestIndex, model->count() - 1);
147+ }
148+
98private:
149private:
99 UsersModel *model = nullptr;
150 UsersModel *model = nullptr;
100};
151};
tests/qmltests/Greeter/tst_Greeter.qml+8
@@ -612,6 +612,14 @@ Item {
612 verify(!view.locked);
612 verify(!view.locked);
613 }
613 }
614
614
615+ function test_hasManualLoginHint() {
616+ LightDMController.showManualLoginHint = true;
617+ resetLoader();
618+ var i = selectUser("*other");
619+ compare(i, LightDM.Users.count - 1); // manual should be last
620+ verify(view.locked);
621+ }
622+
615 function test_fingerprintSuccess() {
623 function test_fingerprintSuccess() {
616 var index = selectUser("has-password");
624 var index = selectUser("has-password");
617 unlockAndShowGreeter(); // turn on lockscreen mode
625 unlockAndShowGreeter(); // turn on lockscreen mode
tests/qmltests/tst_Shell.qml+29−1
@@ -647,7 +647,8 @@ Rectangle {
647 // Shell instance gets destroyed.
647 // Shell instance gets destroyed.
648 tryCompare(shellLoader, "itemDestroyed", true);
648 tryCompare(shellLoader, "itemDestroyed", true);
649
649
650+ LightDMController.reset();
650− setLightDMMockMode("single"); // back to the default value
651+ setLightDMMockMode("single"); // these tests default to "single"
651
652
652 AccountsService.demoEdges = false;
653 AccountsService.demoEdges = false;
653 AccountsService.demoEdgesCompleted = [];
654 AccountsService.demoEdgesCompleted = [];
@@ -1470,6 +1471,33 @@ Rectangle {
1470 }
1471 }
1471 }
1472 }
1472
1473
1474+ function test_manualLoginFlow() {
1475+ LightDMController.showManualLoginHint = true;
1476+ setLightDMMockMode("full");
1477+ loadShell("desktop");
1478+
1479+ var i = selectUser("*other");
1480+ var greeter = findChild(shell, "greeter");
1481+ var username = findChild(greeter, "username" + i);
1482+ var promptField = findChild(greeter, "promptField");
1483+ var promptHint = findChild(greeter, "promptHint");
1484+
1485+ compare(username.text, "Login");
1486+ compare(promptHint.text, "Username");
1487+
1488+ tryCompare(promptField, "activeFocus", true);
1489+ typeString("has-password");
1490+ keyClick(Qt.Key_Enter);
1491+
1492+ tryCompare(username, "text", "has-password");
1493+ tryCompare(promptHint, "text", "Passphrase");
1494+
1495+ typeString("password");
1496+ keyClick(Qt.Key_Enter);
1497+
1498+ confirmLoggedIn(true);
1499+ }
1500+
1473 function test_launcherInverted_data() {
1501 function test_launcherInverted_data() {
1474 return [
1502 return [
1475 {tag: "phone", formFactor: "phone", usageScenario: "phone", launcherInverted: true},
1503 {tag: "phone", formFactor: "phone", usageScenario: "phone", launcherInverted: true},