Isis 3 Programmer Reference
AbstractTableModel.cpp
1 
7 /* SPDX-License-Identifier: CC0-1.0 */
8 
9 #include "IsisDebug.h"
10 
11 #include <cmath>
12 #include <iostream>
13 
14 #include <QCoreApplication>
15 #include <QDateTime>
16 #include <QDebug>
17 #include <QFutureWatcher>
18 #include <QSettings>
19 #include <QtConcurrentRun>
20 #include <QTimer>
21 
22 #include "IException.h"
23 #include "IString.h"
24 
25 #include "AbstractTableDelegate.h"
26 #include "AbstractTableModel.h"
27 #include "BusyLeafItem.h"
28 #include "TableColumn.h"
29 #include "TableColumnList.h"
30 #include "TableView.h"
31 #include "AbstractTreeModel.h"
32 
33 
34 namespace Isis {
35  AbstractTableModel::AbstractTableModel(AbstractTreeModel *model,
36  AbstractTableDelegate *someDelegate) {
37  nullify();
38 
39  m_dataModel = model;
40  connect(model, SIGNAL(cancelSort()), this, SLOT(cancelSort()));
41 
42  m_delegate = someDelegate;
43 
44  m_sortingEnabled = false;
45  m_sortLimit = 10000;
46  m_sorting = false;
47 
48  m_sortedItems = new QList<AbstractTreeItem *>;
49  m_busyItem = new BusyLeafItem;
50  m_sortStatusPoller = new QTimer;
51 
52  m_sortingWatcher = new QFutureWatcher< QList< AbstractTreeItem * > >;
53  connect(m_sortingWatcher, SIGNAL(finished()), this, SLOT(sortFinished()));
54 
55  connect(m_sortStatusPoller, SIGNAL(timeout()),
56  this, SLOT(sortStatusUpdated()));
57 
58  // Signal forwarding
59  connect(model, SIGNAL(modelModified()), SLOT(rebuildSort()));
60 
61  connect(model, SIGNAL(filterProgressChanged(int)),
62  this, SIGNAL(filterProgressChanged(int)));
63 
64  connect(model, SIGNAL(rebuildProgressChanged(int)),
65  this, SIGNAL(rebuildProgressChanged(int)));
66 
67  connect(model, SIGNAL(filterProgressRangeChanged(int, int)),
68  this, SIGNAL(filterProgressRangeChanged(int, int)));
69 
70  connect(model, SIGNAL(rebuildProgressRangeChanged(int, int)),
71  this, SIGNAL(rebuildProgressRangeChanged(int, int)));
72 
73  connect(this, SIGNAL(tableSelectionChanged(QList<AbstractTreeItem *>)),
74  model, SIGNAL(tableSelectionChanged(QList<AbstractTreeItem *>)));
75  connect(model, SIGNAL(filterCountsChanged(int, int)),
76  this, SIGNAL(filterCountsChanged(int, int)));
77  }
78 
79 
80  AbstractTableModel::~AbstractTableModel() {
81  cancelSort();
82 
83  m_dataModel = NULL;
84 
85  delete m_delegate;
86  m_delegate = NULL;
87 
88  delete m_sortedItems;
89  m_sortedItems = NULL;
90 
91  delete m_busyItem;
92  m_busyItem = NULL;
93 
94  delete m_sortStatusPoller;
95  m_sortStatusPoller = NULL;
96 
97  delete m_lessThanFunctor;
98  m_lessThanFunctor = NULL;
99 
100  if (m_columns) {
101  for (int i = 0; i < m_columns->size(); i++)
102  delete(*m_columns)[i];
103 
104  delete m_columns;
105  m_columns = NULL;
106  }
107 
108  delete m_sortingWatcher;
109  m_sortingWatcher = NULL;
110  }
111 
112 
113  bool AbstractTableModel::isSorting() const {
114  return m_sorting;
115  }
116 
117 
118  bool AbstractTableModel::isFiltering() const {
119  return m_dataModel && m_dataModel->isFiltering();
120  }
121 
122 
123  bool AbstractTableModel::sortingIsEnabled() const {
124  return m_sortingEnabled;
125  }
126 
127 
128  void AbstractTableModel::setSortingEnabled(bool enabled) {
129  if (m_sortingEnabled != enabled) {
130  m_sortingEnabled = enabled;
131  rebuildSort();
132  }
133  }
134 
135 
136  int AbstractTableModel::sortLimit() const {
137  return m_sortLimit;
138  }
139 
140 
141  void AbstractTableModel::setSortLimit(int limit) {
142  if (m_sortLimit != limit) {
143  m_sortLimit = limit;
144  rebuildSort();
145  }
146  }
147 
148 
149  bool AbstractTableModel::sortingOn() const {
150  return (sortingIsEnabled() && (getVisibleRowCount() <= sortLimit()));
151  }
152 
153 
154  TableColumnList *AbstractTableModel::getColumns() {
155  if (!m_columns) {
156  m_columns = createColumns();
157  connect(m_columns, SIGNAL(sortOutDated()), this, SLOT(sort()));
158  }
159 
160  return m_columns;
161  }
162 
163 
164  const AbstractTableDelegate *AbstractTableModel::getDelegate() const {
165  return m_delegate;
166  }
167 
168 
169  void AbstractTableModel::applyFilter() {
170  getDataModel()->applyFilter();
171  }
172 
173 
174  void AbstractTableModel::sort() {
175  if (sortingOn() && m_sortedItems->size() && !m_dataModel->isFiltering() &&
176  !m_dataModel->isRebuilding()) {
177  if (isSorting()) {
178  cancelSort();
179  }
180  else if (!m_lessThanFunctor) {
181  // Create a new comparison functor to be used in the m_sort. It will
182  // keep track of the number of comparisons made so that we can make a
183  // guess at the progress of the m_sort.
184  m_lessThanFunctor = new LessThanFunctor(
185  m_columns->getSortingOrder().first());
186 
187  // Sorting is always done on a COPY of the items list.
188  QFuture< QList< AbstractTreeItem * > > future =
189  QtConcurrent::run(this, &AbstractTableModel::doSort,
190  *m_sortedItems);
191  m_sortingWatcher->setFuture(future);
192 
193  emit modelModified();
194  }
195  }
196  }
197 
198 
199  void AbstractTableModel::reverseOrder(TableColumn *column) {
200  }
201 
202 
203  void AbstractTableModel::updateSort() {
204  }
205 
206 
207  AbstractTreeModel *AbstractTableModel::getDataModel() {
208  ASSERT(m_dataModel);
209  return m_dataModel;
210  }
211 
212 
213  const AbstractTreeModel *AbstractTableModel::getDataModel() const {
214  ASSERT(m_dataModel);
215  return m_dataModel;
216  }
217 
218 
219  QList< AbstractTreeItem * > AbstractTableModel::getSortedItems(
220  int start, int end, AbstractTreeModel::InterestingItems flags) {
221  QList< AbstractTreeItem * > sortedSubsetOfItems;
222 
223  if (sortingOn()) {
224  while (start <= end) {
225  if (start < m_sortedItems->size())
226  sortedSubsetOfItems.append(m_sortedItems->at(start));
227  else if (isFiltering())
228  sortedSubsetOfItems.append(m_busyItem);
229 
230  start++;
231  }
232  }
233  else {
234  sortedSubsetOfItems = getDataModel()->getItems(start, end, flags, true);
235  }
236 
237  return sortedSubsetOfItems;
238  }
239 
240 
241  QList< AbstractTreeItem * > AbstractTableModel::getSortedItems(
242  AbstractTreeItem *item1, AbstractTreeItem *item2,
243  AbstractTreeModel::InterestingItems flags) {
244  QList< AbstractTreeItem * > sortedSubsetOfItems;
245 
246  if (!sortingOn()) {
247  sortedSubsetOfItems = getDataModel()->getItems(item1, item2, flags, true);
248  }
249  else {
250  AbstractTreeItem *start = NULL;
251 
252  int currentIndex = 0;
253 
254  while (!start && currentIndex < m_sortedItems->size()) {
255  AbstractTreeItem *current = m_sortedItems->at(currentIndex);
256  if (current == item1)
257  start = item1;
258  else if (current == item2)
259  start = item2;
260 
261  if (!start)
262  currentIndex++;
263  }
264 
265  if (!start) {
266  IString msg = "Could not find the first item";
267  throw IException(IException::Programmer, msg, _FILEINFO_);
268  }
269 
270  AbstractTreeItem *end = item2;
271 
272  // Sometimes we need to build the list forwards and sometimes backwards.
273  // This is accomplished by using either append or prepend. We abstract
274  // away which of these we should use (why should we care) by using the
275  // variable "someKindaPend" to store the appropriate method.
276  void (QList< AbstractTreeItem * >::*someKindaPend)(
277  AbstractTreeItem * const &);
278  someKindaPend = &QList< AbstractTreeItem * >::append;
279 
280  if (start == item2) {
281  end = item1;
282  someKindaPend = &QList< AbstractTreeItem * >::prepend;
283  }
284 
285  while (currentIndex < m_sortedItems->size() &&
286  m_sortedItems->at(currentIndex) != end) {
287  (sortedSubsetOfItems.*someKindaPend)(m_sortedItems->at(currentIndex));
288  currentIndex++;
289  }
290 
291  if (currentIndex >= m_sortedItems->size()) {
292  IString msg = "Could not find the second item";
293  throw IException(IException::Programmer, msg, _FILEINFO_);
294  }
295 
296  (sortedSubsetOfItems.*someKindaPend)(end);
297  }
298 
299  return sortedSubsetOfItems;
300  }
301 
302 
303  void AbstractTableModel::handleTreeSelectionChanged(
304  QList< AbstractTreeItem * > newlySelectedItems,
305  AbstractTreeItem::InternalPointerType pointerType) {
306  QList< AbstractTreeItem * > interestingSelectedItems;
307  foreach (AbstractTreeItem * item, newlySelectedItems) {
308  if (item->getPointerType() == pointerType)
309  interestingSelectedItems.append(item);
310  }
311 
312  if (interestingSelectedItems.size()) {
313  emit treeSelectionChanged(interestingSelectedItems);
314  }
315  }
316 
317 
318  void AbstractTableModel::sortStatusUpdated() {
319  if (m_lessThanFunctor)
320  emit sortProgressChanged(m_lessThanFunctor->getCompareCount());
321  }
322 
323 
324  void AbstractTableModel::sortFinished() {
325  bool interrupted = m_lessThanFunctor->interrupted();
326  delete m_lessThanFunctor;
327  m_lessThanFunctor = NULL;
328 
329  if (!interrupted) {
330  QList< AbstractTreeItem * > newSortedItems = m_sortingWatcher->result();
331 
332  if (!m_dataModel->isFiltering() && !m_dataModel->isRebuilding()) {
333  *m_sortedItems = newSortedItems;
334  emit modelModified();
335  }
336  }
337  else {
338  sort();
339  }
340  }
341 
342 
343  void AbstractTableModel::cancelSort() {
344  if (m_lessThanFunctor) {
345  m_lessThanFunctor->interrupt();
346  m_sortingWatcher->waitForFinished();
347  }
348  }
349 
350 
351  void AbstractTableModel::itemsLost() {
352  cancelSort();
353  m_sortedItems->clear();
354  }
355 
356 
357  QList< AbstractTreeItem * > AbstractTableModel::doSort(
358  QList< AbstractTreeItem * > itemsToSort) {
359  ASSERT(!isSorting());
360  if (!isSorting()) {
361  setSorting(true);
362 
363  QList< TableColumn * > columnsToSortOn = m_columns->getSortingOrder();
364  if (sortingOn()) {
365  // Reset the timer so that it will begin polling the status of the
366  // m_sort.
367  m_sortStatusPoller->start(SORT_UPDATE_FREQUENCY);
368 
369  // Use n*log2(n) as our estimate of the number of comparisons that it
370  // should take to m_sort the list.
371  int numItems = itemsToSort.size();
372  double a = 1.0;
373  double b = 1.0;
374  emit sortProgressRangeChanged(0,
375  (int)((a * numItems) * (log2(b * numItems))));
376 
377  try {
378  qStableSort(itemsToSort.begin(), itemsToSort.end(),
379  *m_lessThanFunctor);
380  }
381  catch (SortingCanceledException &e) {
382  m_sortStatusPoller->stop();
383  emit sortProgressRangeChanged(0, 0);
384  emit sortProgressChanged(0);
385  emit modelModified();
386 
387  setSorting(false);
389  }
390 
391  // The m_sort is done, so stop emiting status updates and make sure we
392  // let the listeners know that the m_sort is done (since the status
393  // will not always reach 100% as we are estimating the progress).
394  m_sortStatusPoller->stop();
395  emit sortProgressRangeChanged(0, 0);
396  emit sortProgressChanged(0);
397  emit modelModified();
398  }
399 
400  setSorting(false);
401  }
402 
403  return itemsToSort;
404  }
405 
406 
407  void AbstractTableModel::nullify() {
408  m_dataModel = NULL;
409  m_delegate = NULL;
410  m_sortedItems = NULL;
411  m_busyItem = NULL;
412  m_sortStatusPoller = NULL;
413  m_lessThanFunctor = NULL;
414  m_columns = NULL;
415  m_sortingWatcher = NULL;
416  }
417 
418 
419  void AbstractTableModel::setSorting(bool isSorting) {
420  m_sorting = isSorting;
421  }
422 
423 
424  void AbstractTableModel::rebuildSort() {
425  ASSERT(m_dataModel);
426  ASSERT(m_sortedItems);
427  m_sortedItems->clear();
428  cancelSort();
429 
430  if (sortingOn()) {
431  m_sortingEnabled = false;
432  *m_sortedItems = getItems(0, -1);
433 
434  foreach (AbstractTreeItem * item, *m_sortedItems) {
435  connect(item, SIGNAL(destroyed(QObject *)), this, SLOT(itemsLost()));
436  }
437 
438  m_sortingEnabled = true;
439  sort();
440 
441  emit userWarning(None);
442  }
443  else {
444  cancelSort();
445  emit modelModified();
446 
447  if (!m_sortingEnabled)
448  emit userWarning(SortingDisabled);
449  else
450  emit userWarning(SortingTableSizeLimitReached);
451  }
452  }
453 
454 
455  // *********** LessThanFunctor implementation *************
456 
457 
458  AbstractTableModel::LessThanFunctor::LessThanFunctor(
459  TableColumn const *someColumn) : m_column(someColumn) {
460  m_sharedData = new LessThanFunctorData;
461  }
462 
463 
464  AbstractTableModel::LessThanFunctor::LessThanFunctor(
465  LessThanFunctor const &other) : m_sharedData(other.m_sharedData) {
466  m_column = other.m_column;
467  }
468 
469 
470  AbstractTableModel::LessThanFunctor::~LessThanFunctor() {
471  m_column = NULL;
472  }
473 
474 
475  int AbstractTableModel::LessThanFunctor::getCompareCount() const {
476  return m_sharedData->getCompareCount();
477  }
478 
479 
480  void AbstractTableModel::LessThanFunctor::interrupt() {
481  m_sharedData->setInterrupted(true);
482  }
483 
484 
485  bool AbstractTableModel::LessThanFunctor::interrupted() {
486  return m_sharedData->interrupted();
487  }
488 
489 
490  void AbstractTableModel::LessThanFunctor::reset() {
491  m_sharedData->setInterrupted(false);
492  }
493 
494 
495  bool AbstractTableModel::LessThanFunctor::operator()(
496  AbstractTreeItem *const &left, AbstractTreeItem *const &right) {
497  if (left->getPointerType() != right->getPointerType()) {
498  IString msg = "Tried to compare apples to oranges";
499  throw IException(IException::Programmer, msg, _FILEINFO_);
500  }
501 
502  if (m_sharedData->interrupted()) {
503  throw SortingCanceledException();
504  }
505 
506  m_sharedData->incrementCompareCount();
507 
508  QVariant leftData = left->getData(m_column->getTitle());
509  QVariant rightData = right->getData(m_column->getTitle());
510  QString busy = BusyLeafItem().getData().toString();
511 
512  bool lessThan;
513  if (leftData.type() == QVariant::String &&
514  rightData.type() == QVariant::String) {
515  lessThan = leftData.toString() < rightData.toString();
516  }
517  else if (leftData.type() == QVariant::Double &&
518  rightData.type() == QVariant::Double) {
519  lessThan = (leftData.toDouble() < rightData.toDouble());
520  }
521  else if (leftData.type() == QVariant::Double ||
522  rightData.type() == QVariant::Double) {
523  // We are comparing a BusyLeafItem to a double. BusyLeafItem's should
524  // always be less than the double.
525  lessThan = (leftData.toString() == busy);
526  }
527  else {
528  lessThan = leftData.toString() < rightData.toString();
529  }
530 
531  return lessThan ^ m_column->sortAscending();
532  }
533 
534 
535  AbstractTableModel::LessThanFunctor &
536  AbstractTableModel::LessThanFunctor::operator=(
537  LessThanFunctor const &other) {
538  if (this != &other) {
539  m_column = other.m_column;
540  m_sharedData = other.m_sharedData;
541  }
542 
543  return *this;
544  }
545 
546 
547  // *********** LessThanFunctorData implementation *************
548 
549 
550  AbstractTableModel::LessThanFunctorData::LessThanFunctorData() {
551  m_compareCount.fetchAndStoreRelaxed(0);
552  m_interruptFlag.fetchAndStoreRelaxed(0);
553  }
554 
555 
556  AbstractTableModel::LessThanFunctorData::LessThanFunctorData(
557  LessThanFunctorData const &other) : QSharedData(other),
558  m_compareCount(other.m_compareCount), m_interruptFlag(other.m_interruptFlag) {
559  }
560 
561 
562  AbstractTableModel::LessThanFunctorData::~LessThanFunctorData() {
563  }
564 
565 
566  int AbstractTableModel::LessThanFunctorData::getCompareCount() const {
567  return m_compareCount;
568  }
569 
570 
571  void AbstractTableModel::LessThanFunctorData::incrementCompareCount() {
572  m_compareCount.fetchAndAddRelaxed(1);
573  }
574 
575 
576  void AbstractTableModel::LessThanFunctorData::setInterrupted(bool newStatus) {
577  newStatus ? m_interruptFlag.fetchAndStoreRelaxed(1) :
578  m_interruptFlag.fetchAndStoreRelaxed(0);
579  }
580 
581 
582  bool AbstractTableModel::LessThanFunctorData::interrupted() {
583  return m_interruptFlag != 0;
584  }
585 }
QSharedData
QList
This is free and unencumbered software released into the public domain.
Definition: BoxcarCachingAlgorithm.h:13
Isis::IException::Programmer
@ Programmer
This error is for when a programmer made an API call that was illegal.
Definition: IException.h:146
QFutureWatcher
This is free and unencumbered software released into the public domain.
Definition: AbstractTableModel.h:24
QObject
Isis
This is free and unencumbered software released into the public domain.
Definition: Apollo.h:16