swift
guiutility.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2014 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
4 #include "gui/guiutility.h"
5 
6 #include <QAbstractItemModel>
7 #include <QApplication>
8 #include <QCheckBox>
9 #include <QComboBox>
10 #include <QGraphicsOpacityEffect>
11 #include <QJsonDocument>
12 #include <QJsonObject>
13 #include <QJsonValue>
14 #include <QLabel>
15 #include <QLayout>
16 #include <QLayoutItem>
17 #include <QLineEdit>
18 #include <QList>
19 #include <QMainWindow>
20 #include <QMetaType>
21 #include <QMimeData>
22 #include <QObject>
23 #include <QPointer>
24 #include <QPropertyAnimation>
25 #include <QRegularExpression>
26 #include <QScreen>
27 #include <QStringBuilder>
28 #include <QTabWidget>
29 #include <QThreadStorage>
30 #include <QTimer>
31 #include <QWidget>
32 #include <QWizard>
33 #include <Qt>
34 #include <QtGlobal>
35 
38 #include "misc/icon.h"
39 #include "misc/verify.h"
40 
41 // for the screen size
42 
43 #ifdef Q_OS_WINDOWS
44 # include <iostream>
45 
46 # include "wtypes.h"
47 #endif
48 
49 using namespace swift::misc;
50 
51 namespace swift::gui
52 {
53  QWidget *CGuiUtility::s_mainApplicationWidget = nullptr;
54 
55  CEnableForFramelessWindow *CGuiUtility::mainFramelessEnabledWindow()
56  {
57  const QWidgetList tlw = topLevelApplicationWidgetsWithName();
58  for (QWidget *w : tlw)
59  {
60  // best choice is to check on frameless window
62  if (!mw) { continue; }
63  if (mw->isMainApplicationWindow()) { return mw; }
64  }
65  return nullptr;
66  }
67 
68  namespace Private
69  {
70  QWidget *mainApplicationWidgetSearch()
71  {
72  CEnableForFramelessWindow *mw = CGuiUtility::mainFramelessEnabledWindow();
73  if (mw && mw->getWidget()) { return mw->getWidget(); }
74 
75  // second choice, try via QMainWindow
76  const QWidgetList tlw = CGuiUtility::topLevelApplicationWidgetsWithName();
77  for (QWidget *w : tlw)
78  {
79  QMainWindow *qmw = qobject_cast<QMainWindow *>(w);
80  if (!qmw) { continue; }
81  if (!qmw->parentWidget()) { return qmw; }
82  }
83  return nullptr;
84  }
85  } // namespace Private
86 
87  void CGuiUtility::registerMainApplicationWidget(QWidget *mainWidget)
88  {
89  CGuiUtility::s_mainApplicationWidget = mainWidget;
90  }
91 
92  QWidget *CGuiUtility::mainApplicationWidget()
93  {
94  if (!CGuiUtility::s_mainApplicationWidget)
95  {
96  CGuiUtility::s_mainApplicationWidget = Private::mainApplicationWidgetSearch();
97  }
98  return CGuiUtility::s_mainApplicationWidget;
99  }
100 
101  qreal CGuiUtility::mainApplicationWidgetPixelRatio()
102  {
103  const QWidget *mw = CGuiUtility::mainApplicationWidget();
104  if (mw) { return mw->devicePixelRatio(); }
105  return 1.0;
106  }
107 
108  QSize CGuiUtility::desktopSize()
109  {
110  const QWidget *mw = CGuiUtility::mainApplicationWidget();
111  if (!mw) return QGuiApplication::primaryScreen()->size();
112 
113  const QWindow *win = mw->windowHandle();
114  if (!win) return QGuiApplication::primaryScreen()->size();
115 
116  return win->size();
117  }
118 
119  namespace Private
120  {
121 #ifdef Q_OS_WINDOWS
122  QSize windowsGetDesktopResolution()
123  {
139  // https://stackoverflow.com/questions/2156212/how-to-get-the-monitor-screen-resolution-from-a-hwnd
140  const HWND hDesktop = GetDesktopWindow();
141  HMONITOR monitor = MonitorFromWindow(hDesktop, MONITOR_DEFAULTTONEAREST);
142  MONITORINFO info;
143  info.cbSize = sizeof(MONITORINFO);
144  GetMonitorInfo(monitor, &info);
145 
151  const int monitor_width = info.rcMonitor.right - info.rcMonitor.left;
152  const int monitor_height = info.rcMonitor.bottom - info.rcMonitor.top;
153  return QSize(monitor_height, monitor_width);
154 
161  }
162 #endif
163  } // namespace Private
164 
165  QSize CGuiUtility::physicalScreenSizeOs()
166  {
167 #ifdef Q_OS_WINDOWS
168  return Private::windowsGetDesktopResolution();
169 #elif defined(Q_OS_MAC)
170  return QSize();
171 #elif defined(Q_OS_LINUX)
172  return QSize();
173 #else
174  return QSize();
175 #endif
176  }
177 
178  bool CGuiUtility::isMainWindowFrameless()
179  {
180  const CEnableForFramelessWindow *mw = CGuiUtility::mainFramelessEnabledWindow();
181  return (mw && mw->isFrameless());
182  }
183 
184  QString CGuiUtility::screenInformation(const QString &separator)
185  {
186  const QSize ps = physicalScreenSizeOs();
187  QString i = u"Number of screens: " % QString::number(QGuiApplication::screens().size()) % separator %
188  u"Primary screen: " % QGuiApplication::primaryScreen()->name() % separator %
189  u"QT_AUTO_SCREEN_SCALE_FACTOR: " % qgetenv("QT_AUTO_SCREEN_SCALE_FACTOR") % separator %
190  u"QT_SCALE_FACTOR: " % qgetenv("QT_SCALE_FACTOR") % separator % u"QT_ENABLE_HIGHDPI_SCALING: " %
191  qgetenv("QT_ENABLE_HIGHDPI_SCALING") % separator % u"QT_SCALE_FACTOR_ROUNDING_POLICY: " %
192  qgetenv("QT_SCALE_FACTOR_ROUNDING_POLICY") % separator % u"QT_SCREEN_SCALE_FACTORS: " %
193  qgetenv("QT_SCREEN_SCALE_FACTORS") % separator % u"OS screen res." % QString::number(ps.width()) %
194  u"/" % QString::number(ps.height()) % separator;
195 
196  for (const QScreen *screen : QGuiApplication::screens())
197  {
198  i += separator % u"Information for screen: " % screen->name() % separator % u"Available geometry: " %
199  rectAsString(screen->availableGeometry()) % separator % u"Available size: " %
200  sizeAsString(screen->availableSize()) % separator % u"Available virtual geometry: " %
201  rectAsString(screen->availableVirtualGeometry()) % separator % u"Available virtual size: " %
202  sizeAsString(screen->availableVirtualSize()) % separator % u"Device ratio: " %
203  QString::number(screen->devicePixelRatio()) % separator % u"Depth: " %
204  QString::number(screen->depth()) % u"bits" % separator % u"Geometry: " %
205  rectAsString(screen->geometry()) % separator % u"Logical DPI: " %
206  QString::number(screen->logicalDotsPerInch()) % separator % u"Logical DPI X: " %
207  QString::number(screen->logicalDotsPerInchX()) % separator % u"Logical DPI Y: " %
208  QString::number(screen->logicalDotsPerInchY()) % separator % u"Orientation: " %
209  orientationAsString(screen->orientation()) % separator % u"Physical DPI: " %
210  QString::number(screen->physicalDotsPerInch()) % separator % u"Physical DPI X: " %
211  QString::number(screen->physicalDotsPerInchX()) % separator % u"Physical DPI Y: " %
212  QString::number(screen->physicalDotsPerInchY()) % separator % u"Physical size: " %
213  sizeAsString(screen->physicalSize()) % u"mm" % separator % u"Primary orientation: " %
214  orientationAsString(screen->primaryOrientation()) % separator % u"Refresh rate: " %
215  QString::number(screen->refreshRate()) % u"Hz" % u"Size: " % sizeAsString(screen->size()) % separator %
216  u"Virtual geometry: " % rectAsString(screen->virtualGeometry()) % separator % u"Virtual size: " %
217  sizeAsString(screen->virtualSize());
218  }
219  return i;
220  }
221 
222  const QString &CGuiUtility::orientationAsString(Qt::ScreenOrientation orientation)
223  {
224  static const QString pr("Primary");
225  static const QString la("Landscape");
226  static const QString po("Portrait");
227  static const QString il("Inverted landscape");
228  static const QString ip("Inverted portrait");
229 
230  switch (orientation)
231  {
232  case Qt::PrimaryOrientation: return pr;
233  case Qt::LandscapeOrientation: return la;
234  case Qt::PortraitOrientation: return po;
235  case Qt::InvertedLandscapeOrientation: return il;
236  case Qt::InvertedPortraitOrientation: return ip;
237  default: break;
238  }
239 
240  static const QString unknown("Unknown");
241  return unknown;
242  }
243 
244  QString CGuiUtility::rectAsString(const QRect &rect)
245  {
246  return QStringLiteral("x: %1 y: %2 w: %3 h: %4")
247  .arg(rect.x())
248  .arg(rect.y())
249  .arg(rect.width())
250  .arg(rect.height());
251  }
252 
253  QString CGuiUtility::rectAsString(const QRectF &rect)
254  {
255  return QStringLiteral("x: %1 y: %2 w: %3 h: %4")
256  .arg(rect.x())
257  .arg(rect.y())
258  .arg(rect.width())
259  .arg(rect.height());
260  }
261 
262  QString CGuiUtility::sizeAsString(const QSize &size)
263  {
264  return QStringLiteral("w: %1 h: %2").arg(size.width()).arg(size.height());
265  }
266 
267  QString CGuiUtility::sizeAsString(const QSizeF &size)
268  {
269  return QStringLiteral("w: %1 h: %2").arg(size.width()).arg(size.height());
270  }
271 
272  static QThreadStorage<QRegularExpression> tsRegex;
273 
274  bool CGuiUtility::lenientTitleComparison(const QString &title, const QString &comparison)
275  {
276  if (title == comparison) { return true; }
277 
278  QString t(title.trimmed().toLower().simplified());
279  QString c(comparison.trimmed().toLower().simplified());
280 
281  // we should not have empty titles
282  SWIFT_VERIFY_X(!t.isEmpty(), Q_FUNC_INFO, "missing title");
283  SWIFT_VERIFY_X(!c.isEmpty(), Q_FUNC_INFO, "missing comparison value");
284  if (t.isEmpty() || c.isEmpty()) { return false; }
285 
286  // same?
287  if (t == c) { return true; }
288 
289  // further unify
290  if (!tsRegex.hasLocalData()) { tsRegex.setLocalData(QRegularExpression("[^a-z0-9\\s]")); }
291  const QRegularExpression &regexp = tsRegex.localData();
292  t = t.remove(regexp);
293  c = c.remove(regexp);
294  return t == c;
295  }
296 
297  bool CGuiUtility::setComboBoxValueByStartingString(QComboBox *box, const QString &candidate,
298  const QString &unspecified)
299  {
300  if (!box) { return false; }
301  if (!candidate.isEmpty())
302  {
303  for (int i = 0; i < box->count(); i++)
304  {
305  const QString t(box->itemText(i));
306  if (t.startsWith(candidate, Qt::CaseInsensitive))
307  {
308  box->setCurrentIndex(i);
309  return true;
310  }
311  }
312  }
313 
314  // not found
315  if (unspecified.isEmpty()) { return false; }
316  for (int i = 0; i < box->count(); i++)
317  {
318  const QString t(box->itemText(i));
319  if (t.startsWith(unspecified, Qt::CaseInsensitive))
320  {
321  box->setCurrentIndex(i);
322  return true;
323  }
324  }
325  return false;
326  }
327 
328  bool CGuiUtility::setComboBoxValueByContainingString(QComboBox *box, const QString &candidate,
329  const QString &unspecified)
330  {
331  if (!box) { return false; }
332  if (!candidate.isEmpty())
333  {
334  const int ci = box->currentIndex();
335  for (int i = 0; i < box->count(); i++)
336  {
337  const QString t(box->itemText(i));
338  if (t.contains(candidate, Qt::CaseInsensitive))
339  {
340  if (ci == i) { return true; } // avoid signals
341  box->setCurrentIndex(i);
342  return true;
343  }
344  }
345  }
346 
347  // not found
348  if (unspecified.isEmpty()) { return false; }
349  const int ci = box->currentIndex();
350  for (int i = 0; i < box->count(); i++)
351  {
352  const QString t(box->itemText(i));
353  if (t.contains(unspecified, Qt::CaseInsensitive))
354  {
355  if (ci == i) { return true; } // avoid signals
356  box->setCurrentIndex(i);
357  return true;
358  }
359  }
360  return false;
361  }
362 
363  bool CGuiUtility::hasSwiftVariantMimeType(const QMimeData *mime)
364  {
365  return mime && mime->hasFormat(swiftJsonDragAndDropMimeType());
366  }
367 
368  CVariant CGuiUtility::fromSwiftDragAndDropData(const QMimeData *mime)
369  {
370  if (!hasSwiftVariantMimeType(mime)) { return CVariant(); }
371  return CGuiUtility::fromSwiftDragAndDropData(mime->data(swiftJsonDragAndDropMimeType()));
372  }
373 
374  CVariant CGuiUtility::fromSwiftDragAndDropData(const QByteArray &utf8Data)
375  {
376  if (utf8Data.isEmpty()) { return CVariant(); }
377  const QJsonDocument jsonDoc(QJsonDocument::fromJson(utf8Data));
378  const QJsonObject jsonObj(jsonDoc.object());
379  const QString typeName(jsonObj.value("type").toString());
380  const int typeId = QMetaType::fromName(qPrintable(typeName)).id();
381 
382  // check if a potential valid value object
383  if (typeName.isEmpty() || typeId == QMetaType::UnknownType) { return CVariant(); }
384 
385  CVariant valueVariant;
386  const CStatusMessage status = valueVariant.convertFromJsonNoThrow(jsonObj, {}, {});
387  if (status.isFailure()) { return CVariant(); }
388  return valueVariant;
389  }
390 
391  int CGuiUtility::metaTypeIdFromSwiftDragAndDropData(const QMimeData *mime)
392  {
393  constexpr int Unknown = static_cast<int>(QMetaType::UnknownType);
394 
395  if (!hasSwiftVariantMimeType(mime)) { return Unknown; }
396  static const QJsonObject jsonObj(QJsonDocument::fromJson(mime->data(swiftJsonDragAndDropMimeType())).object());
397  Q_ASSERT_X(!jsonObj.isEmpty(), Q_FUNC_INFO, "Empty JSON object");
398  const QString typeName(jsonObj.value("type").toString());
399  if (typeName.isEmpty()) { return Unknown; }
400  const int typeId = QMetaType::fromName(qPrintable(typeName)).id();
401  return typeId;
402  }
403 
404  const QString &CGuiUtility::swiftJsonDragAndDropMimeType()
405  {
406  static const QString m("text/json/swift");
407  return m;
408  }
409 
410  QFileInfo CGuiUtility::representedMimeFile(const QMimeData *mime)
411  {
412  if (!mime->hasText()) { return QFileInfo(); }
413  const QString candidate = mime->text();
414  if (candidate.isEmpty()) { return QFileInfo(); }
415  if (!candidate.contains("://")) { return QFileInfo(candidate); }
416  QUrl url(candidate);
417  const QString localFile = url.toLocalFile();
418  return QFileInfo(localFile);
419  }
420 
421  bool CGuiUtility::isMimeRepresentingReadableFile(const QMimeData *mime)
422  {
423  const QFileInfo fi = CGuiUtility::representedMimeFile(mime);
424  return fi.isReadable();
425  }
426 
427  bool CGuiUtility::isMimeRepresentingReadableJsonFile(const QMimeData *mime)
428  {
429  const QFileInfo fi = CGuiUtility::representedMimeFile(mime);
430  if (!fi.isReadable()) { return false; }
431  const QString fn = fi.fileName();
432  return fn.endsWith("json", Qt::CaseInsensitive);
433  }
434 
435  COverlayMessagesFrame *CGuiUtility::nextOverlayMessageFrame(QWidget *widget, int maxLevels)
436  {
437  return nextOverlayMessageWidget<COverlayMessagesFrame>(widget, maxLevels);
438  }
439 
440  COverlayMessagesTabWidget *CGuiUtility::nextOverlayMessageTabWidget(QWidget *widget, int maxLevels)
441  {
442  return nextOverlayMessageWidget<COverlayMessagesTabWidget>(widget, maxLevels);
443  }
444 
445  COverlayMessagesWizardPage *CGuiUtility::nextOverlayMessageWizardPage(QWidget *widget, int maxLevels)
446  {
447  return nextOverlayMessageWidget<COverlayMessagesWizardPage>(widget, maxLevels);
448  }
449 
450  void CGuiUtility::checkBoxReadOnly(QCheckBox *checkBox, bool readOnly)
451  {
452  static const QCheckBox defaultBox;
453  SWIFT_VERIFY_X(checkBox, Q_FUNC_INFO, "no checkbox");
454  if (!checkBox) { return; }
455 
456  static const QString background(
457  "background: rgb(40,40,40)");
458  if (readOnly)
459  {
461  checkBox->setFocusPolicy(Qt::NoFocus);
462 
463  // without that, the checkboxes appear not readonly
464  // obviously style sheet only does not work
465  checkBox->setStyleSheet(background);
466  }
467  else
468  {
471  checkBox->setFocusPolicy(defaultBox.focusPolicy());
472  checkBox->setStyleSheet("");
473  }
474  }
475 
476  void CGuiUtility::checkBoxesReadOnly(QWidget *parent, bool readOnly)
477  {
478  if (!parent) { return; }
479  QList<QCheckBox *> allCheckBoxes = parent->findChildren<QCheckBox *>();
480  for (QCheckBox *cb : allCheckBoxes) { CGuiUtility::checkBoxReadOnly(cb, readOnly); }
481  }
482 
483  void CGuiUtility::tempUnhidePassword(QLineEdit *lineEdit, int unhideMs)
484  {
485  if (!lineEdit) { return; }
486  if (lineEdit->text().isEmpty()) { return; }
487  if (lineEdit->echoMode() != QLineEdit::Password && lineEdit->echoMode() != QLineEdit::PasswordEchoOnEdit)
488  {
489  return;
490  }
491  const QLineEdit::EchoMode mode = lineEdit->echoMode();
492  lineEdit->setEchoMode(QLineEdit::Normal);
493  QPointer<QLineEdit> qpLineEdit(lineEdit);
494  QTimer::singleShot(unhideMs, lineEdit, [=] {
495  if (qpLineEdit) { qpLineEdit->setEchoMode(mode); }
496  });
497  }
498 
499  QWidgetList CGuiUtility::topLevelApplicationWidgetsWithName()
500  {
501  QWidgetList tlw = QApplication::topLevelWidgets();
502  QWidgetList rl;
503  for (QWidget *w : tlw)
504  {
505  if (w->objectName().isEmpty()) { continue; }
506  rl.append(w);
507  }
508  return rl;
509  }
510 
511  QPoint CGuiUtility::mainWidgetGlobalPosition()
512  {
513  QWidget *mw = CGuiUtility::mainApplicationWidget();
514  if (mw) { return mw->pos(); }
515 
516  // fallback, can be mfw it is not found
517  CEnableForFramelessWindow *mfw = CGuiUtility::mainFramelessEnabledWindow();
518  if (!mfw || !mfw->getWidget()) { return QPoint(); }
519  return mfw->getWidget()->pos(); // is main window, so not mapToGlobal
520  }
521 
522  QString CGuiUtility::replaceTabCountValue(const QString &oldName, int count)
523  {
524  const QString v = QString(" (").append(QString::number(count)).append(")");
525  if (oldName.isEmpty()) { return v; }
526  int index = oldName.lastIndexOf('(');
527  if (index == 0) { return v; }
528  if (index < 0) { return QString(oldName).trimmed().append(v); }
529  return QString(oldName.left(index)).trimmed().append(v);
530  }
531 
532  void CGuiUtility::deleteLayout(QLayout *layout, bool deleteWidgets)
533  {
534  // http://stackoverflow.com/a/7569928/356726
535  if (!layout) { return; }
536  QLayoutItem *item { nullptr };
537  while ((item = layout->takeAt(0)))
538  {
539  if (QLayout *sublayout = item->layout(); sublayout) { deleteLayout(sublayout, deleteWidgets); }
540  else if (QWidget *widget = item->widget(); widget)
541  {
542  widget->hide();
543  if (deleteWidgets) { delete widget; }
544  }
545  else { delete item; }
546  }
547 
548  // then finally
549  delete layout;
550  }
551 
552  bool CGuiUtility::staysOnTop(QWidget *widget)
553  {
554  if (!widget) { return false; }
555  const Qt::WindowFlags flags = widget->windowFlags();
556  return Qt::WindowStaysOnTopHint & flags;
557  }
558 
559  QTabWidget *CGuiUtility::parentTabWidget(QWidget *widget, int maxLevels)
560  {
561  int level = 0;
562  do {
563  widget = widget->parentWidget();
564  if (!widget) { return nullptr; }
565  QTabWidget *tw = qobject_cast<QTabWidget *>(widget);
566  if (tw) { return tw; }
567  level++;
568  }
569  while (level < maxLevels);
570  return nullptr;
571  }
572 
573  bool CGuiUtility::toggleStayOnTop(QWidget *widget)
574  {
575  if (!widget) { return false; }
576  Qt::WindowFlags flags = widget->windowFlags();
577  if (Qt::WindowStaysOnTopHint & flags)
578  {
579  flags &= ~Qt::WindowStaysOnTopHint;
580  // flags |= Qt::WindowStaysOnBottomHint;
581  }
582  else
583  {
584  flags &= ~Qt::WindowStaysOnBottomHint;
585  flags |= Qt::WindowStaysOnTopHint;
586  }
587  widget->setWindowFlags(flags);
588  widget->show(); // without that the window sometimes just disappears
589  return Qt::WindowStaysOnTopHint & flags;
590  }
591 
592  bool CGuiUtility::stayOnTop(bool onTop, QWidget *widget)
593  {
594  if (!widget) { return false; }
595  Qt::WindowFlags flags = widget->windowFlags();
596  if (onTop)
597  {
598  flags &= ~Qt::WindowStaysOnBottomHint;
599  flags |= Qt::WindowStaysOnTopHint;
600  }
601  else
602  {
603  flags &= ~Qt::WindowStaysOnTopHint;
604  // flags |= Qt::WindowStaysOnBottomHint;
605  }
606  widget->setWindowFlags(flags);
607  widget->show(); // without that the window sometimes just disappears
608  return onTop;
609  }
610 
611  QString CGuiUtility::marginsToString(const QMargins &margins)
612  {
613  return QStringLiteral("%1:%2:%3:%4")
614  .arg(margins.left())
615  .arg(margins.top())
616  .arg(margins.right())
617  .arg(margins.bottom());
618  }
619 
620  QMargins CGuiUtility::stringToMargins(const QString &str)
621  {
622  const QStringList parts = str.split(":");
623  Q_ASSERT_X(parts.size() == 4, Q_FUNC_INFO, "malformed");
624  bool ok = false;
625  const int l = parts.at(0).toInt(&ok);
626  Q_ASSERT_X(ok, Q_FUNC_INFO, "malformed number");
627  const int t = parts.at(1).toInt(&ok);
628  Q_ASSERT_X(ok, Q_FUNC_INFO, "malformed number");
629  const int r = parts.at(2).toInt(&ok);
630  Q_ASSERT_X(ok, Q_FUNC_INFO, "malformed number");
631  const int b = parts.at(3).toInt(&ok);
632  Q_ASSERT_X(ok, Q_FUNC_INFO, "malformed number");
633  Q_UNUSED(ok)
634  return QMargins(l, t, r, b);
635  }
636 
637  QList<int> CGuiUtility::indexToUniqueRows(const QModelIndexList &indexes)
638  {
639  QList<int> rows;
640  for (const QModelIndex &i : indexes)
641  {
642  const int r = i.row();
643  if (rows.contains(r)) { continue; }
644  rows.append(r);
645  }
646  return rows;
647  }
648 
649  int CGuiUtility::clearModel(QAbstractItemModel *model)
650  {
651  if (!model) { return 0; }
652  const int count = model->rowCount();
653  if (count < 1) { return 0; }
654  model->removeRows(0, count);
655  return count;
656  }
657 
658  bool CGuiUtility::isTopLevelWidget(QWidget *widget)
659  {
660  if (!widget) { return false; }
661  return QApplication::topLevelWidgets().contains(widget);
662  }
663 
664  bool CGuiUtility::isTopLevelWindow(QWidget *widget)
665  {
666  if (!widget) { return false; }
667  if (!widget->isWindow()) { return false; }
668  return QApplication::topLevelWidgets().contains(widget);
669  }
670 
671  bool CGuiUtility::isQMainWindow(const QWidget *widget)
672  {
673  if (!widget) { return false; }
674  const QMainWindow *mw = qobject_cast<const QMainWindow *>(widget);
675  return mw;
676  }
677 
678  bool CGuiUtility::isDialog(const QWidget *widget)
679  {
680  if (!widget) { return false; }
681  const QDialog *mw = qobject_cast<const QDialog *>(widget);
682  return mw;
683  }
684 
685  void CGuiUtility::disableMinMaxCloseButtons(QWidget *window)
686  {
687  if (!window->windowFlags().testFlag(Qt::CustomizeWindowHint))
688  {
691  }
694  }
695 
696  QGraphicsOpacityEffect *CGuiUtility::fadeInWidget(int durationMs, QWidget *widget, double startValue,
697  double endValue)
698  {
699  // http://stackoverflow.com/questions/19087822/how-to-make-qt-widgets-fade-in-or-fade-out#
700  Q_ASSERT(widget);
701  QGraphicsOpacityEffect *effect = new QGraphicsOpacityEffect(widget);
702  widget->setGraphicsEffect(effect);
703  QPropertyAnimation *a = new QPropertyAnimation(effect, "opacity");
704  a->setDuration(durationMs);
705  a->setStartValue(startValue);
706  a->setEndValue(endValue);
709  return effect;
710  }
711 
712  QGraphicsOpacityEffect *CGuiUtility::fadeOutWidget(int durationMs, QWidget *widget, double startValue,
713  double endValue)
714  {
715  Q_ASSERT(widget);
716  QGraphicsOpacityEffect *effect = new QGraphicsOpacityEffect(widget);
717  widget->setGraphicsEffect(effect);
718  QPropertyAnimation *a = new QPropertyAnimation(effect, "opacity");
719  a->setDuration(durationMs);
720  a->setStartValue(startValue);
721  a->setEndValue(endValue);
724  return effect;
725  }
726 
727  QFontMetricsF CGuiUtility::currentFontMetricsF() { return QFontMetricsF(qApp->font()); }
728 
729  QFont CGuiUtility::currentFont()
730  {
731  const QWidget *w = CGuiUtility::mainApplicationWidget();
732  if (w) { return w->font(); }
733  return QApplication::font();
734  }
735 
736  QSizeF CGuiUtility::fontMetrics80Chars(bool withRatio)
737  {
738  // scale is 3.0 on my hires display
739  static const QString s("01234567890123456789012345678901234567890123456789012345678901234567890123456789");
740  const QFontMetricsF fm = CGuiUtility::currentFontMetricsF();
741  const qreal scale = withRatio ? CGuiUtility::mainApplicationWidgetPixelRatio() : 1.0;
742  const QSizeF size = fm.size(Qt::TextSingleLine, s);
743  return size * scale;
744  }
745 
746  QSizeF CGuiUtility::fontMetricsLazyDog43Chars(bool withRatio)
747  {
748  // 43 characters 0123456789012345678901234567890123456789012
749  static const QString s("The quick brown fox jumps over the lazy dog");
750  const QFontMetricsF fm = CGuiUtility::currentFontMetricsF();
751  const qreal scale = withRatio ? CGuiUtility::mainApplicationWidgetPixelRatio() : 1.0;
752  const QSizeF size = fm.size(Qt::TextSingleLine, s);
753  return size * scale;
754  }
755 
756  QSizeF CGuiUtility::fontMetricsEstimateSize(int xCharacters, int yCharacters, bool withRatio)
757  {
758  // 1920/1080: 560/16 256/16 => 530/960
759  // 3840/2160: 400/10 178/10 => 375/600
760  // with ratio we get the physical solution, otherwise logical solution
761  const QSizeF s1 = CGuiUtility::fontMetrics80Chars(withRatio);
762  const QSizeF s2 = CGuiUtility::fontMetricsLazyDog43Chars(withRatio);
763  const QSizeF s = s1 + s2;
764  const qreal w = s.width() * xCharacters / 123; // 123 chars
765  const qreal h = s.height() * yCharacters / 2; // 2 lines
766  return QSizeF(w, h);
767  }
768 
769  void CGuiUtility::centerWidget(QWidget *widget)
770  {
771  const QPoint point(widget->width() / 2.0, 0);
772  const QScreen *pScreen = QGuiApplication::screenAt(widget->mapToGlobal(point));
773  const QRect screenGeometry = pScreen->availableGeometry();
774  const int x = (screenGeometry.width() - widget->width()) / 2;
775  const int y = (screenGeometry.height() - widget->height()) / 2;
776  widget->move(x, y);
777  }
778 
779  void CGuiUtility::centerWidget(QWidget *widget, QWidget *host)
780  {
781  if (!host) { host = widget->parentWidget(); }
782 
783  if (host)
784  {
785  const QRect hostRect = host->geometry();
786  widget->move(hostRect.center() - widget->rect().center());
787  }
788  else { CGuiUtility::centerWidget(widget); }
789  }
790 
791  QString CGuiUtility::metricsInfo()
792  {
793  static const QString s("%1 %2 | 80 chars: w%3 h%4 | 43 chars: w%5 h%6");
794  const QSizeF s80 = CGuiUtility::fontMetrics80Chars();
795  const QSizeF s43 = CGuiUtility::fontMetricsLazyDog43Chars();
796 
797  QString ratio("-");
798  QString desktop("-");
799 
800  const QWidget *mainWidget = CGuiUtility::mainApplicationWidget();
801  if (mainWidget)
802  {
803  const QSize sd = QGuiApplication::primaryScreen()->geometry().size();
804  desktop = QStringLiteral("Desktop w%1 w%2").arg(sd.width()).arg(sd.height());
805  ratio = QStringLiteral("ratio: %1").arg(mainWidget->devicePixelRatioF());
806  }
807  return s.arg(desktop).arg(ratio).arg(s80.width()).arg(s80.height()).arg(s43.width()).arg(s43.height());
808  }
809 
810  void CGuiUtility::forceStyleSheetUpdate(QWidget *widget)
811  {
812  if (!widget) { return; }
813  widget->setStyleSheet(widget->styleSheet());
814  }
815 
816  void CGuiUtility::superviseMainWindowMinSizes(qreal wRatio, qreal hRatio)
817  {
818  QWidget *w = CGuiUtility::mainApplicationWidget();
819  if (!w) { return; }
820  const QSize s = CGuiUtility::desktopSize();
821  const int minW = qRound(wRatio * s.width());
822  const int minH = qRound(hRatio * s.height());
823  w->setMinimumWidth(qMin(minW, w->minimumWidth()));
824  w->setMinimumHeight(qMin(minH, w->minimumHeight()));
825  }
826 
827  QString CGuiUtility::asSimpleHtmlImageWidth(const CIcon &icon, int width)
828  {
829  if (!icon.hasFileResourcePath()) return {};
830  const QString p = icon.getFileResourcePath();
831 
832  if (width < 0) { return QStringLiteral("<img src=\"%1\">").arg(p); }
833  return QStringLiteral("<img src=\"%1\" width=%2>").arg(p, QString::number(width));
834  }
835 
836  QString CGuiUtility::asSimpleHtmlImageHeight(const CIcon &icon, int height)
837  {
838  if (height < 0) { return CGuiUtility::asSimpleHtmlImageWidth(icon); }
839  if (!icon.hasFileResourcePath()) return {};
840  const QString p = icon.getFileResourcePath();
841 
842  return QStringLiteral("<img src=\"%1\" height=%2>").arg(p, QString::number(height));
843  }
844 
845  QDialog *CGuiUtility::findParentDialog(QWidget *widget)
846  {
847  if (CGuiUtility::isDialog(widget)) { return qobject_cast<QDialog *>(widget); }
848  while (widget->parent())
849  {
850  widget = widget->parentWidget();
851  if (CGuiUtility::isDialog(widget)) { return qobject_cast<QDialog *>(widget); }
852  }
853  return nullptr;
854  }
855 
856  QDialog *CGuiUtility::findParentDialog(QWidget *widget, int maxLevel)
857  {
858  if (CGuiUtility::isDialog(widget)) { return qobject_cast<QDialog *>(widget); }
859  int level = 0;
860  while (widget->parent())
861  {
862  level++;
863  if (level > maxLevel) { return nullptr; }
864  widget = widget->parentWidget();
865  if (CGuiUtility::isDialog(widget)) { return qobject_cast<QDialog *>(widget); }
866  }
867  return nullptr;
868  }
869 
870  void CGuiUtility::setElidedText(QLabel *label, const QString &text, Qt::TextElideMode mode)
871  {
872  if (!label) { return; }
873 
874  label->setToolTip(text);
875  if (mode == Qt::ElideNone)
876  {
877  label->setText(text);
878  return;
879  }
880 
881  const QFontMetrics metrics(label->font());
882  const int width = qMax(label->width() - 2, 0);
883  const QString clippedText = metrics.elidedText(text, mode, width);
884  label->setText(clippedText);
885  }
886 
887  void CGuiUtility::setElidedText(QLabel *label, const QString &shortText, const QString &longText,
888  Qt::TextElideMode mode)
889  {
890  if (!label) { return; }
891  if (shortText.isEmpty())
892  {
893  CGuiUtility::setElidedText(label, longText, mode);
894  return;
895  }
896  if (longText.isEmpty())
897  {
898  CGuiUtility::setElidedText(label, shortText, mode);
899  return;
900  }
901 
902  label->setToolTip(longText);
903  const QFontMetrics metrics(label->font());
904  const int width = qMax(label->width() - 2, 0);
905  const int wl = metrics.horizontalAdvance(longText);
906  if (wl >= width)
907  {
908  label->setText(longText);
909  return;
910  }
911  if (qRound(wl * 0.85) > wl)
912  {
913  const QString clippedText = metrics.elidedText(longText, mode, width);
914  label->setText(clippedText);
915  return;
916  }
917  const QString clippedText = metrics.elidedText(shortText, mode, width);
918  label->setText(clippedText);
919  }
920 
921  void CGuiUtility::setWizardButtonWidths(QWizard *wizard)
922  {
923  if (!wizard) { return; }
924 
925  const int minW = qMax(qRound(CGuiUtility::fontMetricsLazyDog43Chars(true).width() * 6.0 / 43.0), 80);
926  if (wizard->button(QWizard::BackButton)) { wizard->button(QWizard::BackButton)->setMinimumWidth(minW); }
927  if (wizard->button(QWizard::NextButton)) { wizard->button(QWizard::NextButton)->setMinimumWidth(minW); }
928  if (wizard->button(QWizard::CancelButton)) { wizard->button(QWizard::CancelButton)->setMinimumWidth(minW); }
929  if (wizard->button(QWizard::FinishButton)) { wizard->button(QWizard::FinishButton)->setMinimumWidth(minW); }
933  }
934 
935  QWidgetList CGuiUtility::getAllModallWidgets()
936  {
937  const QWidgetList widgets = QApplication::topLevelWidgets();
938  QWidgetList openWidgets;
939  for (QWidget *w : widgets)
940  {
941  if (w->isModal()) { openWidgets.push_back(w); }
942  }
943  return openWidgets;
944  }
945 
946  QStringList CGuiUtility::getAllWidgetTitles(const QWidgetList &widgets)
947  {
948  QStringList titles;
949  for (const QWidget *w : widgets)
950  {
951  if (!w) { continue; }
952  if (!w->windowTitle().isEmpty()) { titles.push_back(w->windowTitle()); }
953  else { titles.push_back(QStringLiteral("name: ") % w->objectName()); }
954  }
955  return titles;
956  }
957 
958  QStringList CGuiUtility::getAllWidgetNames(const QWidgetList &widgets)
959  {
960  QStringList titles;
961  for (const QWidget *w : widgets)
962  {
963  if (!w) { continue; }
964  titles.push_back(QStringLiteral("name: ") % w->objectName());
965  }
966  return titles;
967  }
968 
969  QList<QDockWidget *> CGuiUtility::getAllDockWidgets(QWidget *parent, bool floatingOnly)
970  {
971  QList<QDockWidget *> docks;
972  if (parent)
973  {
974  const auto children = parent->findChildren<QDockWidget *>();
975  for (QDockWidget *w : children)
976  {
977  if (!w) { continue; }
978  if (!floatingOnly || w->isFloating()) { docks.push_back(w); }
979  }
980  }
981  return docks;
982  }
983 
984  QList<QDockWidget *> CGuiUtility::getAllDockWidgets(QWindow *parent, bool floatingOnly)
985  {
986  QList<QDockWidget *> docks;
987  if (parent)
988  {
989  const auto children = parent->findChildren<QDockWidget *>();
990  for (QDockWidget *w : children)
991  {
992  if (!w) { continue; }
993  if (!floatingOnly || w->isFloating()) { docks.push_back(w); }
994  }
995  }
996  return docks;
997  }
998 
999  QWidgetList CGuiUtility::closeAllModalWidgets()
1000  {
1001  QWidgetList modals = getAllModallWidgets();
1002  for (QWidget *w : modals)
1003  {
1004  if (!w) { continue; }
1005  w->close();
1006  }
1007  return modals;
1008  }
1009 
1010  QStringList CGuiUtility::closeAllModalWidgetsGetTitles()
1011  {
1012  const QWidgetList modals = getAllModallWidgets();
1013  QStringList titles;
1014  for (QWidget *w : modals)
1015  {
1016  if (!w) { continue; }
1017  titles << w->windowTitle();
1018  w->close();
1019  }
1020  return titles;
1021  }
1022 
1023  QList<QDockWidget *> CGuiUtility::closeAllDockWidgets(QWidget *parent, bool floatingOnly)
1024  {
1025  QList<QDockWidget *> dws = getAllDockWidgets(parent, floatingOnly);
1026  for (QWidget *w : dws)
1027  {
1028  if (!w) { continue; }
1029  w->close();
1030  }
1031  return dws;
1032  }
1033 
1034  QList<QDockWidget *> CGuiUtility::closeAllDockWidgets(QWindow *parent, bool floatingOnly)
1035  {
1036  QList<QDockWidget *> dws = getAllDockWidgets(parent, floatingOnly);
1037  for (QWidget *w : dws)
1038  {
1039  if (!w) { continue; }
1040  w->close();
1041  }
1042  return dws;
1043  }
1044 
1045  QStringList CGuiUtility::closeAllDockWidgetsGetTitles(QWidget *parent, bool floatingOnly)
1046  {
1047  const QList<QDockWidget *> dws = getAllDockWidgets(parent, floatingOnly);
1048  QStringList titles;
1049  for (QWidget *w : dws)
1050  {
1051  if (!w) { continue; }
1052  titles << w->windowTitle();
1053  w->close();
1054  }
1055  return titles;
1056  }
1057 
1058  QStringList CGuiUtility::closeAllDockWidgetsGetTitles(QWindow *parent, bool floatingOnly)
1059  {
1060  const QList<QDockWidget *> dws = getAllDockWidgets(parent, floatingOnly);
1061  QStringList titles;
1062  for (QWidget *w : dws)
1063  {
1064  if (!w) { continue; }
1065  titles << w->windowTitle();
1066  w->close();
1067  }
1068  return titles;
1069  }
1070 
1071  QStringList CGuiUtility::deleteLaterAllDockWidgetsGetTitles(QWidget *parent, bool floatingOnly)
1072  {
1073  const QList<QDockWidget *> dws = getAllDockWidgets(parent, floatingOnly);
1074  QStringList titles;
1075  for (QWidget *w : dws)
1076  {
1077  if (!w) { continue; }
1078  titles << w->windowTitle();
1079  w->deleteLater(); // DANGEROUS
1080  }
1081  return titles;
1082  }
1083 
1084 } // namespace swift::gui
Main window which can be frameless.
bool isMainApplicationWindow() const
Is main application, explicitly set.
QWidget * getWidget() const
Corresponding QMainWindow.
Using this class provides a QFrame with the overlay functionality already integrated.
Using this class provides a QTabWidget with the overlay functionality already integrated.
Using this class provides a QWizardPage with the overlay functionality already integrated.
Value object for icons. An icon is stored in the global icon repository and identified by its index....
Definition: icon.h:39
bool hasFileResourcePath() const
Resource path available?
Definition: icon.h:60
const QString & getFileResourcePath() const
Resource path if any.
Definition: icon.h:57
Streamable status message, e.g.
bool isFailure() const
Operation considered unsuccessful.
Wrapper around QVariant which provides transparent access to CValueObject methods of the contained ob...
Definition: variant.h:66
CStatusMessage convertFromJsonNoThrow(const QJsonObject &json, const CLogCategoryList &categories, const QString &prefix)
Call convertFromJson, catch any CJsonException that is thrown and return it as CStatusMessage.
GUI related classes.
Free functions in swift::misc.
void start(QAbstractAnimation::DeletionPolicy policy)
virtual bool removeRows(int row, int count, const QModelIndex &parent)
virtual int rowCount(const QModelIndex &parent) const const=0
QFont font()
QWidgetList topLevelWidgets()
bool isEmpty() const const
void setCurrentIndex(int index)
QString itemText(int index) const const
QString fileName() const const
bool isReadable() const const
QString elidedText(const QString &text, Qt::TextElideMode mode, int width, int flags) const const
int horizontalAdvance(QChar ch) const const
QSizeF size(int flags, const QString &text, int tabStops, int *tabArray) const const
QScreen * screenAt(const QPoint &point)
QList< QScreen * > screens()
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
QJsonObject object() const const
bool isEmpty() const const
QJsonValue value(QLatin1StringView key) const const
QString toString() const const
void setText(const QString &)
virtual QLayout * layout() override
virtual QLayoutItem * takeAt(int index)=0
void append(QList< T > &&value)
QList< T >::const_reference at(qsizetype i) const const
bool contains(const AT &value) const const
void push_back(QList< T >::parameter_type value)
qsizetype size() const const
int bottom() const const
int left() const const
int right() const const
int top() const const
QMetaType fromName(QByteArrayView typeName)
int id() const const
QByteArray data(const QString &mimeType) const const
virtual bool hasFormat(const QString &mimeType) const const
bool hasText() const const
QString text() const const
QList< T > findChildren(QAnyStringView name, Qt::FindChildOptions options) const const
QObject * parent() const const
qreal devicePixelRatio() const const
qreal devicePixelRatioF() const const
QPoint center() const const
int height() const const
int width() const const
int x() const const
int y() const const
qreal height() const const
qreal width() const const
qreal x() const const
qreal y() const const
int height() const const
int width() const const
qreal height() const const
qreal width() const const
QString & append(QChar ch)
QString arg(Args &&... args) const const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype lastIndexOf(QChar ch, Qt::CaseSensitivity cs) const const
QString left(qsizetype n) &&
QString number(double n, char format, int precision)
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QString simplified() const const
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QString toLower() const const
QString trimmed() const const
CaseInsensitive
ScreenOrientation
TextElideMode
TextSingleLine
WA_TransparentForMouseEvents
typedef WindowFlags
QString toLocalFile() const const
void setDuration(int msecs)
void setEasingCurve(const QEasingCurve &easing)
void setEndValue(const QVariant &value)
void setStartValue(const QVariant &value)
void setFocusPolicy(Qt::FocusPolicy policy)
bool isWindow() const const
QPoint mapToGlobal(const QPoint &pos) const const
void setMinimumHeight(int minh)
void setMinimumWidth(int minw)
QWidget * parentWidget() const const
void setAttribute(Qt::WidgetAttribute attribute, bool on)
void setGraphicsEffect(QGraphicsEffect *effect)
void setWindowFlag(Qt::WindowType flag, bool on)
void show()
void setStyleSheet(const QString &styleSheet)
bool testAttribute(Qt::WidgetAttribute attribute) const const
void setToolTip(const QString &)
QWindow * windowHandle() const const
virtual QSize size() const const override
QAbstractButton * button(QWizard::WizardButton which) const const
#define SWIFT_VERIFY_X(COND, WHERE, WHAT)
A weaker kind of assert.
Definition: verify.h:26