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::type(qPrintable(typeName));
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::type(qPrintable(typeName));
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  {
460  checkBox->setAttribute(Qt::WA_TransparentForMouseEvents);
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  {
469  checkBox->setAttribute(Qt::WA_TransparentForMouseEvents,
470  defaultBox.testAttribute(Qt::WA_TransparentForMouseEvents));
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  QLayout *sublayout { nullptr };
540  QWidget *widget { nullptr };
541  if ((sublayout = item->layout())) { deleteLayout(sublayout, deleteWidgets); }
542  else if ((widget = item->widget()))
543  {
544  widget->hide();
545  if (deleteWidgets) { delete widget; }
546  }
547  else { delete item; }
548  }
549 
550  // then finally
551  delete layout;
552  }
553 
554  bool CGuiUtility::staysOnTop(QWidget *widget)
555  {
556  if (!widget) { return false; }
557  const Qt::WindowFlags flags = widget->windowFlags();
558  return Qt::WindowStaysOnTopHint & flags;
559  }
560 
561  QTabWidget *CGuiUtility::parentTabWidget(QWidget *widget, int maxLevels)
562  {
563  int level = 0;
564  do {
565  widget = widget->parentWidget();
566  if (!widget) { return nullptr; }
567  QTabWidget *tw = qobject_cast<QTabWidget *>(widget);
568  if (tw) { return tw; }
569  level++;
570  }
571  while (level < maxLevels);
572  return nullptr;
573  }
574 
575  bool CGuiUtility::toggleStayOnTop(QWidget *widget)
576  {
577  if (!widget) { return false; }
578  Qt::WindowFlags flags = widget->windowFlags();
579  if (Qt::WindowStaysOnTopHint & flags)
580  {
581  flags &= ~Qt::WindowStaysOnTopHint;
582  // flags |= Qt::WindowStaysOnBottomHint;
583  }
584  else
585  {
586  flags &= ~Qt::WindowStaysOnBottomHint;
587  flags |= Qt::WindowStaysOnTopHint;
588  }
589  widget->setWindowFlags(flags);
590  widget->show(); // without that the window sometimes just disappears
591  return Qt::WindowStaysOnTopHint & flags;
592  }
593 
594  bool CGuiUtility::stayOnTop(bool onTop, QWidget *widget)
595  {
596  if (!widget) { return false; }
597  Qt::WindowFlags flags = widget->windowFlags();
598  if (onTop)
599  {
600  flags &= ~Qt::WindowStaysOnBottomHint;
601  flags |= Qt::WindowStaysOnTopHint;
602  }
603  else
604  {
605  flags &= ~Qt::WindowStaysOnTopHint;
606  // flags |= Qt::WindowStaysOnBottomHint;
607  }
608  widget->setWindowFlags(flags);
609  widget->show(); // without that the window sometimes just disappears
610  return onTop;
611  }
612 
613  QString CGuiUtility::marginsToString(const QMargins &margins)
614  {
615  return QStringLiteral("%1:%2:%3:%4")
616  .arg(margins.left())
617  .arg(margins.top())
618  .arg(margins.right())
619  .arg(margins.bottom());
620  }
621 
622  QMargins CGuiUtility::stringToMargins(const QString &str)
623  {
624  const QStringList parts = str.split(":");
625  Q_ASSERT_X(parts.size() == 4, Q_FUNC_INFO, "malformed");
626  bool ok = false;
627  const int l = parts.at(0).toInt(&ok);
628  Q_ASSERT_X(ok, Q_FUNC_INFO, "malformed number");
629  const int t = parts.at(1).toInt(&ok);
630  Q_ASSERT_X(ok, Q_FUNC_INFO, "malformed number");
631  const int r = parts.at(2).toInt(&ok);
632  Q_ASSERT_X(ok, Q_FUNC_INFO, "malformed number");
633  const int b = parts.at(3).toInt(&ok);
634  Q_ASSERT_X(ok, Q_FUNC_INFO, "malformed number");
635  Q_UNUSED(ok)
636  return QMargins(l, t, r, b);
637  }
638 
639  QList<int> CGuiUtility::indexToUniqueRows(const QModelIndexList &indexes)
640  {
641  QList<int> rows;
642  for (const QModelIndex &i : indexes)
643  {
644  const int r = i.row();
645  if (rows.contains(r)) { continue; }
646  rows.append(r);
647  }
648  return rows;
649  }
650 
651  int CGuiUtility::clearModel(QAbstractItemModel *model)
652  {
653  if (!model) { return 0; }
654  const int count = model->rowCount();
655  if (count < 1) { return 0; }
656  model->removeRows(0, count);
657  return count;
658  }
659 
660  bool CGuiUtility::isTopLevelWidget(QWidget *widget)
661  {
662  if (!widget) { return false; }
663  return QApplication::topLevelWidgets().contains(widget);
664  }
665 
666  bool CGuiUtility::isTopLevelWindow(QWidget *widget)
667  {
668  if (!widget) { return false; }
669  if (!widget->isWindow()) { return false; }
670  return QApplication::topLevelWidgets().contains(widget);
671  }
672 
673  bool CGuiUtility::isQMainWindow(const QWidget *widget)
674  {
675  if (!widget) { return false; }
676  const QMainWindow *mw = qobject_cast<const QMainWindow *>(widget);
677  return mw;
678  }
679 
680  bool CGuiUtility::isDialog(const QWidget *widget)
681  {
682  if (!widget) { return false; }
683  const QDialog *mw = qobject_cast<const QDialog *>(widget);
684  return mw;
685  }
686 
687  void CGuiUtility::disableMinMaxCloseButtons(QWidget *window)
688  {
689  if (!window->windowFlags().testFlag(Qt::CustomizeWindowHint))
690  {
691  window->setWindowFlag(Qt::CustomizeWindowHint);
692  window->setWindowFlag(Qt::WindowTitleHint);
693  }
694  window->setWindowFlag(Qt::WindowMinMaxButtonsHint, false);
695  window->setWindowFlag(Qt::WindowCloseButtonHint, false);
696  }
697 
698  QGraphicsOpacityEffect *CGuiUtility::fadeInWidget(int durationMs, QWidget *widget, double startValue,
699  double endValue)
700  {
701  // http://stackoverflow.com/questions/19087822/how-to-make-qt-widgets-fade-in-or-fade-out#
702  Q_ASSERT(widget);
703  QGraphicsOpacityEffect *effect = new QGraphicsOpacityEffect(widget);
704  widget->setGraphicsEffect(effect);
705  QPropertyAnimation *a = new QPropertyAnimation(effect, "opacity");
706  a->setDuration(durationMs);
707  a->setStartValue(startValue);
708  a->setEndValue(endValue);
709  a->setEasingCurve(QEasingCurve::InBack);
710  a->start(QPropertyAnimation::DeleteWhenStopped);
711  return effect;
712  }
713 
714  QGraphicsOpacityEffect *CGuiUtility::fadeOutWidget(int durationMs, QWidget *widget, double startValue,
715  double endValue)
716  {
717  Q_ASSERT(widget);
718  QGraphicsOpacityEffect *effect = new QGraphicsOpacityEffect(widget);
719  widget->setGraphicsEffect(effect);
720  QPropertyAnimation *a = new QPropertyAnimation(effect, "opacity");
721  a->setDuration(durationMs);
722  a->setStartValue(startValue);
723  a->setEndValue(endValue);
724  a->setEasingCurve(QEasingCurve::OutBack);
725  a->start(QPropertyAnimation::DeleteWhenStopped);
726  return effect;
727  }
728 
729  QFontMetrics CGuiUtility::currentFontMetrics()
730  {
731  const QWidget *w = CGuiUtility::mainApplicationWidget();
732  if (w) { return w->fontMetrics(); }
733  return QApplication::fontMetrics();
734  }
735 
736  QFontMetricsF CGuiUtility::currentFontMetricsF() { return QFontMetricsF(CGuiUtility::currentFontMetrics()); }
737 
738  QFont CGuiUtility::currentFont()
739  {
740  const QWidget *w = CGuiUtility::mainApplicationWidget();
741  if (w) { return w->font(); }
742  return QApplication::font();
743  }
744 
745  QSizeF CGuiUtility::fontMetrics80Chars(bool withRatio)
746  {
747  // scale is 3.0 on my hires display
748  static const QString s("01234567890123456789012345678901234567890123456789012345678901234567890123456789");
749  const QFontMetricsF fm = CGuiUtility::currentFontMetricsF();
750  const qreal scale = withRatio ? CGuiUtility::mainApplicationWidgetPixelRatio() : 1.0;
751  const QSizeF size = fm.size(Qt::TextSingleLine, s);
752  return size * scale;
753  }
754 
755  QSizeF CGuiUtility::fontMetricsLazyDog43Chars(bool withRatio)
756  {
757  // 43 characters 0123456789012345678901234567890123456789012
758  static const QString s("The quick brown fox jumps over the lazy dog");
759  const QFontMetricsF fm = CGuiUtility::currentFontMetrics();
760  const qreal scale = withRatio ? CGuiUtility::mainApplicationWidgetPixelRatio() : 1.0;
761  const QSizeF size = fm.size(Qt::TextSingleLine, s);
762  return size * scale;
763  }
764 
765  QSizeF CGuiUtility::fontMetricsEstimateSize(int xCharacters, int yCharacters, bool withRatio)
766  {
767  // 1920/1080: 560/16 256/16 => 530/960
768  // 3840/2160: 400/10 178/10 => 375/600
769  // with ratio we get the physical solution, otherwise logical solution
770  const QSizeF s1 = CGuiUtility::fontMetrics80Chars(withRatio);
771  const QSizeF s2 = CGuiUtility::fontMetricsLazyDog43Chars(withRatio);
772  const QSizeF s = s1 + s2;
773  const qreal w = s.width() * xCharacters / 123; // 123 chars
774  const qreal h = s.height() * yCharacters / 2; // 2 lines
775  return QSizeF(w, h);
776  }
777 
778  void CGuiUtility::centerWidget(QWidget *widget)
779  {
780  const QPoint point(widget->width() / 2.0, 0);
781  const QScreen *pScreen = QGuiApplication::screenAt(widget->mapToGlobal(point));
782  const QRect screenGeometry = pScreen->availableGeometry();
783  const int x = (screenGeometry.width() - widget->width()) / 2;
784  const int y = (screenGeometry.height() - widget->height()) / 2;
785  widget->move(x, y);
786  }
787 
788  void CGuiUtility::centerWidget(QWidget *widget, QWidget *host)
789  {
790  if (!host) { host = widget->parentWidget(); }
791 
792  if (host)
793  {
794  const QRect hostRect = host->geometry();
795  widget->move(hostRect.center() - widget->rect().center());
796  }
797  else { CGuiUtility::centerWidget(widget); }
798  }
799 
800  QString CGuiUtility::metricsInfo()
801  {
802  static const QString s("%1 %2 %3 | 80 chars: w%4 h%5 | 43 chars: w%6 h%7");
803  const QSizeF s80 = CGuiUtility::fontMetrics80Chars();
804  const QSizeF s43 = CGuiUtility::fontMetricsLazyDog43Chars();
805 
806  QString ratio("-");
807  QString desktop("-");
808 
809  const QWidget *mainWidget = CGuiUtility::mainApplicationWidget();
810  if (mainWidget)
811  {
812  const QSize sd = QGuiApplication::primaryScreen()->geometry().size();
813  desktop = QStringLiteral("Desktop w%1 w%2").arg(sd.width()).arg(sd.height());
814  ratio = QStringLiteral("ratio: %1").arg(mainWidget->devicePixelRatioF());
815  }
816  return s.arg(desktop)
817  .arg(CGuiUtility::isUsingHighDpiScreenSupport() ? "hi DPI" : "-")
818  .arg(ratio)
819  .arg(s80.width())
820  .arg(s80.height())
821  .arg(s43.width())
822  .arg(s43.height());
823  }
824 
825  bool CGuiUtility::isUsingHighDpiScreenSupport()
826  {
827  const QByteArray v = qgetenv("QT_AUTO_SCREEN_SCALE_FACTOR");
828  const QString vs(v);
829  const bool highDpi = stringToBool(vs);
830  return highDpi;
831  }
832 
833  void CGuiUtility::forceStyleSheetUpdate(QWidget *widget)
834  {
835  if (!widget) { return; }
836  widget->setStyleSheet(widget->styleSheet());
837  }
838 
839  void CGuiUtility::superviseMainWindowMinSizes(qreal wRatio, qreal hRatio)
840  {
841  QWidget *w = CGuiUtility::mainApplicationWidget();
842  if (!w) { return; }
843  const QSize s = CGuiUtility::desktopSize();
844  const int minW = qRound(wRatio * s.width());
845  const int minH = qRound(hRatio * s.height());
846  w->setMinimumWidth(qMin(minW, w->minimumWidth()));
847  w->setMinimumHeight(qMin(minH, w->minimumHeight()));
848  }
849 
850  QString CGuiUtility::asSimpleHtmlImageWidth(const CIcon &icon, int width)
851  {
852  if (!icon.hasFileResourcePath()) return {};
853  const QString p = icon.getFileResourcePath();
854 
855  if (width < 0) { return QStringLiteral("<img src=\"%1\">").arg(p); }
856  return QStringLiteral("<img src=\"%1\" width=%2>").arg(p, QString::number(width));
857  }
858 
859  QString CGuiUtility::asSimpleHtmlImageHeight(const CIcon &icon, int height)
860  {
861  if (height < 0) { return CGuiUtility::asSimpleHtmlImageWidth(icon); }
862  if (!icon.hasFileResourcePath()) return {};
863  const QString p = icon.getFileResourcePath();
864 
865  return QStringLiteral("<img src=\"%1\" height=%2>").arg(p, QString::number(height));
866  }
867 
868  QDialog *CGuiUtility::findParentDialog(QWidget *widget)
869  {
870  if (CGuiUtility::isDialog(widget)) { return qobject_cast<QDialog *>(widget); }
871  while (widget->parent())
872  {
873  widget = widget->parentWidget();
874  if (CGuiUtility::isDialog(widget)) { return qobject_cast<QDialog *>(widget); }
875  }
876  return nullptr;
877  }
878 
879  QDialog *CGuiUtility::findParentDialog(QWidget *widget, int maxLevel)
880  {
881  if (CGuiUtility::isDialog(widget)) { return qobject_cast<QDialog *>(widget); }
882  int level = 0;
883  while (widget->parent())
884  {
885  level++;
886  if (level > maxLevel) { return nullptr; }
887  widget = widget->parentWidget();
888  if (CGuiUtility::isDialog(widget)) { return qobject_cast<QDialog *>(widget); }
889  }
890  return nullptr;
891  }
892 
893  void CGuiUtility::setElidedText(QLabel *label, const QString &text, Qt::TextElideMode mode)
894  {
895  if (!label) { return; }
896 
897  label->setToolTip(text);
898  if (mode == Qt::ElideNone)
899  {
900  label->setText(text);
901  return;
902  }
903 
904  const QFontMetrics metrics(label->font());
905  const int width = qMax(label->width() - 2, 0);
906  const QString clippedText = metrics.elidedText(text, mode, width);
907  label->setText(clippedText);
908  }
909 
910  void CGuiUtility::setElidedText(QLabel *label, const QString &shortText, const QString &longText,
911  Qt::TextElideMode mode)
912  {
913  if (!label) { return; }
914  if (shortText.isEmpty())
915  {
916  CGuiUtility::setElidedText(label, longText, mode);
917  return;
918  }
919  if (longText.isEmpty())
920  {
921  CGuiUtility::setElidedText(label, shortText, mode);
922  return;
923  }
924 
925  label->setToolTip(longText);
926  const QFontMetrics metrics(label->font());
927  const int width = qMax(label->width() - 2, 0);
928  const int wl = metrics.horizontalAdvance(longText);
929  if (wl >= width)
930  {
931  label->setText(longText);
932  return;
933  }
934  if (qRound(wl * 0.85) > wl)
935  {
936  const QString clippedText = metrics.elidedText(longText, mode, width);
937  label->setText(clippedText);
938  return;
939  }
940  const QString clippedText = metrics.elidedText(shortText, mode, width);
941  label->setText(clippedText);
942  }
943 
944  void CGuiUtility::setWizardButtonWidths(QWizard *wizard)
945  {
946  if (!wizard) { return; }
947 
948  const int minW = qMax(qRound(CGuiUtility::fontMetricsLazyDog43Chars(true).width() * 6.0 / 43.0), 80);
949  if (wizard->button(QWizard::BackButton)) { wizard->button(QWizard::BackButton)->setMinimumWidth(minW); }
950  if (wizard->button(QWizard::NextButton)) { wizard->button(QWizard::NextButton)->setMinimumWidth(minW); }
951  if (wizard->button(QWizard::CancelButton)) { wizard->button(QWizard::CancelButton)->setMinimumWidth(minW); }
952  if (wizard->button(QWizard::FinishButton)) { wizard->button(QWizard::FinishButton)->setMinimumWidth(minW); }
953  if (wizard->button(QWizard::CustomButton1)) { wizard->button(QWizard::CustomButton1)->setMinimumWidth(minW); }
954  if (wizard->button(QWizard::CustomButton2)) { wizard->button(QWizard::CustomButton2)->setMinimumWidth(minW); }
955  if (wizard->button(QWizard::CustomButton3)) { wizard->button(QWizard::CustomButton3)->setMinimumWidth(minW); }
956  }
957 
958  QWidgetList CGuiUtility::getAllModallWidgets()
959  {
960  const QWidgetList widgets = QApplication::topLevelWidgets();
961  QWidgetList openWidgets;
962  for (QWidget *w : widgets)
963  {
964  if (w->isModal()) { openWidgets.push_back(w); }
965  }
966  return openWidgets;
967  }
968 
969  QStringList CGuiUtility::getAllWidgetTitles(const QWidgetList &widgets)
970  {
971  QStringList titles;
972  for (const QWidget *w : widgets)
973  {
974  if (!w) { continue; }
975  if (!w->windowTitle().isEmpty()) { titles.push_back(w->windowTitle()); }
976  else { titles.push_back(QStringLiteral("name: ") % w->objectName()); }
977  }
978  return titles;
979  }
980 
981  QStringList CGuiUtility::getAllWidgetNames(const QWidgetList &widgets)
982  {
983  QStringList titles;
984  for (const QWidget *w : widgets)
985  {
986  if (!w) { continue; }
987  titles.push_back(QStringLiteral("name: ") % w->objectName());
988  }
989  return titles;
990  }
991 
992  QList<QDockWidget *> CGuiUtility::getAllDockWidgets(QWidget *parent, bool floatingOnly)
993  {
994  QList<QDockWidget *> docks;
995  if (parent)
996  {
997  const auto children = parent->findChildren<QDockWidget *>();
998  for (QDockWidget *w : children)
999  {
1000  if (!w) { continue; }
1001  if (!floatingOnly || w->isFloating()) { docks.push_back(w); }
1002  }
1003  }
1004  return docks;
1005  }
1006 
1007  QList<QDockWidget *> CGuiUtility::getAllDockWidgets(QWindow *parent, bool floatingOnly)
1008  {
1009  QList<QDockWidget *> docks;
1010  if (parent)
1011  {
1012  const auto children = parent->findChildren<QDockWidget *>();
1013  for (QDockWidget *w : children)
1014  {
1015  if (!w) { continue; }
1016  if (!floatingOnly || w->isFloating()) { docks.push_back(w); }
1017  }
1018  }
1019  return docks;
1020  }
1021 
1022  QWidgetList CGuiUtility::closeAllModalWidgets()
1023  {
1024  QWidgetList modals = getAllModallWidgets();
1025  for (QWidget *w : modals)
1026  {
1027  if (!w) { continue; }
1028  w->close();
1029  }
1030  return modals;
1031  }
1032 
1033  QStringList CGuiUtility::closeAllModalWidgetsGetTitles()
1034  {
1035  const QWidgetList modals = getAllModallWidgets();
1036  QStringList titles;
1037  for (QWidget *w : modals)
1038  {
1039  if (!w) { continue; }
1040  titles << w->windowTitle();
1041  w->close();
1042  }
1043  return titles;
1044  }
1045 
1046  QList<QDockWidget *> CGuiUtility::closeAllDockWidgets(QWidget *parent, bool floatingOnly)
1047  {
1048  QList<QDockWidget *> dws = getAllDockWidgets(parent, floatingOnly);
1049  for (QWidget *w : dws)
1050  {
1051  if (!w) { continue; }
1052  w->close();
1053  }
1054  return dws;
1055  }
1056 
1057  QList<QDockWidget *> CGuiUtility::closeAllDockWidgets(QWindow *parent, bool floatingOnly)
1058  {
1059  QList<QDockWidget *> dws = getAllDockWidgets(parent, floatingOnly);
1060  for (QWidget *w : dws)
1061  {
1062  if (!w) { continue; }
1063  w->close();
1064  }
1065  return dws;
1066  }
1067 
1068  QStringList CGuiUtility::closeAllDockWidgetsGetTitles(QWidget *parent, bool floatingOnly)
1069  {
1070  const QList<QDockWidget *> dws = getAllDockWidgets(parent, floatingOnly);
1071  QStringList titles;
1072  for (QWidget *w : dws)
1073  {
1074  if (!w) { continue; }
1075  titles << w->windowTitle();
1076  w->close();
1077  }
1078  return titles;
1079  }
1080 
1081  QStringList CGuiUtility::closeAllDockWidgetsGetTitles(QWindow *parent, bool floatingOnly)
1082  {
1083  const QList<QDockWidget *> dws = getAllDockWidgets(parent, floatingOnly);
1084  QStringList titles;
1085  for (QWidget *w : dws)
1086  {
1087  if (!w) { continue; }
1088  titles << w->windowTitle();
1089  w->close();
1090  }
1091  return titles;
1092  }
1093 
1094  QStringList CGuiUtility::deleteLaterAllDockWidgetsGetTitles(QWidget *parent, bool floatingOnly)
1095  {
1096  const QList<QDockWidget *> dws = getAllDockWidgets(parent, floatingOnly);
1097  QStringList titles;
1098  for (QWidget *w : dws)
1099  {
1100  if (!w) { continue; }
1101  titles << w->windowTitle();
1102  w->deleteLater(); // DANGEROUS
1103  }
1104  return titles;
1105  }
1106 
1107 } // 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.
SWIFT_MISC_EXPORT bool stringToBool(const QString &boolString)
Convert string to bool.
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
#define SWIFT_VERIFY_X(COND, WHERE, WHAT)
A weaker kind of assert.
Definition: verify.h:26