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  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  {
689  window->setWindowFlag(Qt::CustomizeWindowHint);
690  window->setWindowFlag(Qt::WindowTitleHint);
691  }
692  window->setWindowFlag(Qt::WindowMinMaxButtonsHint, false);
693  window->setWindowFlag(Qt::WindowCloseButtonHint, false);
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);
707  a->setEasingCurve(QEasingCurve::InBack);
708  a->start(QPropertyAnimation::DeleteWhenStopped);
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);
722  a->setEasingCurve(QEasingCurve::OutBack);
723  a->start(QPropertyAnimation::DeleteWhenStopped);
724  return effect;
725  }
726 
727  QFontMetrics CGuiUtility::currentFontMetrics()
728  {
729  const QWidget *w = CGuiUtility::mainApplicationWidget();
730  if (w) { return w->fontMetrics(); }
731  return QApplication::fontMetrics();
732  }
733 
734  QFontMetricsF CGuiUtility::currentFontMetricsF() { return QFontMetricsF(CGuiUtility::currentFontMetrics()); }
735 
736  QFont CGuiUtility::currentFont()
737  {
738  const QWidget *w = CGuiUtility::mainApplicationWidget();
739  if (w) { return w->font(); }
740  return QApplication::font();
741  }
742 
743  QSizeF CGuiUtility::fontMetrics80Chars(bool withRatio)
744  {
745  // scale is 3.0 on my hires display
746  static const QString s("01234567890123456789012345678901234567890123456789012345678901234567890123456789");
747  const QFontMetricsF fm = CGuiUtility::currentFontMetricsF();
748  const qreal scale = withRatio ? CGuiUtility::mainApplicationWidgetPixelRatio() : 1.0;
749  const QSizeF size = fm.size(Qt::TextSingleLine, s);
750  return size * scale;
751  }
752 
753  QSizeF CGuiUtility::fontMetricsLazyDog43Chars(bool withRatio)
754  {
755  // 43 characters 0123456789012345678901234567890123456789012
756  static const QString s("The quick brown fox jumps over the lazy dog");
757  const QFontMetricsF fm = CGuiUtility::currentFontMetrics();
758  const qreal scale = withRatio ? CGuiUtility::mainApplicationWidgetPixelRatio() : 1.0;
759  const QSizeF size = fm.size(Qt::TextSingleLine, s);
760  return size * scale;
761  }
762 
763  QSizeF CGuiUtility::fontMetricsEstimateSize(int xCharacters, int yCharacters, bool withRatio)
764  {
765  // 1920/1080: 560/16 256/16 => 530/960
766  // 3840/2160: 400/10 178/10 => 375/600
767  // with ratio we get the physical solution, otherwise logical solution
768  const QSizeF s1 = CGuiUtility::fontMetrics80Chars(withRatio);
769  const QSizeF s2 = CGuiUtility::fontMetricsLazyDog43Chars(withRatio);
770  const QSizeF s = s1 + s2;
771  const qreal w = s.width() * xCharacters / 123; // 123 chars
772  const qreal h = s.height() * yCharacters / 2; // 2 lines
773  return QSizeF(w, h);
774  }
775 
776  void CGuiUtility::centerWidget(QWidget *widget)
777  {
778  const QPoint point(widget->width() / 2.0, 0);
779  const QScreen *pScreen = QGuiApplication::screenAt(widget->mapToGlobal(point));
780  const QRect screenGeometry = pScreen->availableGeometry();
781  const int x = (screenGeometry.width() - widget->width()) / 2;
782  const int y = (screenGeometry.height() - widget->height()) / 2;
783  widget->move(x, y);
784  }
785 
786  void CGuiUtility::centerWidget(QWidget *widget, QWidget *host)
787  {
788  if (!host) { host = widget->parentWidget(); }
789 
790  if (host)
791  {
792  const QRect hostRect = host->geometry();
793  widget->move(hostRect.center() - widget->rect().center());
794  }
795  else { CGuiUtility::centerWidget(widget); }
796  }
797 
798  QString CGuiUtility::metricsInfo()
799  {
800  static const QString s("%1 %2 %3 | 80 chars: w%4 h%5 | 43 chars: w%6 h%7");
801  const QSizeF s80 = CGuiUtility::fontMetrics80Chars();
802  const QSizeF s43 = CGuiUtility::fontMetricsLazyDog43Chars();
803 
804  QString ratio("-");
805  QString desktop("-");
806 
807  const QWidget *mainWidget = CGuiUtility::mainApplicationWidget();
808  if (mainWidget)
809  {
810  const QSize sd = QGuiApplication::primaryScreen()->geometry().size();
811  desktop = QStringLiteral("Desktop w%1 w%2").arg(sd.width()).arg(sd.height());
812  ratio = QStringLiteral("ratio: %1").arg(mainWidget->devicePixelRatioF());
813  }
814  return s.arg(desktop)
815  .arg(CGuiUtility::isUsingHighDpiScreenSupport() ? "hi DPI" : "-")
816  .arg(ratio)
817  .arg(s80.width())
818  .arg(s80.height())
819  .arg(s43.width())
820  .arg(s43.height());
821  }
822 
823  bool CGuiUtility::isUsingHighDpiScreenSupport()
824  {
825  const QByteArray v = qgetenv("QT_AUTO_SCREEN_SCALE_FACTOR");
826  const QString vs(v);
827  const bool highDpi = stringToBool(vs);
828  return highDpi;
829  }
830 
831  void CGuiUtility::forceStyleSheetUpdate(QWidget *widget)
832  {
833  if (!widget) { return; }
834  widget->setStyleSheet(widget->styleSheet());
835  }
836 
837  void CGuiUtility::superviseMainWindowMinSizes(qreal wRatio, qreal hRatio)
838  {
839  QWidget *w = CGuiUtility::mainApplicationWidget();
840  if (!w) { return; }
841  const QSize s = CGuiUtility::desktopSize();
842  const int minW = qRound(wRatio * s.width());
843  const int minH = qRound(hRatio * s.height());
844  w->setMinimumWidth(qMin(minW, w->minimumWidth()));
845  w->setMinimumHeight(qMin(minH, w->minimumHeight()));
846  }
847 
848  QString CGuiUtility::asSimpleHtmlImageWidth(const CIcon &icon, int width)
849  {
850  if (!icon.hasFileResourcePath()) return {};
851  const QString p = icon.getFileResourcePath();
852 
853  if (width < 0) { return QStringLiteral("<img src=\"%1\">").arg(p); }
854  return QStringLiteral("<img src=\"%1\" width=%2>").arg(p, QString::number(width));
855  }
856 
857  QString CGuiUtility::asSimpleHtmlImageHeight(const CIcon &icon, int height)
858  {
859  if (height < 0) { return CGuiUtility::asSimpleHtmlImageWidth(icon); }
860  if (!icon.hasFileResourcePath()) return {};
861  const QString p = icon.getFileResourcePath();
862 
863  return QStringLiteral("<img src=\"%1\" height=%2>").arg(p, QString::number(height));
864  }
865 
866  QDialog *CGuiUtility::findParentDialog(QWidget *widget)
867  {
868  if (CGuiUtility::isDialog(widget)) { return qobject_cast<QDialog *>(widget); }
869  while (widget->parent())
870  {
871  widget = widget->parentWidget();
872  if (CGuiUtility::isDialog(widget)) { return qobject_cast<QDialog *>(widget); }
873  }
874  return nullptr;
875  }
876 
877  QDialog *CGuiUtility::findParentDialog(QWidget *widget, int maxLevel)
878  {
879  if (CGuiUtility::isDialog(widget)) { return qobject_cast<QDialog *>(widget); }
880  int level = 0;
881  while (widget->parent())
882  {
883  level++;
884  if (level > maxLevel) { return nullptr; }
885  widget = widget->parentWidget();
886  if (CGuiUtility::isDialog(widget)) { return qobject_cast<QDialog *>(widget); }
887  }
888  return nullptr;
889  }
890 
891  void CGuiUtility::setElidedText(QLabel *label, const QString &text, Qt::TextElideMode mode)
892  {
893  if (!label) { return; }
894 
895  label->setToolTip(text);
896  if (mode == Qt::ElideNone)
897  {
898  label->setText(text);
899  return;
900  }
901 
902  const QFontMetrics metrics(label->font());
903  const int width = qMax(label->width() - 2, 0);
904  const QString clippedText = metrics.elidedText(text, mode, width);
905  label->setText(clippedText);
906  }
907 
908  void CGuiUtility::setElidedText(QLabel *label, const QString &shortText, const QString &longText,
909  Qt::TextElideMode mode)
910  {
911  if (!label) { return; }
912  if (shortText.isEmpty())
913  {
914  CGuiUtility::setElidedText(label, longText, mode);
915  return;
916  }
917  if (longText.isEmpty())
918  {
919  CGuiUtility::setElidedText(label, shortText, mode);
920  return;
921  }
922 
923  label->setToolTip(longText);
924  const QFontMetrics metrics(label->font());
925  const int width = qMax(label->width() - 2, 0);
926  const int wl = metrics.horizontalAdvance(longText);
927  if (wl >= width)
928  {
929  label->setText(longText);
930  return;
931  }
932  if (qRound(wl * 0.85) > wl)
933  {
934  const QString clippedText = metrics.elidedText(longText, mode, width);
935  label->setText(clippedText);
936  return;
937  }
938  const QString clippedText = metrics.elidedText(shortText, mode, width);
939  label->setText(clippedText);
940  }
941 
942  void CGuiUtility::setWizardButtonWidths(QWizard *wizard)
943  {
944  if (!wizard) { return; }
945 
946  const int minW = qMax(qRound(CGuiUtility::fontMetricsLazyDog43Chars(true).width() * 6.0 / 43.0), 80);
947  if (wizard->button(QWizard::BackButton)) { wizard->button(QWizard::BackButton)->setMinimumWidth(minW); }
948  if (wizard->button(QWizard::NextButton)) { wizard->button(QWizard::NextButton)->setMinimumWidth(minW); }
949  if (wizard->button(QWizard::CancelButton)) { wizard->button(QWizard::CancelButton)->setMinimumWidth(minW); }
950  if (wizard->button(QWizard::FinishButton)) { wizard->button(QWizard::FinishButton)->setMinimumWidth(minW); }
951  if (wizard->button(QWizard::CustomButton1)) { wizard->button(QWizard::CustomButton1)->setMinimumWidth(minW); }
952  if (wizard->button(QWizard::CustomButton2)) { wizard->button(QWizard::CustomButton2)->setMinimumWidth(minW); }
953  if (wizard->button(QWizard::CustomButton3)) { wizard->button(QWizard::CustomButton3)->setMinimumWidth(minW); }
954  }
955 
956  QWidgetList CGuiUtility::getAllModallWidgets()
957  {
958  const QWidgetList widgets = QApplication::topLevelWidgets();
959  QWidgetList openWidgets;
960  for (QWidget *w : widgets)
961  {
962  if (w->isModal()) { openWidgets.push_back(w); }
963  }
964  return openWidgets;
965  }
966 
967  QStringList CGuiUtility::getAllWidgetTitles(const QWidgetList &widgets)
968  {
969  QStringList titles;
970  for (const QWidget *w : widgets)
971  {
972  if (!w) { continue; }
973  if (!w->windowTitle().isEmpty()) { titles.push_back(w->windowTitle()); }
974  else { titles.push_back(QStringLiteral("name: ") % w->objectName()); }
975  }
976  return titles;
977  }
978 
979  QStringList CGuiUtility::getAllWidgetNames(const QWidgetList &widgets)
980  {
981  QStringList titles;
982  for (const QWidget *w : widgets)
983  {
984  if (!w) { continue; }
985  titles.push_back(QStringLiteral("name: ") % w->objectName());
986  }
987  return titles;
988  }
989 
990  QList<QDockWidget *> CGuiUtility::getAllDockWidgets(QWidget *parent, bool floatingOnly)
991  {
992  QList<QDockWidget *> docks;
993  if (parent)
994  {
995  const auto children = parent->findChildren<QDockWidget *>();
996  for (QDockWidget *w : children)
997  {
998  if (!w) { continue; }
999  if (!floatingOnly || w->isFloating()) { docks.push_back(w); }
1000  }
1001  }
1002  return docks;
1003  }
1004 
1005  QList<QDockWidget *> CGuiUtility::getAllDockWidgets(QWindow *parent, bool floatingOnly)
1006  {
1007  QList<QDockWidget *> docks;
1008  if (parent)
1009  {
1010  const auto children = parent->findChildren<QDockWidget *>();
1011  for (QDockWidget *w : children)
1012  {
1013  if (!w) { continue; }
1014  if (!floatingOnly || w->isFloating()) { docks.push_back(w); }
1015  }
1016  }
1017  return docks;
1018  }
1019 
1020  QWidgetList CGuiUtility::closeAllModalWidgets()
1021  {
1022  QWidgetList modals = getAllModallWidgets();
1023  for (QWidget *w : modals)
1024  {
1025  if (!w) { continue; }
1026  w->close();
1027  }
1028  return modals;
1029  }
1030 
1031  QStringList CGuiUtility::closeAllModalWidgetsGetTitles()
1032  {
1033  const QWidgetList modals = getAllModallWidgets();
1034  QStringList titles;
1035  for (QWidget *w : modals)
1036  {
1037  if (!w) { continue; }
1038  titles << w->windowTitle();
1039  w->close();
1040  }
1041  return titles;
1042  }
1043 
1044  QList<QDockWidget *> CGuiUtility::closeAllDockWidgets(QWidget *parent, bool floatingOnly)
1045  {
1046  QList<QDockWidget *> dws = getAllDockWidgets(parent, floatingOnly);
1047  for (QWidget *w : dws)
1048  {
1049  if (!w) { continue; }
1050  w->close();
1051  }
1052  return dws;
1053  }
1054 
1055  QList<QDockWidget *> CGuiUtility::closeAllDockWidgets(QWindow *parent, bool floatingOnly)
1056  {
1057  QList<QDockWidget *> dws = getAllDockWidgets(parent, floatingOnly);
1058  for (QWidget *w : dws)
1059  {
1060  if (!w) { continue; }
1061  w->close();
1062  }
1063  return dws;
1064  }
1065 
1066  QStringList CGuiUtility::closeAllDockWidgetsGetTitles(QWidget *parent, bool floatingOnly)
1067  {
1068  const QList<QDockWidget *> dws = getAllDockWidgets(parent, floatingOnly);
1069  QStringList titles;
1070  for (QWidget *w : dws)
1071  {
1072  if (!w) { continue; }
1073  titles << w->windowTitle();
1074  w->close();
1075  }
1076  return titles;
1077  }
1078 
1079  QStringList CGuiUtility::closeAllDockWidgetsGetTitles(QWindow *parent, bool floatingOnly)
1080  {
1081  const QList<QDockWidget *> dws = getAllDockWidgets(parent, floatingOnly);
1082  QStringList titles;
1083  for (QWidget *w : dws)
1084  {
1085  if (!w) { continue; }
1086  titles << w->windowTitle();
1087  w->close();
1088  }
1089  return titles;
1090  }
1091 
1092  QStringList CGuiUtility::deleteLaterAllDockWidgetsGetTitles(QWidget *parent, bool floatingOnly)
1093  {
1094  const QList<QDockWidget *> dws = getAllDockWidgets(parent, floatingOnly);
1095  QStringList titles;
1096  for (QWidget *w : dws)
1097  {
1098  if (!w) { continue; }
1099  titles << w->windowTitle();
1100  w->deleteLater(); // DANGEROUS
1101  }
1102  return titles;
1103  }
1104 
1105 } // 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