#include "./traymenu.h" #include "./trayicon.h" #include "./traywidget.h" #include #include #include #include #include #include #include #ifdef TRAY_MENU_HANDLE_WINDOWS11_STYLE #include #endif #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) #define QT_SUPPORTS_SYSTEM_WINDOW_COMMANDS #endif using namespace QtUtilities; namespace QtGui { static constexpr auto border = 10; #ifdef TRAY_MENU_HANDLE_WINDOWS11_STYLE static bool isWindows11Style(const QWidget *widget) { const auto *const s = widget->style(); return s && s->name().compare(QLatin1String("windows11"), Qt::CaseInsensitive) == 0; } #endif TrayMenu::TrayMenu(TrayIcon *trayIcon, QWidget *parent) : QMenu(parent) , m_layout(new QHBoxLayout) , m_trayIcon(trayIcon) , m_windowType(WindowType::Popup) , m_startedSystemWindowCommand(false) #ifdef TRAY_MENU_HANDLE_WINDOWS11_STYLE , m_isWindows11Style(isWindows11Style(this)) #endif { setObjectName(QStringLiteral("QtGui::TrayMenu")); setLayout(m_layout); updateContentMargins(); m_layout->setSpacing(0); m_layout->addWidget(m_trayWidget = new TrayWidget(this)); setPlatformMenu(nullptr); setWindowFlags(Qt::FramelessWindowHint | Qt::Popup); setWindowIcon(m_trayWidget->windowIcon()); } QSize TrayMenu::sizeHint() const { return Settings::values().appearance.trayMenuSize; } /*! * \brief Moves the specified \a innerRect at the specified \a point into the specified \a outerRect * by altering \a point. */ static void moveInside(QPoint &point, const QSize &innerRect, const QRect &outerRect) { if (point.y() < outerRect.top()) { point.setY(outerRect.top()); } else if (point.y() + innerRect.height() > outerRect.bottom()) { point.setY(outerRect.bottom() - innerRect.height()); } if (point.x() < outerRect.left()) { point.setX(outerRect.left()); } else if (point.x() + innerRect.width() > outerRect.right()) { point.setX(outerRect.right() - innerRect.width()); } } void TrayMenu::showUsingPositioningSettings() { if (m_windowType == WindowType::None) { widget().showWebUI(); return; } resize(sizeHint()); auto pos = Settings::values().appearance.positioning.positionToUse(); if (pos.has_value()) { moveInside(pos.value(), size(), availableScreenGeometryAtPoint(pos.value())); popup(pos.value()); } else { show(); } activateWindow(); } bool TrayMenu::event(QEvent *event) { #ifdef TRAY_MENU_HANDLE_WINDOWS11_STYLE switch (event->type()) { case QEvent::StyleChange: m_isWindows11Style = isWindows11Style(this); updateContentMargins(); break; case QEvent::PolishRequest: case QEvent::Polish: if (m_windowType != TrayMenu::WindowType::Popup && m_isWindows11Style) { // avoid polishing via the Windows 11 style as it would break behavior if we don't actually show this as popup event->accept(); return true; } break; default:; } #endif return QMenu::event(event); } void TrayMenu::setWindowType(int windowType) { if (windowType >= 0 && windowType <= 3) { setWindowType(static_cast(windowType)); } } void TrayMenu::setWindowType(WindowType windowType) { if (m_windowType == windowType) { return; } auto flags = Qt::WindowFlags(); switch (m_windowType = windowType) { case WindowType::Popup: flags = Qt::FramelessWindowHint | Qt::Popup; break; case WindowType::NormalWindow: flags = Qt::Window; break; case WindowType::CustomWindow: flags = Qt::Dialog | Qt::CustomizeWindowHint; break; case WindowType::None: break; } setWindowFlags(flags); #ifdef TRAY_MENU_HANDLE_WINDOWS11_STYLE // ensure correct margins and polishing when using Windows 11 style if (m_isWindows11Style) { updateContentMargins(); if (windowType == WindowType::Popup) { if (auto *const s = style()) { s->polish(this); } } } #endif } void TrayMenu::mousePressEvent(QMouseEvent *event) { // skip any special behavior if the tray menu is shown as a regular window if (m_windowType == TrayMenu::WindowType::NormalWindow) { return; } // try starting a system window resize/move to allow resizing/moving the borderless window #ifdef QT_SUPPORTS_SYSTEM_WINDOW_COMMANDS if (auto *const window = this->windowHandle()) { // keep default behavior on X11 for popups as the start functions don't work there (even though they return true) #if defined(Q_OS_UNIX) && !(defined(Q_OS_ANDROID) || defined(Q_OS_DARWIN)) if (m_windowType == TrayMenu::WindowType::Popup) { static const auto platform = QGuiApplication::platformName(); if (!platform.compare(QLatin1String("xcb"), Qt::CaseInsensitive)) { QMenu::mousePressEvent(event); return; } } #endif // check relevant edges and start the appropriate system resize/move const auto pos = event->pos(); auto edges = Qt::Edges(); if (pos.x() < border) edges |= Qt::LeftEdge; if (pos.x() >= width() - border) edges |= Qt::RightEdge; if (pos.y() < border) edges |= Qt::TopEdge; if (pos.y() >= height() - border) edges |= Qt::BottomEdge; m_startedSystemWindowCommand = edges ? window->startSystemResize(edges) : window->startSystemMove(); } #endif // fallback to the default behavior for the current window type if system window resize/move is not possible if (!m_startedSystemWindowCommand) { QMenu::mousePressEvent(event); } } void TrayMenu::mouseReleaseEvent(QMouseEvent *event) { // cover cases analogous to TrayMenu::mousePressEvent() if (m_windowType == TrayMenu::WindowType::NormalWindow) { return; } if (m_startedSystemWindowCommand) { m_startedSystemWindowCommand = false; } else { QMenu::mouseReleaseEvent(event); } } void TrayMenu::moveEvent(QMoveEvent *event) { auto &settings = Settings::values().appearance.positioning; if (settings.useAssumedIconPosition) { Settings::values().appearance.positioning.assumedIconPosition = event->pos(); emit positioningSettingsChanged(); } } void TrayMenu::resizeEvent(QResizeEvent *event) { Settings::values().appearance.trayMenuSize = event->size(); emit positioningSettingsChanged(); } void TrayMenu::paintEvent(QPaintEvent *event) { if (m_windowType == WindowType::Popup) { QMenu::paintEvent(event); } else { #ifdef TRAY_MENU_HANDLE_WINDOWS11_STYLE const auto p = m_windowType != TrayMenu::WindowType::Popup && m_isWindows11Style ? QGuiApplication::palette() : QPalette(palette()); #else const auto &p = palette(); #endif QPainter(this).fillRect(event->rect(), p.color(backgroundRole())); QWidget::paintEvent(event); } } void TrayMenu::focusOutEvent(QFocusEvent *) { if (m_windowType == WindowType::CustomWindow) { if (const auto *fw = focusWidget(); fw->hasFocus()) { return; } close(); } } void TrayMenu::updateContentMargins() { #ifdef TRAY_MENU_HANDLE_WINDOWS11_STYLE // set higher margins to account for the shadow effects of the Windows 11 style // note: Not sure whether there's a way to determine the required margins dynamically. if (m_windowType == TrayMenu::WindowType::Popup && m_isWindows11Style) { m_layout->setContentsMargins(2, 2, 10, 2); return; } #endif m_layout->setContentsMargins(0, 0, 0, 0); } } // namespace QtGui