swift
viewbasenontemplate.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2018 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
4 #include <algorithm>
5 
6 #include <QAction>
7 #include <QApplication>
8 #include <QMetaMethod>
9 #include <QShortcut>
10 
11 #include "config/buildconfig.h"
13 #include "gui/dockwidgetinfoarea.h"
16 #include "gui/guiapplication.h"
17 #include "gui/guiutility.h"
18 #include "gui/loadindicator.h"
19 #include "gui/menus/fontmenus.h"
20 #include "gui/menus/menudelegate.h"
21 #include "gui/shortcut.h"
22 #include "gui/views/viewbase.h"
23 #include "misc/logmessage.h"
24 
25 using namespace swift::config;
26 using namespace swift::misc;
27 using namespace swift::gui;
28 using namespace swift::gui::menus;
29 using namespace swift::gui::models;
30 using namespace swift::gui::filters;
31 using namespace swift::gui::settings;
32 using namespace swift::gui::components;
33 
34 namespace swift::gui::views
35 {
36  CViewBaseNonTemplate::CViewBaseNonTemplate(QWidget *parent) : COverlayMessagesTableView(parent)
37  {
38  this->setContextMenuPolicy(Qt::CustomContextMenu);
39  connect(this, &QWidget::customContextMenuRequested, this, &CViewBaseNonTemplate::customMenuRequested);
40  connect(this, &QTableView::clicked, this, &CViewBaseNonTemplate::onClicked);
41  connect(this, &QTableView::doubleClicked, this, &CViewBaseNonTemplate::onDoubleClicked);
42  this->horizontalHeader()->setSortIndicatorShown(true);
43 
44  // setting resize mode rowsResizeModeToContent() causes extremly slow views
45  // default, see: m_rowResizeMode
46 
47  // scroll modes
48  this->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
49  this->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
50  this->setWordWrap(false);
51  this->setTextElideMode(Qt::ElideNone);
52 
53  // shortcuts
54  QShortcut *filter = new QShortcut(CShortcut::keyDisplayFilter(), this);
55  bool s = connect(filter, &QShortcut::activated, this, &CViewBaseNonTemplate::displayFilterDialog);
56  Q_ASSERT_X(s, Q_FUNC_INFO, "Shortcut");
57  filter->setObjectName("Filter shortcut for " + this->objectName());
58  filter->setContext(Qt::WidgetShortcut);
59 
60  QShortcut *clearSelection = new QShortcut(CShortcut::keyClearSelection(), this);
61  s = connect(clearSelection, &QShortcut::activated, this, &CViewBaseNonTemplate::clearSelection);
62  Q_ASSERT_X(s, Q_FUNC_INFO, "Shortcut");
63  clearSelection->setObjectName("Clear selection shortcut for " + this->objectName());
64  clearSelection->setContext(Qt::WidgetShortcut);
65 
66  QShortcut *saveJson = new QShortcut(CShortcut::keySaveViews(), this);
67  s = connect(saveJson, &QShortcut::activated, this, &CViewBaseNonTemplate::saveJsonAction);
68  Q_ASSERT_X(s, Q_FUNC_INFO, "Shortcut");
69  saveJson->setObjectName("Save JSON for " + this->objectName());
70  saveJson->setContext(Qt::WidgetShortcut);
71 
72  QShortcut *deleteRow = new QShortcut(CShortcut::keyDelete(), this);
73  s = connect(deleteRow, &QShortcut::activated, this, &CViewBaseNonTemplate::removeSelectedRowsChecked);
74  Q_ASSERT_X(s, Q_FUNC_INFO, "Shortcut");
75  deleteRow->setObjectName("Remove selected rows for " + this->objectName());
76  deleteRow->setContext(Qt::WidgetShortcut);
77 
78  QShortcut *copy = new QShortcut(CShortcut::keyCopy(), this);
79  s = connect(copy, &QShortcut::activated, this, &CViewBaseNonTemplate::copy);
80  Q_ASSERT_X(s, Q_FUNC_INFO, "Shortcut");
81  copy->setObjectName("Copy selection shortcut for " + this->objectName());
82  copy->setContext(Qt::WidgetShortcut);
83 
84  QShortcut *resize = new QShortcut(CShortcut::keyResizeView(), this);
85  s = connect(resize, &QShortcut::activated, this, &CViewBaseNonTemplate::fullResizeToContents);
86  Q_ASSERT_X(s, Q_FUNC_INFO, "Shortcut");
87  resize->setObjectName("Resize view shortcut for " + this->objectName());
88  resize->setContext(Qt::WidgetShortcut);
89  }
90 
92  {
93  // dtor
94  }
95 
97  {
98  // further init could go here
99  const bool c = CEnableForDockWidgetInfoArea::setParentDockWidgetInfoArea(parentDockableWidget);
100  return c;
101  }
102 
104 
105  void CViewBaseNonTemplate::setFilterWidgetImpl(QWidget *filterWidget)
106  {
107  if (filterWidget == m_filterWidget) { return; }
108 
109  // dialog or filter widget
110  if (m_filterWidget)
111  {
112  disconnect(m_filterWidget);
114  if (m_filterWidget->parent() == this) { m_filterWidget->deleteLater(); }
115  m_filterWidget = nullptr;
116  }
117 
118  if (filterWidget)
119  {
120  this->menuAddItems(MenuFilter);
121  m_filterWidget = filterWidget;
122  }
123  }
124 
126  {
127  if (filterDialog == m_filterWidget) { return; }
128  this->setFilterWidgetImpl(filterDialog);
129  if (filterDialog)
130  {
131  const bool s =
132  connect(filterDialog, &CFilterDialog::finished, this, &CViewBaseNonTemplate::filterDialogFinished);
133  Q_ASSERT_X(s, Q_FUNC_INFO, "filter dialog connect");
134  Q_UNUSED(s);
135  }
136  }
137 
139  {
140  if (filterWidget == m_filterWidget) { return; }
141  this->setFilterWidgetImpl(filterWidget);
142  if (filterWidget)
143  {
144  bool s = connect(filterWidget, &CFilterWidget::changeFilter, this,
145  &CViewBaseNonTemplate::filterWidgetChangedFilter, Qt::QueuedConnection);
146  Q_ASSERT_X(s, Q_FUNC_INFO, "filter connect changeFilter");
147  s = connect(this, &CViewBaseNonTemplate::modelDataChanged, filterWidget, &CFilterWidget::onRowCountChanged,
148  Qt::QueuedConnection);
149  Q_ASSERT_X(s, Q_FUNC_INFO, "filter connect modelDataChanged");
150  Q_UNUSED(s);
151  }
152  }
153 
155 
157  {
159  }
160 
161  void CViewBaseNonTemplate::setSelectionModel(QItemSelectionModel *model)
162  {
163  if (this->selectionModel()) { disconnect(this->selectionModel()); }
164  QTableView::setSelectionModel(model);
165  if (this->selectionModel())
166  {
167  connect(this->selectionModel(), &QItemSelectionModel::currentRowChanged, this,
169  }
170  }
171 
173 
175  {
176  return this->loadJson(directory);
177  }
178 
179  CStatusMessage CViewBaseNonTemplate::showFileSaveDialog(bool selectedOnly, const QString &directory)
180  {
181  return this->saveJson(selectedOnly, directory);
182  }
183 
185  {
186  this->horizontalHeader()->setSectionResizeMode(mode);
187  }
188 
190  {
191  if (menu && nestPreviousMenu)
192  {
193  // new menu with nesting
194  menu->setNestedDelegate(m_menu);
195  m_menu = menu;
196  }
197  else if (!menu && nestPreviousMenu)
198  {
199  // nested new menu
201  }
202  else
203  {
204  // no nesting
205  m_menu = menu;
206  }
207  return menu;
208  }
209 
211  {
212  if (m_menuFlagActions.contains(menu)) { return m_menuFlagActions.value(menu); }
213 
214  CMenuActions ma;
215  switch (menu)
216  {
217  case MenuRefresh:
218  {
219  static const QMetaMethod requestSignal = QMetaMethod::fromSignal(&CViewBaseNonTemplate::requestUpdate);
220  if (!this->isSignalConnected(requestSignal)) break;
221  ma.addAction(CIcons::refresh16(), "Update", CMenuAction::pathViewUpdates(),
223  break;
224  }
225  case MenuBackend:
226  {
227  static const QMetaMethod requestSignal =
228  QMetaMethod::fromSignal(&CViewBaseNonTemplate::requestNewBackendData);
229  if (!this->isSignalConnected(requestSignal)) break;
230  ma.addAction(CIcons::refresh16(), "Reload from backend", CMenuAction::pathViewUpdates(),
232  break;
233  }
235  {
236  QAction *a =
237  ma.addAction(CIcons::appMappings16(), "Automatically display (when loaded)",
238  CMenuAction::pathViewUpdates(), { this, &CViewBaseNonTemplate::toggleAutoDisplay });
239  a->setCheckable(true);
240  a->setChecked(this->displayAutomatically());
241  break;
242  }
244  {
245  ma.addAction(CIcons::delete16(), "Remove selected rows", CMenuAction::pathViewAddRemove(),
246  { this, &CViewBaseNonTemplate::removeSelectedRowsChecked }, CShortcut::keyDelete());
247  break;
248  }
249  case MenuClear:
250  {
251  ma.addAction(CIcons::delete16(), "Clear", CMenuAction::pathViewAddRemove(),
252  { this, &CViewBaseNonTemplate::clear });
253  break;
254  }
255  case MenuFilter:
256  {
257  if (m_filterWidget)
258  {
259  const bool dialog = qobject_cast<QDialog *>(m_filterWidget);
260  if (dialog)
261  ma.addAction(CIcons::filter16(),
263  CMenuAction::pathViewFilter(), { this, &CViewBaseNonTemplate::displayFilterDialog },
265  ma.addAction(CIcons::filter16(), "Remove Filter", CMenuAction::pathViewFilter(),
267  }
268  break;
269  }
271  {
272  ma.addAction(CIcons::tableRelationship16(), "Materialize filtered data", CMenuAction::pathViewFilter(),
274  break;
275  }
276  case MenuLoad:
277  {
278  ma.addAction(CIcons::disk16(), "Load from file ", CMenuAction::pathViewLoadSave(),
280  break;
281  }
282  case MenuSave:
283  {
284  ma.addAction(CIcons::disk16(),
286  CMenuAction::pathViewLoadSave(), { this, &CViewBaseNonTemplate::saveJsonAction },
288  if (this->hasSelection())
289  {
290  ma.addAction(CIcons::disk16(), "Save selected data in file", CMenuAction::pathViewLoadSave(),
292  break;
293  }
294  break;
295  }
296  case MenuCut:
297  {
298  if (!QApplication::clipboard()) break;
299  ma.addAction(CIcons::cut16(), "Cut", CMenuAction::pathViewCutPaste(), { this, &CViewBaseNonTemplate::cut },
300  QKeySequence(QKeySequence::Paste));
301  break;
302  }
303  case MenuPaste:
304  {
305  if (!QApplication::clipboard()) break;
306  ma.addAction(CIcons::paste16(), "Paste", CMenuAction::pathViewCutPaste(),
307  { this, &CViewBaseNonTemplate::paste }, QKeySequence(QKeySequence::Paste));
308  break;
309  }
310  case MenuCopy:
311  {
312  if (!QApplication::clipboard()) break;
313  ma.addAction(CIcons::copy16(), "Copy", CMenuAction::pathViewCutPaste(),
314  { this, &CViewBaseNonTemplate::copy }, QKeySequence(QKeySequence::Copy));
315  break;
316  }
317  default: break;
318  }
319  m_menuFlagActions.insert(menu, ma);
320  return ma;
321  }
322 
324  {
325  if (!this->allowsMultipleSelectedRows()) { return; }
326  const CGeneralGuiSettings settings = m_guiSettings.getThreadLocal();
329  {
330  this->setSelectionMode(settings.getPreferredSelection());
331  }
332  }
333 
334  void CViewBaseNonTemplate::rememberLastJsonDirectory(const QString &selectedFileOrDir)
335  {
336  if (selectedFileOrDir.isEmpty()) { return; }
337  const QString dir = CDirectories::fileNameToDirectory(selectedFileOrDir);
338  QDir d(dir);
339  if (!d.exists()) { return; }
340 
341  // existing dir
342  CDirectories directories = m_dirSettings.get();
343  directories.setPropertyByIndex(m_dirSettingsIndex, CVariant::fromValue(dir));
344  const CStatusMessage msg = m_dirSettings.setAndSave(directories);
345  CLogMessage::preformatted(msg);
346  }
347 
349  {
350  const CDirectories directories = m_dirSettings.get();
351  return directories.propertyByIndex(m_dirSettingsIndex).toString();
352  }
353 
355  {
356  if (!m_textEditDialog) { m_textEditDialog = new CTextEditDialog(this); }
357  return m_textEditDialog;
358  }
359 
361  {
362  // delegate?
363  if (m_menu) { m_menu->customMenu(menuActions); }
364 
365  // standard view menus
366  if (m_menus.testFlag(MenuRefresh)) { menuActions.addActions(this->initMenuActions(MenuRefresh)); }
367  if (m_menus.testFlag(MenuBackend)) { menuActions.addActions(this->initMenuActions(MenuBackend)); }
368 
369  if (m_menus.testFlag(MenuClear)) { menuActions.addActions(this->initMenuActions(MenuClear)); }
370  if (m_menus.testFlag(MenuDisplayAutomatically))
371  {
372  // here I expect only one action
373  QAction *a = menuActions.addActions(this->initMenuActions(MenuDisplayAutomatically)).first();
374  a->setChecked(this->displayAutomatically());
375  }
376  if (m_menus.testFlag(MenuRemoveSelectedRows))
377  {
378  if (this->hasSelection()) { menuActions.addActions(this->initMenuActions(MenuRemoveSelectedRows)); }
379  }
380 
381  if (m_menus.testFlag(MenuCopy)) { menuActions.addActions(this->initMenuActions(MenuCopy)); }
382  if (m_menus.testFlag(MenuCut)) { menuActions.addActions(this->initMenuActions(MenuCut)); }
383  if (m_menus.testFlag(MenuPaste)) { menuActions.addActions(this->initMenuActions(MenuPaste)); }
384  if (m_menus.testFlag(MenuFont) && m_fontMenu)
385  {
386  menuActions.addActions(m_fontMenu->getActions(), CMenuAction::pathFont());
387  }
388 
389  if (m_menus.testFlag(MenuFilter) && m_filterWidget)
390  {
391  menuActions.addActions(this->initMenuActions(MenuFilter));
392  if (m_menus.testFlag(MenuMaterializeFilter))
393  {
395  }
396  }
397 
398  // selection menus, not in menu action list because it depends on current selection
399  const SelectionMode sm = this->selectionMode();
400  if (sm == MultiSelection || sm == ExtendedSelection)
401  {
402  menuActions.addAction("Select all", CMenuAction::pathViewSelection(), nullptr,
404  }
405  if (sm != NoSelection)
406  {
407  menuActions.addAction("Clear selection " + CShortcut::toParenthesisString(CShortcut::keyClearSelection()),
408  CMenuAction::pathViewSelection(), nullptr,
409  { this, &CViewBaseNonTemplate::clearSelection }, CShortcut::keyClearSelection());
410  }
411  if ((m_originalSelectionMode == MultiSelection || m_originalSelectionMode == ExtendedSelection) &&
413  {
414  if (sm != MultiSelection)
415  {
416  menuActions.addAction("Switch to multi selection", CMenuAction::pathViewSelection(), nullptr,
418  }
419 
420  if (sm != ExtendedSelection)
421  {
422  menuActions.addAction("Switch to extended selection", CMenuAction::pathViewSelection(), nullptr,
424  }
425 
426  if (sm != SingleSelection)
427  {
428  menuActions.addAction("Switch to single selection", CMenuAction::pathViewSelection(), nullptr,
430  }
431  }
432 
433  // load/save
434  if (m_menus.testFlag(MenuLoad)) { menuActions.addActions(this->initMenuActions(MenuLoad)); }
435  if (m_menus.testFlag(MenuSave) && !isEmpty()) { menuActions.addActions(this->initMenuActions(MenuSave)); }
436 
437  // resizing
438  menuActions.addAction(
439  CIcons::resize16(), "&Resize " + CShortcut::toParenthesisString(CShortcut::keyResizeView()),
440  CMenuAction::pathViewResize(), nullptr, { this, &CViewBaseNonTemplate::presizeOrFullResizeToContents });
441 
442  // resize to content might decrease performance,
443  // so I only allow changing to "content resizing" if size matches
444  // const bool enabled = !this->reachedResizeThreshold();
445  const bool enabled = true;
446  const bool autoResize = (m_resizeMode == ResizingAuto);
447 
448  // when not set to auto, then lets set how we want to resize rows
449  // for auto this is too slow
450  // const bool ww = this->wordWrap();
451  QAction *resizeRowsAction =
452  menuActions.addAction(CIcons::resizeVertical16(), "Resize rows to content", CMenuAction::pathViewResize(),
453  nullptr, { this, &CViewBaseNonTemplate::resizeRowsToContents });
454  resizeRowsAction->setEnabled(
455  true); // as changing from word wraap to none word wrap can leave to high columns, we always enable this
456 
466  // export actions, display in text edit
467  if (CBuildConfig::isLocalDeveloperDebugBuild())
468  {
469  menuActions.addAction(CIcons::tableSheet16(), "Display as JSON", CMenuAction::pathViewLoadSave(),
471  if (this->hasSelection())
472  {
473  menuActions.addAction(CIcons::tableSheet16(), "Display selected as JSON",
474  CMenuAction::pathViewLoadSave(),
476  ;
477  }
478  }
479 
480  QAction *actionInteractiveResize =
481  menuActions.addAction(CIcons::viewTile(), "Resize (auto)", CMenuAction::pathViewResize(), nullptr);
482  actionInteractiveResize->setObjectName(this->objectName().append("ActionResizing"));
483  actionInteractiveResize->setCheckable(true);
484  actionInteractiveResize->setChecked(autoResize);
485  actionInteractiveResize->setEnabled(enabled);
486  connect(actionInteractiveResize, &QAction::toggled, this, &CViewBaseNonTemplate::toggleResizeMode);
487 
488  QAction *actionWordWrap = menuActions.addAction(CIcons::viewMultiColumn(), "Word wrap (multiline)",
489  CMenuAction::pathViewResize(), nullptr);
490  actionWordWrap->setObjectName(this->objectName().append("ActionResizing"));
491  actionWordWrap->setCheckable(true);
492  actionWordWrap->setChecked(this->wordWrap());
493  actionWordWrap->setEnabled(true);
494  connect(actionWordWrap, &QAction::toggled, this, &CViewBaseNonTemplate::toggleWordWrap);
495  }
496 
497  void CViewBaseNonTemplate::resizeEvent(QResizeEvent *event)
498  {
499  if (this->isShowingLoadIndicator())
500  {
501  // re-center
502  this->centerLoadIndicator();
503  }
504  QTableView::resizeEvent(event);
505  }
506 
508  {
509  const QFontMetrics m(this->getHorizontalHeaderFont());
510  const int h = m.height();
511  return h;
512  }
513 
514  bool CViewBaseNonTemplate::hasSelection() const { return this->selectionModel()->hasSelection(); }
515 
516  QModelIndexList CViewBaseNonTemplate::selectedRows() const
517  {
518  // make sure this is ordered by row and wee keep the same order as in unselectedRows
519  // if we'd know for sure the indexes are always sorted we can remove the sorting here
520  // Qt docu selectedIndexes: Returns a list of all selected model item indexes. The list contains no duplicates,
521  // and is not sorted.
522  QModelIndexList indexes = this->selectionModel()->selectedRows();
523  std::sort(indexes.begin(), indexes.end());
524  return indexes;
525  }
526 
527  QModelIndexList CViewBaseNonTemplate::unselectedRows() const
528  {
529  const QModelIndexList selected = this->selectedRows();
530  QModelIndexList unselected;
531  const int rows = this->rowCount();
532  for (int r = 0; r < rows; r++)
533  {
534  const QModelIndex mi = this->model()->index(r, 0);
535  if (selected.contains(mi)) { continue; }
536  unselected.push_back(mi);
537  }
538  return unselected;
539  }
540 
541  int CViewBaseNonTemplate::selectRows(const QSet<int> &rows)
542  {
543  if (!this->selectionModel()) { return 0; }
544 
545  // multiple times faster than multiple this->selectRow()
546  this->clearSelection();
547  QItemSelection selectedItems;
548  const int columns = this->model()->columnCount() - 1;
549  for (int r : rows) { selectedItems.select(this->model()->index(r, 0), this->model()->index(r, columns)); }
550  this->selectionModel()->select(selectedItems, QItemSelectionModel::Select);
551  return selectedItems.size();
552  }
553 
555  {
556  if (!this->hasSelection()) { return 0; }
557  return this->selectedRows().count();
558  }
559 
560  int CViewBaseNonTemplate::unselectedRowCount() const { return this->rowCount() - this->selectedRowCount(); }
561 
562  bool CViewBaseNonTemplate::hasSingleSelectedRow() const { return this->selectedRowCount() == 1; }
563 
565 
567  {
568  return m_originalSelectionMode == ExtendedSelection || m_originalSelectionMode == MultiSelection;
569  }
570 
572  {
573  QAbstractItemView::SelectionMode m = this->selectionMode();
574  return m == QAbstractItemView::MultiSelection || m == QAbstractItemView::ExtendedSelection;
575  }
576 
578  {
579  this->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive); // faster mode
580  this->horizontalHeader()->setStretchLastSection(true);
581  const int fh = qRound(1.5 * this->getHorizontalHeaderFontHeight());
582  this->verticalHeader()->setDefaultSectionSize(fh); // for height
583  this->verticalHeader()->setMinimumSectionSize(fh); // for height
584 
585  switch (m_rowResizeMode)
586  {
587  case Interactive: this->rowsResizeModeToInteractive(); break;
588  case Content: this->rowsResizeModeToContent(); break;
589  default: Q_ASSERT_X(false, Q_FUNC_INFO, "wrong resize mode"); break;
590  }
591 
592  // call this deferred, otherwise the values are overridden with any values
593  // from the UI builder
594  const QPointer<CViewBaseNonTemplate> guard(this);
595  QTimer::singleShot(500, this, [=]() {
596  if (!guard) { return; }
598  });
599  }
600 
602  {
603  // some logic to find a useful default name
604  if (load)
605  {
606  return CFileUtils::appendFilePaths(this->getRememberedLastJsonDirectory(),
607  CFileUtils::jsonWildcardAppendix());
608  }
609 
610  // Save file path
611  const QString dir = m_dirSettings.get().propertyByIndex(m_dirSettingsIndex).toString();
612  QString name(m_saveFileName);
613  if (name.isEmpty())
614  {
615  // create a name
616  if (this->getDockWidgetInfoArea()) { name = this->getDockWidgetInfoArea()->windowTitle(); }
617  else { name = this->metaObject()->className(); }
618  }
619  if (!name.endsWith(CFileUtils::jsonAppendix(), Qt::CaseInsensitive)) { name += CFileUtils::jsonAppendix(); }
620  return CFileUtils::appendFilePaths(dir, name);
621  }
622 
623  void CViewBaseNonTemplate::menuRemoveItems(Menu menusToRemove) { m_menus &= (~menusToRemove); }
624 
626  {
627  m_menus |= menusToAdd;
628  if (menusToAdd.testFlag(MenuRemoveSelectedRows)) { m_enableDeleteSelectedRows = true; }
629  }
630 
632  {
633  if (!m_menus.testFlag(MenuFilter)) { return; }
634  if (!m_filterWidget) { return; }
635  m_filterWidget->show();
636  }
637 
639  {
640  if (!m_menus.testFlag(MenuLoad)) { return; }
641  const CStatusMessage m = this->loadJson();
642  if (!m.isEmpty()) { CLogMessage::preformatted(m); }
643  }
644 
646  {
647  if (this->isEmpty()) { return; }
648  if (!m_menus.testFlag(MenuSave)) { return; }
649  const CStatusMessage m = this->saveJson(false);
650  if (!m.isEmpty()) { CLogMessage::preformatted(m); }
651  }
652 
654  {
655  if (this->isEmpty()) { return; }
656  if (!m_menus.testFlag(MenuSave)) { return; }
657  const CStatusMessage m = this->saveJson(true);
658  if (!m.isEmpty()) { CLogMessage::preformatted(m); }
659  }
660 
662  {
664  emit this->requestUpdate();
665  }
666 
668  {
670  emit this->requestNewBackendData();
671  }
672 
674 
676  {
677  const int height = this->verticalHeader()->minimumSectionSize();
678  QHeaderView *verticalHeader = this->verticalHeader();
679  Q_ASSERT_X(verticalHeader, Q_FUNC_INFO, "Missing vertical header");
680  verticalHeader->setSectionResizeMode(QHeaderView::Interactive);
681  verticalHeader->setDefaultSectionSize(height);
682  m_rowResizeMode = Interactive;
683  this->showVerticalHeader();
684  }
685 
687  {
688  QHeaderView *verticalHeader = this->verticalHeader();
689  verticalHeader->setVisible(this->wordWrap() && m_resizeMode != ResizingAuto && m_rowResizeMode == Interactive);
690  verticalHeader->setFixedWidth(16);
691  }
692 
694  {
695  QHeaderView *verticalHeader = this->verticalHeader();
696  Q_ASSERT(verticalHeader);
697  verticalHeader->setSectionResizeMode(QHeaderView::ResizeToContents);
698  m_rowResizeMode = Content;
699  this->showVerticalHeader();
700  }
701 
703  {
704  if (elements > ResizeRowsToContentThreshold) { this->rowsResizeModeToInteractive(); }
705  else { this->rowsResizeModeToContent(); }
706  }
707 
708  int CViewBaseNonTemplate::showLoadIndicator(int containerSizeDependent, std::chrono::milliseconds timeout,
709  bool processEvents)
710  {
711  using namespace std::chrono_literals;
712  if (!m_enabledLoadIndicator) { return -1; }
713  if (m_showingLoadIndicator) { return -1; }
714 
715  if (this->hasDockWidgetArea())
716  {
717  if (!this->isVisibleWidget()) { return -1; }
718  }
719 
720  if (containerSizeDependent >= 0)
721  {
722  // really with indicator?
723  if (containerSizeDependent < ResizeSubsetThreshold) { return -1; }
724  }
725  m_showingLoadIndicator = true;
727 
728  if (!m_loadIndicator)
729  {
730  m_loadIndicator = new CLoadIndicator(64, 64, this);
731  connect(m_loadIndicator, &CLoadIndicator::timedOut, this, &CViewBaseNonTemplate::onLoadIndicatorTimedOut);
732  }
733  this->centerLoadIndicator();
734  return m_loadIndicator->startAnimation(timeout > 0ms ? timeout : m_loadIndicatorTimeoutDefault, processEvents);
735  }
736 
737  int CViewBaseNonTemplate::showLoadIndicatorWithTimeout(std::chrono::milliseconds timeout, bool processEvents)
738  {
739  return this->showLoadIndicator(-1, timeout, processEvents);
740  }
741 
743  {
744  if (!m_loadIndicator) { return; }
745  const QPoint middle = this->viewport()->geometry().center();
747  }
748 
750  {
751  if (!m_showingLoadIndicator) { return; }
752  m_showingLoadIndicator = false;
754  if (!m_loadIndicator) { return; }
755  m_loadIndicator->stopAnimation(loadingId);
756  }
757 
758  bool CViewBaseNonTemplate::isResizeConditionMet(int containerSize) const
759  {
760  if (m_resizeMode == ResizingAlways) { return true; }
761  if (m_resizeMode == PresizeSubset) { return false; }
762  if (m_resizeMode == ResizingOff) { return false; }
763  if (m_resizeMode == ResizingOnce) { return m_resizeCount < 1; }
764  if (m_resizeMode == ResizingAuto)
765  {
766  if (reachedResizeThreshold(containerSize)) { return false; }
767  if (m_resizeAutoNthTime < 2) { return true; }
768  return (m_resizeCount % m_resizeAutoNthTime) == 0;
769  }
770  return false;
771  }
772 
774  {
777 
792  // useless if mode is Interactive
793  if (m_rowResizeMode == Content)
794  {
795  this->resizeRowsToContents(); // rows
796  }
797  m_resizeCount++;
798 
799  // re-stretch
801  {
802  // toggling forces the stretch, otherwise not working
803  this->horizontalHeader()->setStretchLastSection(false);
804  this->horizontalHeader()->setStretchLastSection(true);
805  }
806 
807  // const int cols = this->colorCount();
808  // if (this->endsWithEmptyColumn()) { this->setColumnWidth(cols - 1, 10); }
809  // gives a weird NO METRICS warning
810 
811  this->resizeColumnsToContents(); // columns
812 
819  }
820 
821  void CViewBaseNonTemplate::customMenuRequested(const QPoint &pos)
822  {
823  QMenu menu;
824  CMenuActions menuActions;
825  this->customMenu(menuActions);
826  if (menuActions.isEmpty()) { return; }
827  menuActions.toQMenu(menu, true);
828 
829  // Nested dock widget menu
830  const CDockWidgetInfoArea *dockWidget = this->getDockWidgetInfoArea();
831  if (dockWidget)
832  {
833  if (!menu.isEmpty()) { menu.addSeparator(); }
834  const QString mm = QStringLiteral("Dock widget '%1'").arg(dockWidget->windowTitleOrBackup());
835  QMenu *dockWidgetSubMenu = menu.addMenu(CIcons::text16(), mm);
836  dockWidget->addToContextMenu(dockWidgetSubMenu);
837  }
838 
839  const QPoint globalPos = this->mapToGlobal(pos);
840  menu.exec(globalPos);
841  }
842 
843  void CViewBaseNonTemplate::onLoadIndicatorTimedOut() { m_showingLoadIndicator = false; }
844 
845  void CViewBaseNonTemplate::toggleResizeMode(bool checked)
846  {
847  m_resizeMode = checked ? ResizingAuto : ResizingOff;
848  if (m_resizeMode == ResizingAuto)
849  {
850  // make sure not use this one here
852  }
853  else { this->showVerticalHeader(); }
854  }
855 
856  void CViewBaseNonTemplate::toggleWordWrap(bool checked)
857  {
858  if (this->wordWrap() == checked) { return; }
859  if (checked)
860  {
861  // menuAddItems()
862  }
863  this->setWordWrap(checked);
864  this->showVerticalHeader(); // can be slow
865  }
866 
867  void CViewBaseNonTemplate::toggleAutoDisplay()
868  {
869  const QAction *a = qobject_cast<const QAction *>(QObject::sender());
870  if (!a) { return; }
871  Q_ASSERT_X(a->isCheckable(), Q_FUNC_INFO, "object not checkable");
872  m_displayAutomatically = a->isChecked();
873  }
874 
876  {
877  // FIXME: Workaround to implement the logic on our own because the default selectAll() implementation does not
878  // seem to work
879  this->clearSelection();
880  QItemSelection selectedItems;
881  const int columns = this->model()->columnCount() - 1;
882  const int rows = this->model()->rowCount() - 1;
883  selectedItems.select(this->model()->index(0, 0), this->model()->index(rows, columns));
884  this->selectionModel()->select(selectedItems, QItemSelectionModel::Select);
885  }
886 
887  void CViewBaseNonTemplate::setSingleSelection() { this->setSelectionMode(SingleSelection); }
888 
890  {
891  if (this->allowsMultipleSelectedRows()) { this->setSelectionMode(ExtendedSelection); }
892  }
893 
895  {
896  if (this->allowsMultipleSelectedRows()) { this->setSelectionMode(MultiSelection); }
897  }
898 
899  void CViewBaseNonTemplate::removeSelectedRowsChecked()
900  {
901  if (!m_enableDeleteSelectedRows) { return; }
902  this->removeSelectedRows();
903  }
904 
905  void CViewBaseNonTemplate::dragEnterEvent(QDragEnterEvent *event)
906  {
907  if (!event || !this->acceptDrop(event->mimeData())) { return; }
908  this->setBackgroundRole(QPalette::Highlight);
909  event->acceptProposedAction();
910  }
911 
912  void CViewBaseNonTemplate::dragMoveEvent(QDragMoveEvent *event)
913  {
914  if (!event || !this->acceptDrop(event->mimeData())) { return; }
915  event->acceptProposedAction();
916  }
917 
918  void CViewBaseNonTemplate::dragLeaveEvent(QDragLeaveEvent *event)
919  {
920  if (!event) { return; }
921  event->accept();
922  }
923 
924  void CViewBaseNonTemplate::dropEvent(QDropEvent *event)
925  {
926  if (!event) { return; }
927  QTableView::dropEvent(event);
928  }
929 
931  {
932  containerSize = containerSize >= 0 ? containerSize : this->rowCount();
933  const int presizeRandomElements = containerSize > 1000 ? containerSize / 100 : containerSize / 40;
934  return presizeRandomElements;
935  }
936 } // namespace swift::gui::views
QString windowTitleOrBackup() const
If current window title is empty, use backup.
Definition: dockwidget.cpp:433
Specialized class for dock widgets serving as info area.
virtual void addToContextMenu(QMenu *contextMenu) const
Contribute to menu.
CDockWidgetInfoArea * getDockWidgetInfoArea() const
Corresponding dockable widget in info area.
virtual bool setParentDockWidgetInfoArea(CDockWidgetInfoArea *parentDockableWidget)
Corresponding dockable widget in info area.
static QWidget * mainApplicationWidget()
Main application window widget.
Definition: guiutility.cpp:92
The QProgressIndicator class lets an application display a progress indicator to show that a lengthy ...
Definition: loadindicator.h:31
void centerLoadIndicator(const QPoint &middle)
Center this load indicator.
int startAnimation(std::chrono::milliseconds timeout=std::chrono::milliseconds(0), bool processEvents=false)
Starts the spin animation.
void timedOut()
Timed out.
void stopAnimation(int indicatorId=-1)
Stops the spin animation.
Using this class provides a QTableView with the overlay functionality already integrated.
static const QKeySequence & keyDelete()
Delete, e.g. selected rows.
Definition: shortcut.cpp:61
static const QKeySequence & keySelectAll()
For selecting all.
Definition: shortcut.cpp:30
static const QKeySequence & keyResizeView()
Resize view.
Definition: shortcut.cpp:42
static const QKeySequence & keyDisplayFilter()
Display filter.
Definition: shortcut.cpp:36
static const QKeySequence & keyClearSelection()
For deselecting all.
Definition: shortcut.cpp:24
static const QKeySequence & keyCopy()
Copy.
Definition: shortcut.cpp:73
static QString toParenthesisString(const QKeySequence &sequence)
As string for menus etc. Looks like "(CTRL + R)".
Definition: shortcut.cpp:97
static const QKeySequence & keySaveViews()
Save in views.
Definition: shortcut.cpp:54
Base for filter dialog.
Definition: filterdialog.h:20
Base for filter dialog.
Definition: filterwidget.h:21
QList< QAction * > getActions() const
Allow to use the actions directly.
Definition: fontmenus.cpp:46
void setEnabled(bool enabled)
Set enabled / disabled (allows to gray out)
Definition: menuaction.cpp:66
Bunch of CMenuAction objects.
Definition: menuaction.h:384
CMenuActions addActions(const CMenuActions &actions)
Add menu actions, returns last valid QAction.
Definition: menuaction.cpp:217
void toQMenu(QMenu &menu, bool separateGroups) const
Insert the sorted actions to the menu.
Definition: menuaction.cpp:326
CMenuAction addAction(const CMenuAction &menuAction)
Add menu action.
Definition: menuaction.cpp:210
bool isEmpty() const
Empty?
Definition: menuaction.h:405
CMenuAction first() const
First action.
Definition: menuaction.h:485
Interface to implement a custom menu.
Definition: menudelegate.h:21
IMenuDelegate * getNestedDelegate() const
Nested delegate.
Definition: menudelegate.h:32
virtual void customMenu(CMenuActions &menuActions)=0
Display custom menu.
void setNestedDelegate(IMenuDelegate *nestedDelegate)
Set nested delegate.
Definition: menudelegate.h:29
QAbstractItemView::SelectionMode getPreferredSelection() const
Preferred selection.
Definition: guisettings.cpp:29
virtual bool filterWidgetChangedFilter(bool enabled)=0
Filter changed in filter widget.
QString getFileDialogFileName(bool load) const
Default file for load/save operations.
QWidget * m_filterWidget
filter widget or dialog
Definition: viewbase.h:602
void menuAddItems(Menu menusToAdd)
Add given menu items.
virtual void setSelectionModel(QItemSelectionModel *model)
components::CTextEditDialog * textEditDialog()
Init text edit dialog if required and return pointer to it.
static constexpr int ResizeSubsetThreshold
When to use pre-sizing with random elements.
Definition: viewbase.h:153
void rowsResizeModeToInteractive()
Init as interactive, as this allows manually resizing.
bool m_forceStretchLastColumnWhenResized
a small table might (few columns) fail stretching, force again
Definition: viewbase.h:590
void hideLoadIndicator(int loadingId=-1)
Hide loading indicator.
void setHorizontalHeaderSectionResizeMode(QHeaderView::ResizeMode mode)
Resize mode.
virtual bool reachedResizeThreshold(int containerSize=-1) const =0
Skip resizing because of size?
swift::misc::CDirectories::ColumnIndex m_dirSettingsIndex
allows to set more specialized directories //!< remember last JSON directory, having this member allo...
Definition: viewbase.h:610
menus::IMenuDelegate * setCustomMenu(menus::IMenuDelegate *menu, bool nestPreviousMenu=true)
Set custom menu if applicable.
Menu m_menus
Default menu settings.
Definition: viewbase.h:603
@ PresizeSubset
use a subset of the data to resize
Definition: viewbase.h:104
@ ResizingAuto
resizing when below threshold,
Definition: viewbase.h:101
int selectedRowCount() const
Number of selected rows.
virtual void resizeToContents()
Resize to contents, strategy depends on container size.
SelectionMode m_originalSelectionMode
Selection mode set.
Definition: viewbase.h:585
void triggerReload()
Trigger reload from backend by signal requestUpdate();.
bool isCurrentlyAllowingMultipleRowSelections() const
Is the current selection mode allow multiple selection.
virtual int removeSelectedRows()=0
Remove selected rows.
int getPresizeRandomElementsSize(int containerSize=-1) const
Calculate presize count.
bool allowsMultipleSelectedRows() const
Allows to select multiple rows.
void requestNewBackendData()
Load data from backend (where it makes sense)
virtual void removeFilter()=0
Remove filter.
virtual QString getRememberedLastJsonDirectory() const
JSON directory.
virtual void onDoubleClicked(const QModelIndex &index)=0
Index double clicked.
virtual void displaySelectedJsonPopup()=0
Display JSON data.
virtual void onClicked(const QModelIndex &index)=0
Index clicked.
virtual bool acceptDrop(const QMimeData *mimeData) const =0
Accept drop data?
void loadIndicatorVisibilityChanged(bool visible)
Load indicator's visibility has been changed.
int getHorizontalHeaderFontHeight() const
Horizontal font height.
bool m_displayAutomatically
display directly when loaded
Definition: viewbase.h:597
QModelIndexList selectedRows() const
Selected rows if any.
void loadJsonAction()
Load JSON for action/menu, void return signatur.
void menuRemoveItems(Menu menusToRemove)
Remove given menu items.
virtual void rememberLastJsonDirectory(const QString &selectedFileOrDir)
JSON directory.
virtual swift::misc::CStatusMessage loadJson(const QString &directory={})=0
Load JSON.
CLoadIndicator * m_loadIndicator
load indicator if needed
Definition: viewbase.h:606
virtual QModelIndexList unselectedRows() const
Unselected (not selected) rows if any.
RowsResizeMode m_rowResizeMode
row resize mode for row height
Definition: viewbase.h:584
void requestUpdate()
Ask for new data from currently loaded data.
bool m_enabledLoadIndicator
loading indicator enabled/disabled
Definition: viewbase.h:593
int showLoadIndicatorWithTimeout(std::chrono::milliseconds timeout=std::chrono::milliseconds { 0 }, bool processEvents=true)
Show loading indicator which can time out.
void saveJsonAction()
Save JSON for action/menu, void return signatur.
void saveSelectedJsonAction()
Save JSON for action/menu, void return signatur.
virtual void resizeEvent(QResizeEvent *event)
QWidget * mainApplicationWindowWidget() const
Main application window widget if any.
virtual void displayJsonPopup()=0
Display JSON data.
virtual void performModeBasedResizeToContent()=0
Perform resizing (no presizing) / non slot method for template.
virtual void materializeFilter()=0
Materialize filter.
void setFilterWidget(filters::CFilterWidget *filterWidget)
Set filter widget.
virtual void dragEnterEvent(QDragEnterEvent *event)
swift::misc::CStatusMessage showFileLoadDialog(const QString &directory={})
Show file load dialog.
void onModelChanged()
Underlying model changed.
int m_resizeAutoNthTime
with ResizeAuto, resize every n-th time
Definition: viewbase.h:588
virtual void fullResizeToContents()
Full resizing to content, might be slow.
void setMultiSelection()
Change selection modes.
QMap< MenuFlag, menus::CMenuActions > m_menuFlagActions
initialized actions for menu flag (enum)
Definition: viewbase.h:608
std::chrono::milliseconds m_loadIndicatorTimeoutDefault
default time for timeout
Definition: viewbase.h:589
virtual void clear()=0
Clear data.
virtual void onRowSelected(const QModelIndex &index)=0
Row selected.
void rowsResizeModeToContent()
Resize mode to content.
virtual void dropEvent(QDropEvent *event)
virtual bool filterDialogFinished(int status)=0
Filter dialog finished.
QString m_saveFileName
save file name (JSON)
Definition: viewbase.h:609
menus::CFontMenu * m_fontMenu
font menu if applicable
Definition: viewbase.h:605
void centerLoadIndicator()
Center / re-center load indicator.
virtual bool isResizeConditionMet(int containerSize=-1) const
Resize or skip resize?
bool hasSingleSelectedRow() const
Single selected row.
bool hasMultipleSelectedRows() const
Multiple selected rows.
swift::misc::CSettingReadOnly< settings::TGeneralGui > m_guiSettings
general GUI settings
Definition: viewbase.h:616
virtual bool isEmpty() const =0
Empty?
int showLoadIndicator(int containerSizeDependent=-1, std::chrono::milliseconds timeout=std::chrono::milliseconds { 0 }, bool processEvents=true)
Show loading indicator.
int m_resizeCount
flag / counter, how many resize activities
Definition: viewbase.h:586
virtual void presizeOrFullResizeToContents()=0
Depending on CViewBaseNonTemplate::ResizeSubsetThreshold presize or fully resize.
virtual void customMenu(menus::CMenuActions &menuActions)
Method creating the menu.
swift::misc::CStatusMessage showFileSaveDialog(bool selectedOnly, const QString &directory={})
Show file save dialog.
virtual void updateSortIndicator()=0
Set the sort indicator to the current sort column.
virtual swift::misc::CStatusMessage saveJson(bool selectedOnly=false, const QString &directory={})=0
Save JSON.
void rowsResizeModeBasedOnThreshold(int elements)
Set content/interactive mode based on ResizeRowsToContentThreshold.
void enableLoadIndicator(bool enable)
Enable loading indicator.
static constexpr int ResizeRowsToContentThreshold
When to use rows resizing (which is slow)
Definition: viewbase.h:157
bool isShowingLoadIndicator
Load indicator property allows using in stylesheet.
Definition: viewbase.h:91
void settingsChanged()
Settings have been changed.
void setExtendedSelection()
Change selection modes.
void modelDataChanged(int count, bool withFilter)
Model data changed.
bool m_showingLoadIndicator
showing loading indicator
Definition: viewbase.h:592
int selectRows(const QSet< int > &rows)
Select given rows.
virtual bool setParentDockWidgetInfoArea(swift::gui::CDockWidgetInfoArea *parentDockableWidget)
Corresponding dockable widget in info area.
virtual void paste()=0
Clipboard cut/copy/paste.
void displayFilterDialog()
Display the filter dialog.
menus::IMenuDelegate * m_menu
custom menu if any
Definition: viewbase.h:604
swift::misc::CSetting< swift::misc::settings::TDirectorySettings > m_dirSettings
directory for load/save
Definition: viewbase.h:613
void setSingleSelection()
Change selection modes.
menus::CMenuActions initMenuActions(MenuFlag menu)
Init menu actions.
virtual void copy()=0
Clipboard cut/copy/paste.
void setFilterDialog(filters::CFilterDialog *filterDialog)
Filter dialog.
components::CTextEditDialog * m_textEditDialog
text edit dialog
Definition: viewbase.h:607
virtual void dragLeaveEvent(QDragLeaveEvent *event)
void triggerReloadFromBackend()
Trigger reload from backend by signal requestNewBackendData()
int unselectedRowCount() const
Unselected row count.
@ MenuDisplayAutomatically
allow to switch display automatically
Definition: viewbase.h:124
@ MenuClear
allow clearing the view via menu
Definition: viewbase.h:120
@ MenuFont
font related menu (size)
Definition: viewbase.h:135
@ MenuRefresh
allow refreshing the view via menu
Definition: viewbase.h:122
@ MenuCopy
copy (for copy/paste)
Definition: viewbase.h:132
@ MenuBackend
allow to request data from backend
Definition: viewbase.h:123
@ MenuRemoveSelectedRows
allow to remove selected rows
Definition: viewbase.h:121
@ MenuPaste
paste (for copy/paste)
Definition: viewbase.h:133
@ MenuMaterializeFilter
materialize filter (filtered data become model data)
Definition: viewbase.h:127
@ MenuFilter
filter can be opened
Definition: viewbase.h:126
@ MenuToggleSelectionMode
allow to toggle selection mode
Definition: viewbase.h:130
bool hasSelection() const
Selection (selected rows)
virtual void dragMoveEvent(QDragMoveEvent *event)
virtual int rowCount() const =0
Elements in container.
bool displayAutomatically() const
Display automatically (when models are loaded)
Definition: viewbase.h:219
bool m_enableDeleteSelectedRows
selected rows can be deleted
Definition: viewbase.h:598
const QFont & getHorizontalHeaderFont() const
Header (horizontal) font.
Definition: viewbase.h:225
virtual void cut()=0
Clipboard cut/copy/paste.
CStatusMessage setAndSave(const T &value, qint64 timestamp=0)
Write and save in the same step. Must be called from the thread in which the owner lives.
Definition: valuecache.h:417
T get() const
Get a copy of the current value.
Definition: valuecache.h:408
Directories (swift data directories)
Definition: directories.h:25
void setPropertyByIndex(CPropertyIndexRef index, const QVariant &variant)
Set property by index.
Definition: directories.cpp:82
QVariant propertyByIndex(CPropertyIndexRef index) const
Property by index.
Definition: directories.cpp:62
bool isEmpty() const
Message empty.
Streamable status message, e.g.
High level reusable GUI components.
Definition: aboutdialog.cpp:13
Filter to search data sets.
Models to be used with views, mainly QTableView.
Views, mainly QTableView.
GUI related classes.
Free functions in swift::misc.
auto singleShot(int msec, QObject *target, F &&task)
Starts a single-shot timer which will call a task in the thread of the given object when it times out...
Definition: threadutils.h:30