LibrePCB Developers Documentation
editablelistmodel.h
Go to the documentation of this file.
1 /*
2  * LibrePCB - Professional EDA for everyone!
3  * Copyright (C) 2013 LibrePCB Developers, see AUTHORS.md for contributors.
4  * https://librepcb.org/
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program. If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #ifndef LIBREPCB_EDITOR_EDITABLELISTMODEL_H
21 #define LIBREPCB_EDITOR_EDITABLELISTMODEL_H
22 
23 /*******************************************************************************
24  * Includes
25  ******************************************************************************/
26 #include "comboboxdelegate.h"
27 
30 
31 #include <QtCore>
32 #include <QtWidgets>
33 
34 #include <optional.hpp>
35 
36 /*******************************************************************************
37  * Namespace / Forward Declarations
38  ******************************************************************************/
39 namespace librepcb {
40 namespace editor {
41 
42 /*******************************************************************************
43  * Types
44  ******************************************************************************/
45 
47 
48 /*******************************************************************************
49  * Class EditableListModel
50  ******************************************************************************/
51 
71 template <typename T,
73 class EditableListModel final : public QAbstractTableModel {
74  typedef typename T::value_type ValueType;
75 
76 public:
77  enum Column { COLUMN_TEXT, COLUMN_ACTIONS, _COLUMN_COUNT };
78 
79  // Constructors / Destructor
80  EditableListModel(const EditableListModel& other) noexcept = delete;
81  explicit EditableListModel(QObject* parent = nullptr) noexcept
82  : QAbstractTableModel(parent),
83  mChoices(),
84  mValues(),
85  mDefaultValue(),
86  mNewValue(),
87  mPlaceholderText(),
88  mComboBoxItems() {}
89  ~EditableListModel() noexcept {}
90 
91  // Getters
92  const T& getValues() const noexcept { return mValues; }
93 
94  // Setters
95  void setDefaultValue(const ValueType& value) noexcept {
96  mDefaultValue = value;
97  mNewValue = value;
98  }
99 
100  void setPlaceholderText(const QString& text) noexcept {
101  mPlaceholderText = text;
102  emit dataChanged(index(mValues.count(), COLUMN_TEXT),
103  index(mValues.count(), COLUMN_TEXT));
104  }
105 
106  void setValues(const T& values) noexcept {
107  emit beginResetModel();
108  mValues = values;
109  emit endResetModel();
110  }
111 
112  void setChoices(const T& choices) noexcept {
113  emit beginResetModel();
114  mChoices = choices;
115  updateComboBoxItems();
116  emit endResetModel();
117  }
118 
119  void setDisplayText(const ValueType& value, const QString& text) noexcept {
120  emit beginResetModel();
121  mDisplayTexts[value] = text;
122  emit endResetModel();
123  }
124 
125  void setIcon(const ValueType& value, const QIcon& icon) noexcept {
126  emit beginResetModel();
127  mIcons[value] = icon;
128  emit endResetModel();
129  }
130 
131  // Slots
132  void add(const QPersistentModelIndex& itemIndex) noexcept {
133  Q_UNUSED(itemIndex);
134 
135  if (!mNewValue) {
136  QMessageBox::critical(nullptr, tr("Error"), tr("Invalid value."));
137  return;
138  }
139 
140  if (mValues.contains(*mNewValue)) {
141  QMessageBox::critical(nullptr, tr("Error"),
142  tr("Value already contained in list."));
143  return;
144  }
145 
146  beginInsertRows(QModelIndex(), mValues.count(), mValues.count());
147  mValues.append(*mNewValue);
148  endInsertRows();
149 
150  mNewValue = mDefaultValue;
151  emit dataChanged(index(mValues.count(), 0),
152  index(mValues.count(), _COLUMN_COUNT - 1));
153  }
154 
155  void remove(const QPersistentModelIndex& itemIndex) noexcept {
156  int row = itemIndex.data(Qt::EditRole).toInt();
157  if ((row >= 0) && (row < mValues.count())) {
158  beginRemoveRows(QModelIndex(), row, row);
159  mValues.removeAt(row);
160  endRemoveRows();
161  }
162  }
163 
164  void moveUp(const QPersistentModelIndex& itemIndex) noexcept {
165  int row = itemIndex.data(Qt::EditRole).toInt();
166  if (row >= 1) {
167  mValues.move(row, row - 1);
168  emit dataChanged(index(row - 1, 0), index(row, _COLUMN_COUNT - 1));
169  }
170  }
171 
172  void moveDown(const QPersistentModelIndex& itemIndex) noexcept {
173  int row = itemIndex.data(Qt::EditRole).toInt();
174  if (row < (mValues.count() - 1)) {
175  mValues.move(row, row + 1);
176  emit dataChanged(index(row, 0), index(row + 1, _COLUMN_COUNT - 1));
177  }
178  }
179 
180  // Inherited from QAbstractItemModel
181  int rowCount(const QModelIndex& parent = QModelIndex()) const override {
182  if (!parent.isValid()) {
183  return mValues.count() + 1;
184  }
185  return 0;
186  }
187 
188  int columnCount(const QModelIndex& parent = QModelIndex()) const override {
189  if (!parent.isValid()) {
190  return _COLUMN_COUNT;
191  }
192  return 0;
193  }
194 
195  QVariant data(const QModelIndex& index,
196  int role = Qt::DisplayRole) const override {
197  if (!index.isValid()) {
198  return QVariant();
199  }
200 
201  tl::optional<ValueType> value = mNewValue;
202  if ((index.row() >= 0) && (index.row() < mValues.count())) {
203  value = mValues.at(index.row());
204  }
205  bool showPlaceholder = (index.row() == mValues.count()) &&
206  ((!value) || getDisplayText(*value).isEmpty());
207  switch (index.column()) {
208  case COLUMN_TEXT: {
209  switch (role) {
210  case Qt::DisplayRole:
211  return showPlaceholder
212  ? mPlaceholderText
213  : (value ? getDisplayText(*value) : QString());
214  case Qt::DecorationRole:
215  return value ? mIcons.value(*value) : QIcon();
216  case Qt::EditRole:
217  return value ? getDataForValue(*value) : QVariant();
218  case Qt::ForegroundRole:
219  if (showPlaceholder) {
220  QColor color = qApp->palette().text().color();
221  color.setAlpha(128);
222  return QBrush(color);
223  } else {
224  return QVariant();
225  }
226  case Qt::UserRole:
227  return QVariant::fromValue(mComboBoxItems);
228  default:
229  return QVariant();
230  }
231  }
232  case COLUMN_ACTIONS: {
233  switch (role) {
234  case Qt::EditRole:
235  return index.row();
236  default:
237  return QVariant();
238  }
239  }
240  default:
241  return QVariant();
242  }
243 
244  return QVariant();
245  }
246 
247  QVariant headerData(int section, Qt::Orientation orientation,
248  int role = Qt::DisplayRole) const override {
249  if (orientation == Qt::Horizontal) {
250  if (role == Qt::DisplayRole) {
251  switch (section) {
252  case COLUMN_TEXT:
253  return tr("Item");
254  default:
255  return QVariant();
256  }
257  }
258  } else if (orientation == Qt::Vertical) {
259  bool isLastRow = section >= mValues.count();
260  if (role == Qt::DisplayRole) {
261  return isLastRow ? tr("New:") : QString::number(section + 1);
262  } else if (role == Qt::TextAlignmentRole) {
263  return QVariant(Qt::AlignRight | Qt::AlignVCenter);
264  }
265  }
266  return QVariant();
267  }
268 
269  Qt::ItemFlags flags(const QModelIndex& index) const override {
270  Qt::ItemFlags f = QAbstractTableModel::flags(index);
271  if (index.isValid() && (index.row() == mValues.count())) {
272  f |= Qt::ItemIsEditable;
273  }
274  return f;
275  }
276 
277  bool setData(const QModelIndex& index, const QVariant& value,
278  int role = Qt::EditRole) override {
279  bool isLastRow = index.row() >= mValues.count();
280 
281  if ((index.column() == COLUMN_TEXT) && role == Qt::EditRole) {
282  if (isLastRow) {
283  mNewValue = convertInputValue(value, mDefaultValue);
284  }
285  emit dataChanged(index, index);
286  return true;
287  }
288 
289  return false;
290  }
291 
292  // Operator Overloadings
293  EditableListModel& operator=(const EditableListModel& rhs) noexcept;
294 
295 private: // Methods
296  QString getDisplayText(const Uuid& value) const noexcept {
297  return mDisplayTexts.value(value, value.toStr());
298  }
299 
300  QString getDisplayText(const QUrl& value) const noexcept {
301  return value.toDisplayString();
302  }
303 
304  QString getDisplayText(const QString& value) const noexcept {
305  switch (TYPE) {
307  return Toolbox::prettyPrintLocale(value);
308  default:
309  return mDisplayTexts.value(value, value);
310  }
311  }
312 
313  QVariant getDataForValue(const Uuid& value) const noexcept {
314  return value.toStr();
315  }
316 
317  QVariant getDataForValue(const QUrl& value) const noexcept { return value; }
318 
319  QVariant getDataForValue(const QString& value) const noexcept {
320  return value;
321  }
322 
323  tl::optional<Uuid> convertInputValue(
324  const QVariant& input, const tl::optional<Uuid>& tag) const noexcept {
325  Q_UNUSED(tag); // used only for template tag dispatching
326  return Uuid::tryFromString(input.toString());
327  }
328 
329  tl::optional<QString> convertInputValue(
330  const QVariant& input, const tl::optional<QString>& tag) const noexcept {
331  Q_UNUSED(tag); // used only for template tag dispatching
332  QString str = input.toString().trimmed();
333  return str.isEmpty() ? tl::nullopt : tl::make_optional(str);
334  }
335 
336  tl::optional<QUrl> convertInputValue(
337  const QVariant& input, const tl::optional<QUrl>& tag) const noexcept {
338  Q_UNUSED(tag); // used only for template tag dispatching
339  QUrl url = QUrl::fromUserInput(input.toString());
340  return url.isValid() ? tl::make_optional(url) : tl::nullopt;
341  }
342 
343  void updateComboBoxItems() noexcept {
344  mComboBoxItems.clear();
345  foreach (const ValueType& choice, mChoices) {
346  ComboBoxDelegate::Item item{getDisplayText(choice), mIcons[choice],
347  getDataForValue(choice)};
348  mComboBoxItems.append(item);
349  }
350  mComboBoxItems.sort();
351  }
352 
353 private: // Data
356  tl::optional<ValueType> mDefaultValue;
357  tl::optional<ValueType> mNewValue;
359  QHash<ValueType, QString> mDisplayTexts;
360  QHash<ValueType, QIcon> mIcons;
362 };
363 
364 /*******************************************************************************
365  * End of File
366  ******************************************************************************/
367 
368 } // namespace editor
369 } // namespace librepcb
370 
371 #endif
void setIcon(const ValueType &value, const QIcon &icon) noexcept
Definition: editablelistmodel.h:125
Definition: editablelistmodel.h:77
Qt::ItemFlags flags(const QModelIndex &index) const override
Definition: editablelistmodel.h:269
QString getDisplayText(const QUrl &value) const noexcept
Definition: editablelistmodel.h:300
A generic QAbstractTableModel subclass to view and edit list of various data types.
Definition: editablelistmodel.h:73
T mChoices
Definition: editablelistmodel.h:354
Definition: comboboxdelegate.h:47
void setPlaceholderText(const QString &text) noexcept
Definition: editablelistmodel.h:100
Definition: occmodel.cpp:77
void setValues(const T &values) noexcept
Definition: editablelistmodel.h:106
int columnCount(const QModelIndex &parent=QModelIndex()) const override
Definition: editablelistmodel.h:188
QString getDisplayText(const QString &value) const noexcept
Definition: editablelistmodel.h:304
tl::optional< QString > convertInputValue(const QVariant &input, const tl::optional< QString > &tag) const noexcept
Definition: editablelistmodel.h:329
const T & getValues() const noexcept
Definition: editablelistmodel.h:92
void moveDown(const QPersistentModelIndex &itemIndex) noexcept
Definition: editablelistmodel.h:172
T::value_type ValueType
Definition: editablelistmodel.h:74
static QString prettyPrintLocale(const QString &code) noexcept
Pretty print the name of a QLocale.
Definition: toolbox.cpp:265
void updateComboBoxItems() noexcept
Definition: editablelistmodel.h:343
QHash< ValueType, QString > mDisplayTexts
Definition: editablelistmodel.h:359
EditableListModel(QObject *parent=nullptr) noexcept
Definition: editablelistmodel.h:81
QString mPlaceholderText
Definition: editablelistmodel.h:358
tl::optional< Uuid > convertInputValue(const QVariant &input, const tl::optional< Uuid > &tag) const noexcept
Definition: editablelistmodel.h:323
QVariant getDataForValue(const Uuid &value) const noexcept
Definition: editablelistmodel.h:313
QVariant getDataForValue(const QUrl &value) const noexcept
Definition: editablelistmodel.h:317
Definition: comboboxdelegate.h:52
void add(const QPersistentModelIndex &itemIndex) noexcept
Definition: editablelistmodel.h:132
void setDefaultValue(const ValueType &value) noexcept
Definition: editablelistmodel.h:95
~EditableListModel() noexcept
Definition: editablelistmodel.h:89
ComboBoxDelegate::Items mComboBoxItems
Definition: editablelistmodel.h:361
tl::optional< ValueType > mNewValue
Definition: editablelistmodel.h:357
Column
Definition: editablelistmodel.h:77
QHash< ValueType, QIcon > mIcons
Definition: editablelistmodel.h:360
bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole) override
Definition: editablelistmodel.h:277
EditableListModelType
Definition: editablelistmodel.h:46
void setDisplayText(const ValueType &value, const QString &text) noexcept
Definition: editablelistmodel.h:119
int rowCount(const QModelIndex &parent=QModelIndex()) const override
Definition: editablelistmodel.h:181
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
Definition: editablelistmodel.h:195
QString getDisplayText(const Uuid &value) const noexcept
Definition: editablelistmodel.h:296
QVariant getDataForValue(const QString &value) const noexcept
Definition: editablelistmodel.h:319
void setChoices(const T &choices) noexcept
Definition: editablelistmodel.h:112
The Uuid class is a replacement for QUuid to get UUID strings without {} braces.
Definition: uuid.h:58
static tl::optional< Uuid > tryFromString(const QString &str) noexcept
Try creating a Uuid from a string, returning empty optional if invalid.
Definition: uuid.cpp:119
tl::optional< QUrl > convertInputValue(const QVariant &input, const tl::optional< QUrl > &tag) const noexcept
Definition: editablelistmodel.h:336
void moveUp(const QPersistentModelIndex &itemIndex) noexcept
Definition: editablelistmodel.h:164
T mValues
Definition: editablelistmodel.h:355
QVariant headerData(int section, Qt::Orientation orientation, int role=Qt::DisplayRole) const override
Definition: editablelistmodel.h:247
tl::optional< ValueType > mDefaultValue
Definition: editablelistmodel.h:356