#include "widgetutils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace vnotex; void WidgetUtils::setPropertyDynamically(QWidget *p_widget, const char *p_prop, const QVariant &p_val) { p_widget->setProperty(p_prop, p_val); updateStyle(p_widget); } void WidgetUtils::updateStyle(QWidget *p_widget) { p_widget->style()->unpolish(p_widget); p_widget->style()->polish(p_widget); p_widget->update(); } qreal WidgetUtils::calculateScaleFactor(const QScreen *p_screen) { static qreal factor = -1; if (factor < 0 || p_screen) { auto screen = p_screen ? p_screen : QGuiApplication::primaryScreen(); factor = screen->devicePixelRatio(); qDebug() << screen->name() << "dpi" << factor; } return factor; } bool WidgetUtils::isScrollBarVisible(QAbstractScrollArea *p_widget, bool p_horizontal) { auto scrollBar = p_horizontal ? p_widget->horizontalScrollBar() : p_widget->verticalScrollBar(); if (scrollBar && scrollBar->isVisible() && scrollBar->minimum() != scrollBar->maximum()) { return true; } return false; } QSize WidgetUtils::availableScreenSize(QWidget *p_widget) { return p_widget->screen()->availableGeometry().size(); } void WidgetUtils::openUrlByDesktop(const QUrl &p_url) { const auto scheme = p_url.scheme(); if (scheme != "http" && scheme != "https" && !(p_url.isLocalFile() && QFileInfo(p_url.toLocalFile()).isDir())) { // Prompt for user. int ret = MessageBoxHelper::questionYesNo(MessageBoxHelper::Warning, MainWindow::tr("Are you sure to open link (%1)?").arg(p_url.toString()), MainWindow::tr("Malicious link might do harm to your device."), QString(), nullptr); if (ret == QMessageBox::No) { return; } } QDesktopServices::openUrl(p_url); } bool WidgetUtils::processKeyEventLikeVi(QWidget *p_widget, QKeyEvent *p_event, QWidget *p_escTargetWidget) { Q_ASSERT(p_widget); bool eventHandled = false; int key = p_event->key(); int modifiers = p_event->modifiers(); if (!p_escTargetWidget) { p_escTargetWidget = p_widget; } switch (key) { case Qt::Key_BracketLeft: { if (isViControlModifier(modifiers)) { auto escEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Escape, Qt::NoModifier); QCoreApplication::postEvent(p_escTargetWidget, escEvent); eventHandled = true; } break; } case Qt::Key_J: { if (isViControlModifier(modifiers)) { // The event must be allocated on the heap since the post event queue will take ownership // of the event and delete it once it has been posted. auto downEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Down, Qt::NoModifier); QCoreApplication::postEvent(p_widget, downEvent); eventHandled = true; } break; } case Qt::Key_K: { if (isViControlModifier(modifiers)) { auto upEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Up, Qt::NoModifier); QCoreApplication::postEvent(p_widget, upEvent); eventHandled = true; } break; } case Qt::Key_H: { if (isViControlModifier(modifiers)) { auto upEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Left, Qt::NoModifier); QCoreApplication::postEvent(p_widget, upEvent); eventHandled = true; } break; } case Qt::Key_L: { if (isViControlModifier(modifiers)) { auto upEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Right, Qt::NoModifier); QCoreApplication::postEvent(p_widget, upEvent); eventHandled = true; } break; } default: break; } if (eventHandled) { p_event->accept(); } return eventHandled; } bool WidgetUtils::isViControlModifier(int p_modifiers) { #if defined(Q_OS_MACOS) || defined(Q_OS_MAC) return p_modifiers == Qt::MetaModifier; #else return p_modifiers == Qt::ControlModifier; #endif } void WidgetUtils::clearActionGroup(QActionGroup *p_actGroup) { auto actions = p_actGroup->actions(); for (auto action : actions) { p_actGroup->removeAction(action); } } void WidgetUtils::addActionShortcut(QAction *p_action, const QString &p_shortcut, Qt::ShortcutContext p_context) { QKeySequence kseq(p_shortcut); if (kseq.isEmpty()) { return; } p_action->setShortcut(kseq); p_action->setShortcutContext(p_context); p_action->setText(QStringLiteral("%1\t%2").arg(p_action->text(), kseq.toString(QKeySequence::NativeText))); } void WidgetUtils::addActionShortcutText(QAction *p_action, const QString &p_shortcut) { if (p_shortcut.isEmpty()) { return; } QKeySequence kseq(p_shortcut); if (kseq.isEmpty()) { return; } p_action->setText(QStringLiteral("%1\t%2").arg(p_action->text(), kseq.toString(QKeySequence::NativeText))); } void WidgetUtils::addButtonShortcutText(QPushButton *p_button, const QString &p_shortcut) { if (p_shortcut.isEmpty()) { return; } QKeySequence kseq(p_shortcut); if (kseq.isEmpty()) { return; } p_button->setText(QStringLiteral("%1 (%2)").arg(p_button->text(), kseq.toString(QKeySequence::NativeText))); } void WidgetUtils::updateSize(QWidget *p_widget) { p_widget->adjustSize(); p_widget->updateGeometry(); } void WidgetUtils::resizeToHideScrollBarLater(QScrollArea *p_scroll, bool p_vertical, bool p_horizontal) { QTimer::singleShot(200, p_scroll, [p_scroll, p_vertical, p_horizontal]() { WidgetUtils::resizeToHideScrollBar(p_scroll, p_vertical, p_horizontal); }); } void WidgetUtils::resizeToHideScrollBar(QScrollArea *p_scroll, bool p_vertical, bool p_horizontal) { bool changed = false; auto parentWidget = p_scroll->parentWidget(); if (p_horizontal && WidgetUtils::isScrollBarVisible(p_scroll, true)) { auto scrollBar = p_scroll->horizontalScrollBar(); auto delta = scrollBar->maximum() - scrollBar->minimum(); auto availableSize = WidgetUtils::availableScreenSize(p_scroll); if (parentWidget) { int newWidth = parentWidget->width() + delta; if (newWidth <= availableSize.width()) { changed = true; p_scroll->resize(p_scroll->width() + delta, p_scroll->height()); auto geo = parentWidget->geometry(); parentWidget->setGeometry(geo.x() - delta / 2, geo.y(), newWidth, geo.height()); } } else { int newWidth = p_scroll->width() + delta; if (newWidth <= availableSize.width()) { changed = true; p_scroll->resize(newWidth, p_scroll->height()); } } } if (p_vertical && WidgetUtils::isScrollBarVisible(p_scroll, false)) { auto scrollBar = p_scroll->verticalScrollBar(); auto delta = scrollBar->maximum() - scrollBar->minimum(); auto availableSize = WidgetUtils::availableScreenSize(p_scroll); if (parentWidget) { int newHeight = parentWidget->height() + delta; if (newHeight <= availableSize.height()) { changed = true; p_scroll->resize(p_scroll->width(), p_scroll->height() + delta); auto geo = parentWidget->geometry(); parentWidget->setGeometry(geo.x(), geo.y() - delta / 2, geo.width(), newHeight); } } else { int newHeight = p_scroll->height() + delta; if (newHeight <= availableSize.height()) { changed = true; p_scroll->resize(p_scroll->width(), newHeight); } } } if (changed) { p_scroll->updateGeometry(); } } QShortcut *WidgetUtils::createShortcut(const QString &p_shortcut, QWidget *p_widget, Qt::ShortcutContext p_context) { QKeySequence kseq(p_shortcut); if (kseq.isEmpty()) { return nullptr; } auto shortcut = new QShortcut(kseq, p_widget, nullptr, nullptr, p_context); if (shortcut->key().isEmpty()) { delete shortcut; return nullptr; } return shortcut; } bool WidgetUtils::isMetaKey(int p_key) { return p_key == Qt::Key_Control || p_key == Qt::Key_Shift || p_key == Qt::Key_Meta #if defined(Q_OS_LINUX) // For mapping Caps as Ctrl in KDE. || p_key == Qt::Key_CapsLock #endif || p_key == Qt::Key_Alt; } QVector WidgetUtils::getVisibleIndexes(const QListView *p_view) { QVector indexes; auto firstItem = p_view->indexAt(QPoint(0, 0)); if (!firstItem.isValid()) { return indexes; } auto lastItem = p_view->indexAt(p_view->viewport()->rect().bottomLeft()); int firstRow = firstItem.row(); int lastRow = lastItem.isValid() ? lastItem.row() : (p_view->model()->rowCount() - 1); for (int i = firstRow; i <= lastRow; ++i) { if (p_view->isRowHidden(i)) { continue; } auto item = firstItem.siblingAtRow(i); if (item.isValid()) { indexes.append(item); } } return indexes; } QString WidgetUtils::getMonospaceFont() { static QString font; if (font.isNull()) { QStringList candidates; candidates << QStringLiteral("YaHei Consolas Hybrid") << QStringLiteral("Consolas") << QStringLiteral("Monaco") << QStringLiteral("Andale Mono") << QStringLiteral("Monospace") << QStringLiteral("Courier New"); auto availFamilies = QFontDatabase().families(); for (const auto &candidate : candidates) { QString family = candidate.trimmed().toLower(); for (auto availFamily : availFamilies) { availFamily.remove(QRegularExpression("\\[.*\\]")); if (family == availFamily.trimmed().toLower()) { font = availFamily; return font; } } } // Fallback to current font. font = QFont().family(); } return font; } QAction *WidgetUtils::findActionByObjectName(const QList &p_actions, const QString &p_objName) { for (auto act : p_actions) { if (act->objectName() == p_objName) { return act; } } return nullptr; } // Insert @p_action into @p_menu after action @p_after. void WidgetUtils::insertActionAfter(QMenu *p_menu, QAction *p_after, QAction *p_action) { p_menu->insertAction(p_after, p_action); if (p_after) { p_menu->removeAction(p_after); p_menu->insertAction(p_action, p_after); } } void WidgetUtils::selectBaseName(QLineEdit *p_lineEdit) { auto text = p_lineEdit->text(); int dotIndex = text.lastIndexOf(QLatin1Char('.')); p_lineEdit->setSelection(0, (dotIndex == -1) ? text.size() : dotIndex); } void WidgetUtils::setContentsMargins(QLayout *p_layout) { // Use 0 bottom margin to align dock widgets with the content area. p_layout->setContentsMargins(CONTENTS_MARGIN, CONTENTS_MARGIN, CONTENTS_MARGIN, 0); } bool WidgetUtils::distributeWidgetsOfSplitter(QSplitter *p_splitter) { if (!p_splitter) { return false; } if (p_splitter->count() == 0) { return false; } else if (p_splitter->count() == 1) { return true; } auto sizes = p_splitter->sizes(); int totalWidth = 0; for (auto sz : sizes) { totalWidth += sz; } int newWidth = totalWidth / sizes.size(); if (newWidth == 0) { return false; } bool changed = false; for (int i = 0; i < sizes.size(); ++i) { if (sizes[i] != newWidth) { sizes[i] = newWidth; changed = true; } } if (changed) { p_splitter->setSizes(sizes); return true; } return false; } void WidgetUtils::clearLayout(QFormLayout *p_layout) { for (int i = p_layout->rowCount() - 1; i >= 0; --i) { p_layout->removeRow(i); } } // Different from QWidget::isAncestorOf(): unnecessary to be within the same window. bool WidgetUtils::isOrAncestorOf(const QWidget *p_widget, const QWidget *p_child) { Q_ASSERT(p_widget); if (!p_child) { return false; } const QWidget *pa = p_child; while (pa) { if (pa == p_widget) { return true; } pa = pa->parentWidget(); } return false; }