swift
listmodelbase.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2013 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
4 // Drag and drop docu:
5 // http://doc.qt.io/qt-5/model-view-programming.html#using-drag-and-drop-with-item-views
6 
8 
9 #include <QJsonDocument>
10 #include <QList>
11 #include <QMimeData>
12 
13 #include "gui/guiutility.h"
14 #include "gui/models/allmodelcontainers.h"
15 #include "misc/variant.h"
16 #include "misc/worker.h"
17 
18 using namespace swift::misc;
19 using namespace swift::misc::aviation;
20 
21 namespace swift::gui::models
22 {
23  template <typename T, bool UseCompare>
24  CListModelBase<T, UseCompare>::CListModelBase(const QString &translationContext, QObject *parent)
25  : CListModelBaseNonTemplate(translationContext, parent)
26  {}
27 
28  template <typename T, bool UseCompare>
29  int CListModelBase<T, UseCompare>::rowCount(const QModelIndex &parentIndex) const
30  {
31  Q_UNUSED(parentIndex)
32  return this->containerOrFilteredContainer().size();
33  }
34 
35  template <typename T, bool UseCompare>
36  bool CListModelBase<T, UseCompare>::canDropMimeData(const QMimeData *data, Qt::DropAction action, int row,
37  int column, const QModelIndex &parent) const
38  {
39  Q_UNUSED(action)
40  Q_UNUSED(row)
41  Q_UNUSED(column)
42  Q_UNUSED(parent)
43  if (!this->isDropAllowed()) { return false; }
44  if (!this->acceptDrop(data)) { return false; }
45  return true;
46  }
47 
48  template <typename T, bool UseCompare>
49  bool CListModelBase<T, UseCompare>::dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int row,
50  int column, const QModelIndex &parent)
51  {
52  Q_UNUSED(row)
53  Q_UNUSED(column)
54 
55  if (!this->isOrderable() || !this->acceptDrop(mimeData)) { return false; }
56  const CVariant valueVariant(this->toCVariant(mimeData));
57  if (valueVariant.isValid())
58  {
59  if (action == Qt::MoveAction)
60  {
61  const ContainerType container(valueVariant.value<ContainerType>());
62  if (container.isEmpty()) { return false; }
63  const int position = parent.row();
64  this->moveItems(container, position);
65  }
66  }
67  return true;
68  }
69 
70  template <typename T, bool UseCompare>
71  bool CListModelBase<T, UseCompare>::isValidIndex(const QModelIndex &index) const
72  {
73  if (!index.isValid()) { return false; }
74  return (index.row() >= 0 && index.row() < this->rowCount(index) && index.column() >= 0 &&
75  index.column() < this->columnCount(index));
76  }
77 
78  template <typename T, bool UseCompare>
79  QVariant CListModelBase<T, UseCompare>::data(const QModelIndex &index, int role) const
80  {
81  // check / init
82  if (!this->isValidIndex(index)) { return QVariant(); }
83 
84  if (role == Qt::BackgroundRole) { return CListModelBaseNonTemplate::data(index, role); }
85 
86  // Formatter
87  const CDefaultFormatter *formatter = m_columns.getFormatter(index);
88  Q_ASSERT_X(formatter, Q_FUNC_INFO, "Missing formatter");
89 
90  // Upfront checking avoids unnecessary data fetching
91  if (!formatter || !formatter->supportsRole(role)) { return CListModelBaseNonTemplate::data(index, role); }
92  // index, upfront checking
93  const int row = index.row();
94  const int col = index.column();
95  const CPropertyIndex propertyIndex = this->columnToPropertyIndex(col);
96  const int propertyIndexFront = propertyIndex.frontCasted<int>();
97 
98  // special cases
99  switch (propertyIndexFront)
100  {
101  case CPropertyIndexRef::GlobalIndexLineNumber: return QVariant::fromValue(row + 1);
102  case CPropertyIndexRef::GlobalIndexEmpty: return {};
103  default: break; // continue here
104  }
105 
106  // Formatted data
107  const ObjectType obj = this->containerOrFilteredContainer()[row];
108  return formatter->data(role, obj.propertyByIndex(propertyIndex)).getQVariant();
109  }
110 
111  template <typename T, bool UseCompare>
112  bool CListModelBase<T, UseCompare>::setData(const QModelIndex &index, const QVariant &value, int role)
113  {
114  Qt::ItemDataRole dataRole = static_cast<Qt::ItemDataRole>(role);
115  if (!(dataRole == Qt::UserRole || dataRole == Qt::EditRole)) { return false; }
116 
117  // check / init
118  if (!this->isValidIndex(index)) { return false; }
119  if (!m_columns.isEditable(index)) { return false; }
120  const CDefaultFormatter *formatter = m_columns.getFormatter(index);
121  Q_ASSERT(formatter);
122  if (!formatter) { return false; }
123 
124  ObjectType obj = m_container[index.row()];
125  const ObjectType currentObject(obj);
126  const CPropertyIndex propertyIndex = this->columnToPropertyIndex(index.column());
127  obj.setPropertyByIndex(propertyIndex, value);
128 
129  if (obj != currentObject)
130  {
131  const QModelIndex topLeft = index.sibling(index.row(), 0);
132  const QModelIndex bottomRight = index.sibling(index.row(), this->columnCount() - 1);
133  m_container[index.row()] = obj;
134  const CVariant co = CVariant::fromValue(obj);
135  emit objectChanged(co, propertyIndex);
136  emit this->dataChanged(topLeft, bottomRight);
137  this->updateFilteredContainer();
138  return true;
139  }
140  return false;
141  }
142 
143  template <typename T, bool UseCompare>
144  bool CListModelBase<T, UseCompare>::setInContainer(const QModelIndex &index, const ObjectType &obj)
145  {
146  if (!index.isValid()) { return false; }
147  const int row = index.row();
148  if (row < 0 || row >= this->container().size()) { return false; }
149  m_container[row] = obj;
150  return true;
151  }
152 
153  template <typename T, bool UseCompare>
154  int CListModelBase<T, UseCompare>::update(const ContainerType &container, bool sort)
155  {
156  if (m_modelDestroyed) { return 0; }
157 
158  // Keep sorting out of begin/end reset model
159  ContainerType sortedContainer;
160  ContainerType selection;
161  if (m_selectionModel) { selection = m_selectionModel->selectedObjects(); }
162  const int oldSize = m_container.size();
163  const bool performSort = sort && container.size() > 1 && this->hasValidSortColumn();
164  if (performSort)
165  {
166  const int sortColumn = this->getSortColumn();
167  sortedContainer = this->sortContainerByColumn(container, sortColumn, m_sortOrder);
168  }
169 
170  this->beginResetModel();
171  m_container = performSort ? sortedContainer : container;
172  this->updateFilteredContainer(); // use sorted container for filtered if applicable
173  this->endResetModel();
174 
175  // reselect if implemented in specialized view
176  if (!selection.isEmpty()) { m_selectionModel->selectObjects(selection); }
177 
178  const int newSize = m_container.size();
179  Q_UNUSED(oldSize)
180 
181  // I have to update even with same size because I cannot tell what/if data are changed
182  this->emitModelDataChanged();
183  return newSize;
184  }
185 
186  template <typename T, bool UseCompare>
187  void CListModelBase<T, UseCompare>::update(const QModelIndex &index, const ObjectType &object)
188  {
189  if (m_modelDestroyed) { return; }
190  if (index.row() >= m_container.size()) { return; }
191  m_container[index.row()] = object;
192 
193  const QModelIndex i1 = index.sibling(index.row(), 0);
194  const QModelIndex i2 = index.sibling(index.row(), this->columnCount(index) - 1);
195  emit this->dataChanged(i1, i2); // which range has been changed
196  }
197 
198  template <typename T, bool UseCompare>
199  void CListModelBase<T, UseCompare>::update(int rowIndex, const ObjectType &object)
200  {
201  this->update(this->index(rowIndex, 0), object);
202  }
203 
204  template <typename T, bool UseCompare>
206  {
207  Q_UNUSED(sort)
208  if (m_modelDestroyed) { return nullptr; }
209  const auto sortColumn = this->getSortColumn();
210  const auto sortOrder = this->getSortOrder();
211  CWorker *worker = CWorker::fromTask(this, "ModelSort", [this, container, sortColumn, sortOrder]() {
212  return this->sortContainerByColumn(container, sortColumn, sortOrder);
213  });
214  worker->thenWithResult<ContainerType>(this, [this](const ContainerType &sortedContainer) {
215  if (m_modelDestroyed) { return; }
216  this->update(sortedContainer, false);
217  });
219  return worker;
220  }
221 
222  template <typename T, bool UseCompare>
224  {
225  if (m_modelDestroyed) { return; }
226  if (container.size() > asyncThreshold && sort)
227  {
228  // larger container with sorting
229  this->updateAsync(container, sort);
230  }
231  else { this->update(container, sort); }
232  }
233 
234  template <typename T, bool UseCompare>
236  {
237  const bool f = m_filter && m_filter->isValid();
238  return f;
239  }
240 
241  template <typename T, bool UseCompare>
243  {
244  if (!this->hasFilter()) { return; }
245  m_filter.reset(nullptr);
246  this->beginResetModel();
247  this->updateFilteredContainer();
248  this->endResetModel();
249  this->emitModelDataChanged();
250  }
251 
252  template <typename T, bool UseCompare>
254  {
255  ContainerType selection;
256  if (m_selectionModel) { selection = m_selectionModel->selectedObjects(); }
257 
258  if (!filter)
259  {
260  this->removeFilter(); // clear filter
261  return;
262  }
263  if (filter->isValid())
264  {
265  m_filter = std::move(filter);
266  this->beginResetModel();
267  this->updateFilteredContainer();
268  this->endResetModel();
269  this->emitModelDataChanged();
270  }
271  else
272  {
273  // invalid filter, so clear filter
274  this->removeFilter();
275  }
276 
277  // reselect if implemented in specialized views
278  if (!selection.isEmpty()) { m_selectionModel->selectObjects(selection); }
279  }
280 
281  template <typename T, bool UseCompare>
283  CListModelBase<T, UseCompare>::at(const QModelIndex &index) const
284  {
285  if (index.row() < 0 || index.row() >= this->rowCount())
286  {
287  static const ObjectType def {}; // default object
288  return def;
289  }
290  else { return this->containerOrFilteredContainer()[index.row()]; }
291  }
292 
293  template <typename T, bool UseCompare>
295  {
296  return m_container;
297  }
298 
299  template <typename T, bool UseCompare>
302  {
303  return m_containerFiltered;
304  }
305 
306  template <typename T, bool UseCompare>
309  {
310  if (this->hasFilter())
311  {
312  if (filtered) { *filtered = true; }
313  return m_containerFiltered;
314  }
315  else
316  {
317  if (filtered) { *filtered = false; }
318  return m_container;
319  }
320  }
321 
322  template <typename T, bool UseCompare>
324  {
325  beginInsertRows(QModelIndex(), m_container.size(), m_container.size());
326  m_container.push_back(object);
327  endInsertRows();
328 
329  if (this->hasFilter())
330  {
331  // this will change the whole used model as we cannot predict the filter
332  this->beginResetModel();
333  this->updateFilteredContainer();
334  this->endResetModel();
335  }
336  this->emitModelDataChanged();
337  }
338 
339  template <typename T, bool UseCompare>
341  {
342  beginInsertRows(QModelIndex(), m_container.size(), m_container.size());
343  m_container.push_back(container);
344  endInsertRows();
345 
346  if (this->hasFilter())
347  {
348  // this will change the whole used model as we cannot predict the filter
349  this->beginResetModel();
350  this->updateFilteredContainer();
351  this->endResetModel();
352  }
353  this->emitModelDataChanged();
354  }
355 
356  template <typename T, bool UseCompare>
358  {
359  beginInsertRows(QModelIndex(), 0, 0);
360  m_container.insert(m_container.begin(), object);
361  endInsertRows();
362 
363  if (this->hasFilter())
364  {
365  // this will change the whole used model as we cannot predict the filter
366  this->beginResetModel();
367  this->updateFilteredContainer();
368  this->endResetModel();
369  }
370  this->emitModelDataChanged();
371  }
372 
373  template <typename T, bool UseCompare>
375  {
376  if (container.isEmpty()) { return; }
377  beginInsertRows(QModelIndex(), 0, 0);
378  m_container.push_back(container);
379  endInsertRows();
380 
381  if (this->hasFilter())
382  {
383  // this will change the whole used model as we cannot predict the filter
384  this->beginResetModel();
385  this->updateFilteredContainer();
386  this->endResetModel();
387  }
388  this->emitModelDataChanged();
389  }
390 
391  template <typename T, bool UseCompare>
393  {
394  const int oldSize = m_container.size();
395  beginRemoveRows(QModelIndex(), 0, 0);
396  m_container.remove(object);
397  endRemoveRows();
398  const int newSize = m_container.size();
399  if (oldSize != newSize)
400  {
401  this->emitModelDataChanged();
402  if (this->hasFilter())
403  {
404  this->beginResetModel();
405  this->updateFilteredContainer();
406  this->endResetModel();
407  }
408  }
409  }
410 
411  template <typename T, bool UseCompare>
413  {
414  beginResetModel();
415  m_container.clear();
416  m_containerFiltered.clear();
417  endResetModel();
418  this->emitModelDataChanged();
419  }
420 
421  template <typename T, bool UseCompare>
423  {
424  return m_container.isEmpty();
425  }
426 
427  template <typename T, bool UseCompare>
429  {
430  if (this->hasFilter()) { m_containerFiltered = m_filter->filter(m_container); }
431  else { m_containerFiltered.clear(); }
432  }
433 
434  template <typename T, bool UseCompare>
436  {
437  const int n = this->containerOrFilteredContainer().size();
438  emit this->modelDataChanged(n, this->hasFilter());
439  emit this->changed();
440  }
441 
442  template <typename T, bool UseCompare>
443  void CListModelBase<T, UseCompare>::onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight,
444  const QVector<int> &roles)
445  {
446  // underlying base class changed
447  Q_UNUSED(topLeft)
448  Q_UNUSED(bottomRight)
449  Q_UNUSED(roles)
450  this->emitModelDataChanged();
451  }
452 
453  template <typename T, bool UseCompare>
455  {
456  const int n = this->containerOrFilteredContainer().size();
457  emit this->changedDigest();
458  emit this->modelDataChangedDigest(n, this->hasFilter());
459  }
460 
461  template <typename T, bool UseCompare>
463  {
464  // overridden in specialized class
465  Q_UNUSED(items)
466  Q_UNUSED(position)
467  }
468 
469  template <typename T, bool UseCompare>
471  {
472  this->sort(this->getSortColumn(), this->getSortOrder());
473  }
474 
475  template <typename T, bool UseCompare>
477  {
478  // sort the values
479  this->updateContainerMaybeAsync(m_container, true);
480  }
481 
482  template <typename T, bool UseCompare>
483  void CListModelBase<T, UseCompare>::sort(int column, Qt::SortOrder order)
484  {
485  if (column == m_sortColumn && order == m_sortOrder) { return; }
486 
487  // new order
488  m_sortColumn = column;
489  m_sortOrder = order;
490  if (m_container.size() < 2)
491  {
492  return; // nothing to do
493  }
494 
495  // sort the values
496  this->updateContainerMaybeAsync(m_container, true);
497  }
498 
499  template <typename T, bool UseCompare>
500  void CListModelBase<T, UseCompare>::truncate(int maxNumber, bool forceSort)
501  {
502  if (this->rowCount() <= maxNumber) { return; }
503  if (forceSort)
504  {
505  this->sort(); // make sure container is sorted
506  }
507  ContainerType container(this->container());
508  container.truncate(maxNumber);
509  this->updateContainerMaybeAsync(container, false);
510  }
511 
512  template <typename T, bool UseCompare>
515  Qt::SortOrder order) const
516  {
517  if (m_modelDestroyed) { return container; }
518  if (container.size() < 2 || !m_columns.isSortable(column))
519  {
520  return container; // nothing to do
521  }
522 
523  // this is the only part not really thread safe, but columns do not change so far
524  const CPropertyIndex propertyIndex = m_columns.columnToSortPropertyIndex(column);
525  Q_ASSERT(!propertyIndex.isEmpty());
526  if (propertyIndex.isEmpty())
527  {
528  return container; // at release build do nothing
529  }
530 
531  // sort the values
532  const auto tieBreakersCopy =
533  m_sortTieBreakers;
534  const std::integral_constant<bool, UseCompare> marker {};
535  const auto p = [=](const ObjectType &a, const ObjectType &b) -> bool {
536  return Private::compareForModelSort<ObjectType>(a, b, order, propertyIndex, tieBreakersCopy, marker);
537  };
538 
539  return container.sorted(p);
540  }
541 
542  template <typename T, bool UseCompare>
543  QMimeData *CListModelBase<T, UseCompare>::mimeData(const QModelIndexList &indexes) const
544  {
545  QMimeData *mimeData = new QMimeData();
546  if (indexes.isEmpty()) { return mimeData; }
547 
548  ContainerType container;
549  QList<int> rows; // avoid redundant objects
550 
551  // Indexes are per row and column
552  for (const QModelIndex &index : indexes)
553  {
554  if (!index.isValid()) { continue; }
555  const int r = index.row();
556  if (rows.contains(r)) { continue; }
557  container.push_back(this->at(index));
558  rows.append(r);
559  }
560 
561  // to JSON via CVariant
562  const QJsonDocument containerJson(CVariant::fromValue(container).toJson());
563  const QString jsonString(containerJson.toJson(QJsonDocument::Compact));
564 
565  mimeData->setData(CGuiUtility::swiftJsonDragAndDropMimeType(), jsonString.toUtf8());
566  return mimeData;
567  }
568 
569  template <typename T, bool UseCompare>
570  QJsonObject CListModelBase<T, UseCompare>::toJson(bool selectedOnly) const
571  {
572  const CVariant variant =
573  CVariant::fromValue(selectedOnly && m_selectionModel ? m_selectionModel->selectedObjects() : container());
574  return variant.toJson();
575  }
576 
577  template <typename T, bool UseCompare>
578  QString CListModelBase<T, UseCompare>::toJsonString(QJsonDocument::JsonFormat format, bool selectedOnly) const
579  {
580  const CVariant variant =
581  CVariant::fromValue(selectedOnly && m_selectionModel ? m_selectionModel->selectedObjects() : container());
582  return variant.toJsonString(format);
583  }
584 
585  template <typename T, bool UseCompare>
587  {
588  return false;
589  }
590 
591 } // namespace swift::gui::models
static const QString & swiftJsonDragAndDropMimeType()
Metatype.
Definition: guiutility.cpp:404
Column formatter default implementation, also serving as interface.
virtual swift::misc::CVariant data(int role, const swift::misc::CVariant &inputData) const
Receives CVariant of column data, and returns CVariant wrapping string, pixmap, or other values depen...
bool supportsRole(int role) const
Is given role supported by formatter.
const ContainerType & container() const
Used container data.
virtual void clear()
Clear the list.
virtual const ObjectType & at(const QModelIndex &index) const
Object at row position.
QMimeData * mimeData(const QModelIndexList &indexes) const final
void sort()
Sort by given sort order.
virtual bool isEmpty() const
Empty?
QVariant data(const QModelIndex &index, int role) const
bool dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int row, int column, const QModelIndex &parent) final
virtual void push_back(const ObjectType &object)
Similar to ContainerType::push_back.
bool hasFilter() const
Filter available.
void takeFilterOwnership(std::unique_ptr< IModelFilter< ContainerType >> &filter)
Set the filter.
QString toJsonString(QJsonDocument::JsonFormat format=QJsonDocument::Indented, bool selectedOnly=false) const
Convert to JSON string.
virtual void insert(const ObjectType &object)
Similar to ContainerType::insert here inserts at first position.
void onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomLeft, const QVector< int > &roles)
Feedback when QStandardItemModel::dataChanged was called.
bool isOrderable() const
Orderable, normally use a container swift::misc::IOrderableList.
virtual swift::misc::CWorker * updateAsync(const ContainerType &container, bool sort=true)
Asynchronous update.
void truncate(int maxNumber, bool forceSort=false)
Truncate to given number.
void onChangedDigest()
Feedback when QStandardItemModel::dataChanged was called.
typename T::value_type ObjectType
Container element type.
Definition: listmodelbase.h:41
virtual void remove(const ObjectType &object)
Remove object.
virtual bool isValidIndex(const QModelIndex &index) const
Valid index (in range)
void updateFilteredContainer()
Update filtered container.
int rowCount(const QModelIndex &parentIndex=QModelIndex()) const final
QJsonObject toJson(bool selectedOnly=false) const
Convert to JSON.
ContainerType sortContainerByColumn(const ContainerType &container, int column, Qt::SortOrder order) const
Sort container by given column / order. This is used by sort() but als for asynchronous updates in th...
bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole) final
const ContainerType & containerOrFilteredContainer(bool *filtered=nullptr) const
Full container or cached filtered container as approproiate.
const ContainerType & containerFiltered() const
Used container data.
virtual void moveItems(const ContainerType &items, int position)
Move items to position, normally called from dropMimeData.
void emitModelDataChanged()
Model changed.
void resort()
Sort by given sort order.
bool canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const final
virtual int update(const ContainerType &container, bool sort=true)
Update by new container.
virtual void updateContainerMaybeAsync(const ContainerType &container, bool sort=true)
Update by new container.
bool setInContainer(const QModelIndex &index, const ObjectType &obj)
Simple set of data in container, using class is responsible for firing signals etc.
Non templated base class, allows Q_OBJECT and signals to be used.
void asyncUpdateFinished()
Asynchronous update finished.
Model filter interface.
Definition: modelfilter.h:21
bool isEmpty() const
Empty?
CastType frontCasted() const
First element casted to given type, usually the PropertIndex enum.
size_type size() const
Returns number of elements in the sequence.
Definition: sequence.h:273
bool isEmpty() const
Synonym for empty.
Definition: sequence.h:285
Wrapper around QVariant which provides transparent access to CValueObject methods of the contained ob...
Definition: variant.h:66
const QVariant & getQVariant() const
Return the internal QVariant.
Definition: variant.h:191
QJsonObject toJson() const
Cast to JSON object.
QString toJsonString(QJsonDocument::JsonFormat format=QJsonDocument::Indented) const
Convenience function JSON as string.
void then(T *context, F functor)
Connects to a functor or method which will be called when the task is finished.
Definition: worker.h:70
Class for doing some arbitrary parcel of work in its own thread.
Definition: worker.h:188
void thenWithResult(F functor)
Connects to a functor to which will be passed the result when the task is finished.
Definition: worker.h:218
Value object encapsulating a list of voice rooms.
Definition: clientlist.h:27
Models to be used with views, mainly QTableView.
Free functions in swift::misc.