Lomiri
UsersModel.cpp
1 /*
2  * Copyright (C) 2013, 2015-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 #include "Greeter.h"
18 #include "UsersModel.h"
19 #include <QIdentityProxyModel>
20 #include <QLightDM/UsersModel>
21 
22 #include <libintl.h>
23 
24 // First, we define an internal class that wraps LightDM's UsersModel. This
25 // class will modify some of the data coming from LightDM. For example, we
26 // modify any empty Real Names into just normal Names. We also add optional
27 // rows, depending on configuration.
28 // (We can't modify the data directly in UsersModel below because it won't sort
29 // using the modified data.)
30 class MangleModel : public QIdentityProxyModel
31 {
32  Q_OBJECT
33 
34 public:
35  explicit MangleModel(QObject* parent=0);
36 
37  QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
38  int rowCount(const QModelIndex &parent = QModelIndex()) const override;
39  QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
40 
41 private:
42  struct CustomRow {
43  QString name;
44  QString realName;
45  };
46 
47  int sourceRowCount() const;
48 
49  void updateGuestRow();
50  void updateManualRow();
51  void updateCustomRows();
52 
53  void addCustomRow(const CustomRow &newRow);
54  void removeCustomRow(const QString &rowName);
55 
56  QList<CustomRow> m_customRows;
57  bool m_updatingCustomRows;
58 };
59 
60 MangleModel::MangleModel(QObject* parent)
61  : QIdentityProxyModel(parent)
62  , m_updatingCustomRows(false)
63 {
64  setSourceModel(new QLightDM::UsersModel(this));
65 
66  updateCustomRows();
67 
68  // Would be nice if there were a rowCountChanged signal in the base class.
69  // We redo all custom rows on any row count change, because (A) some of
70  // custom rows (manual login) use row count information and (B) when
71  // testing, we use a modelReset signal as a way to indicate that a custom
72  // row has been toggled off or on.
73  connect(this, &QIdentityProxyModel::modelReset,
74  this, &MangleModel::updateCustomRows);
75  connect(this, &QIdentityProxyModel::rowsInserted,
76  this, &MangleModel::updateCustomRows);
77  connect(this, &QIdentityProxyModel::rowsRemoved,
78  this, &MangleModel::updateCustomRows);
79 }
80 
81 QVariant MangleModel::data(const QModelIndex &index, int role) const
82 {
83  QVariant variantData;
84 
85  if (index.row() >= rowCount())
86  return QVariant();
87 
88  bool isCustomRow = index.row() >= sourceRowCount();
89  if (isCustomRow && index.column() == 0) {
90  int customIndex = index.row() - sourceRowCount();
91  if (role == QLightDM::UsersModel::NameRole) {
92  variantData = m_customRows[customIndex].name;
93  } else if (role == QLightDM::UsersModel::RealNameRole) {
94  variantData = m_customRows[customIndex].realName;
95  } else if (role == QLightDM::UsersModel::LoggedInRole) {
96  variantData = false;
97  } else if (role == QLightDM::UsersModel::SessionRole) {
98  variantData = Greeter::instance()->defaultSessionHint();
99  }
100  } else {
101  variantData = QIdentityProxyModel::data(index, role);
102  }
103 
104  // If user's real name is empty, switch to unix name
105  if (role == QLightDM::UsersModel::RealNameRole && variantData.toString().isEmpty()) {
106  variantData = data(index, QLightDM::UsersModel::NameRole);
107  } else if (role == QLightDM::UsersModel::BackgroundPathRole && variantData.toString().startsWith('#')) {
108  const QString stringData = "data:image/svg+xml,<svg><rect width='100%' height='100%' fill='" + variantData.toString() + "'/></svg>";
109  variantData = stringData;
110  }
111 
112  // Workaround for liblightdm returning "" when a user has no default session
113  if (Q_UNLIKELY(role == QLightDM::UsersModel::SessionRole && variantData.toString().isEmpty())) {
114  variantData = Greeter::instance()->defaultSessionHint();
115  }
116 
117  return variantData;
118 }
119 
120 void MangleModel::addCustomRow(const CustomRow &newRow)
121 {
122  for (int i = 0; i < m_customRows.size(); i++) {
123  if (m_customRows[i].name == newRow.name) {
124  return; // we don't have custom rows that change content yet
125  }
126  }
127 
128  beginInsertRows(QModelIndex(), rowCount(), rowCount());
129  m_customRows << newRow;
130  endInsertRows();
131 }
132 
133 void MangleModel::removeCustomRow(const QString &rowName)
134 {
135  for (int i = 0; i < m_customRows.size(); i++) {
136  if (m_customRows[i].name == rowName) {
137  int rowNum = sourceRowCount() + i;
138  beginRemoveRows(QModelIndex(), rowNum, rowNum);
139  m_customRows.removeAt(i);
140  endRemoveRows();
141  break;
142  }
143  }
144 }
145 
146 void MangleModel::updateManualRow()
147 {
148  bool hasAnotherEntry = sourceRowCount() > 0;
149  for (int i = 0; !hasAnotherEntry && i < m_customRows.size(); i++) {
150  if (m_customRows[i].name != QStringLiteral("*other")) {
151  hasAnotherEntry = true;
152  }
153  }
154 
155  // Show manual login if we are asked to OR if no other entry exists
156  if (Greeter::instance()->showManualLoginHint() || !hasAnotherEntry)
157  addCustomRow({QStringLiteral("*other"), gettext("Login")});
158  else
159  removeCustomRow(QStringLiteral("*other"));
160 }
161 
162 void MangleModel::updateGuestRow()
163 {
164  if (Greeter::instance()->hasGuestAccount())
165  addCustomRow({QStringLiteral("*guest"), gettext("Guest Session")});
166  else
167  removeCustomRow(QStringLiteral("*guest"));
168 }
169 
170 void MangleModel::updateCustomRows()
171 {
172  // We update when rowCount changes, but we also insert/remove rows here.
173  // So guard this function to avoid recursion.
174  if (m_updatingCustomRows)
175  return;
176 
177  m_updatingCustomRows = true;
178  updateGuestRow();
179  updateManualRow();
180  m_updatingCustomRows = false;
181 }
182 
183 int MangleModel::rowCount(const QModelIndex &parent) const
184 {
185  if (parent.isValid())
186  return 0;
187  else
188  return sourceRowCount() + m_customRows.size();
189 }
190 
191 int MangleModel::sourceRowCount() const
192 {
193  return Greeter::instance()->hideUsersHint() ? 0 : sourceModel()->rowCount();
194 }
195 
196 QModelIndex MangleModel::index(int row, int column, const QModelIndex &parent) const
197 {
198  if (row >= rowCount())
199  return QModelIndex();
200 
201  bool isCustomRow = row >= sourceRowCount();
202  if (isCustomRow && !parent.isValid()) {
203  return createIndex(row, column);
204  } else {
205  return QIdentityProxyModel::index(row, column, parent);
206  }
207 }
208 
209 // **** Now we continue with actual UsersModel class ****
210 
211 UsersModel::UsersModel(QObject* parent)
212  : LomiriSortFilterProxyModelQML(parent)
213 {
214  setModel(new MangleModel(this));
215  setSortCaseSensitivity(Qt::CaseInsensitive);
216  setSortLocaleAware(true);
217  setSortRole(QLightDM::UsersModel::RealNameRole);
218  sort(0);
219 }
220 
221 bool UsersModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
222 {
223  auto leftName = source_left.data(QLightDM::UsersModel::NameRole);
224  auto rightName = source_right.data(QLightDM::UsersModel::NameRole);
225 
226  if (leftName == QStringLiteral("*guest"))
227  return false;
228  if (rightName == QStringLiteral("*guest"))
229  return true;
230  if (leftName == QStringLiteral("*other"))
231  return false;
232  if (rightName == QStringLiteral("*other"))
233  return true;
234 
235  return LomiriSortFilterProxyModelQML::lessThan(source_left, source_right);
236 }
237 
238 #include "UsersModel.moc"