| | |
| | | // Copyright (C) 2023-2024 Stdware Collections (https://www.github.com/stdware) |
| | | // Copyright (C) 2021-2023 wangwenx190 (Yuhang Zhao) |
| | | // SPDX-License-Identifier: Apache-2.0 |
| | | |
| | | #include "abstractwindowcontext_p.h" |
| | | |
| | | #include <QtGui/QPen> |
| | | #include <QtGui/QPainter> |
| | | #include <QtGui/QScreen> |
| | | #include <memory> |
| | | |
| | | #include "qwkglobal_p.h" |
| | | |
| | | namespace QWK { |
| | | |
| | | AbstractWindowContext::AbstractWindowContext() { |
| | | namespace { |
| | | |
| | | class WinIdChangeEventFilter : public QObject { |
| | | public: |
| | | explicit WinIdChangeEventFilter(QObject *widget, AbstractWindowContext *ctx, |
| | | QObject *parent = nullptr) |
| | | : QObject(parent), ctx(ctx) { |
| | | widget->installEventFilter(this); |
| | | } |
| | | |
| | | protected: |
| | | bool eventFilter(QObject *obj, QEvent *event) override { |
| | | Q_UNUSED(obj) |
| | | if (event->type() == QEvent::WinIdChange) { |
| | | ctx->notifyWinIdChange(); |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | protected: |
| | | AbstractWindowContext *ctx; |
| | | }; |
| | | |
| | | class WindowEventFilter : public QObject { |
| | | public: |
| | | explicit WindowEventFilter(QWindow *window, AbstractWindowContext *ctx, |
| | | QObject *parent = nullptr) |
| | | : QObject(parent), ctx(ctx), window(window) { |
| | | if (window) |
| | | window->installEventFilter(this); |
| | | } |
| | | |
| | | inline void setWindow(QWindow *win) { |
| | | if (auto oldWin = window.data(); oldWin) { |
| | | oldWin->removeEventFilter(this); |
| | | } |
| | | window = win; |
| | | if (win) { |
| | | win->installEventFilter(this); |
| | | } |
| | | } |
| | | |
| | | protected: |
| | | bool eventFilter(QObject *obj, QEvent *event) override { |
| | | return ctx->sharedDispatch(obj, event); |
| | | } |
| | | |
| | | protected: |
| | | AbstractWindowContext *ctx; |
| | | QPointer<QWindow> window; |
| | | }; |
| | | |
| | | } |
| | | |
| | | AbstractWindowContext::AbstractWindowContext() = default; |
| | | |
| | | AbstractWindowContext::~AbstractWindowContext() = default; |
| | | |
| | | bool AbstractWindowContext::setup(QObject *host, WindowItemDelegate *delegate) { |
| | | if (!host || !delegate) { |
| | | return false; |
| | | void AbstractWindowContext::setup(QObject *host, WindowItemDelegate *delegate) { |
| | | if (m_host || !host || !delegate) { |
| | | return; |
| | | } |
| | | |
| | | auto windowHandle = delegate->hostWindow(host); |
| | | if (!windowHandle) { |
| | | return false; |
| | | } |
| | | |
| | | m_host = host; |
| | | m_delegate.reset(delegate); |
| | | m_windowHandle = windowHandle; |
| | | m_winIdChangeEventFilter = std::make_unique<WinIdChangeEventFilter>(host, this); |
| | | m_windowEventFilter = std::make_unique<WindowEventFilter>(m_windowHandle, this); |
| | | |
| | | if (!setupHost()) { |
| | | m_host = nullptr; |
| | | m_delegate = nullptr; |
| | | m_windowHandle = nullptr; |
| | | return false; |
| | | m_windowHandle = m_delegate->hostWindow(m_host); |
| | | if (m_windowHandle) { |
| | | winIdChanged(); |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | |
| | | |
| | | bool AbstractWindowContext::setHitTestVisible(const QObject *obj, bool visible) { |
| | | Q_ASSERT(obj); |
| | |
| | | return true; |
| | | } |
| | | |
| | | bool AbstractWindowContext::setHitTestVisible(const QRect &rect, bool visible) { |
| | | Q_ASSERT(rect.isValid()); |
| | | if (!rect.isValid()) { |
| | | return false; |
| | | } |
| | | |
| | | if (visible) { |
| | | m_hitTestVisibleRects.append(rect); |
| | | } else { |
| | | m_hitTestVisibleRects.removeAll(rect); |
| | | } |
| | | hitTestVisibleShapeDirty = true; |
| | | return true; |
| | | } |
| | | |
| | | bool AbstractWindowContext::setSystemButton(WindowAgentBase::SystemButton button, |
| | | QObject *obj) { |
| | | Q_ASSERT(obj); |
| | | Q_ASSERT(button != WindowAgentBase::Unknown); |
| | | if (!obj || (button == WindowAgentBase::Unknown)) { |
| | | if (button == WindowAgentBase::Unknown) { |
| | | return false; |
| | | } |
| | | |
| | |
| | | |
| | | bool AbstractWindowContext::setTitleBar(QObject *item) { |
| | | Q_ASSERT(item); |
| | | if (!item) { |
| | | return false; |
| | | } |
| | | |
| | | if (m_titleBar == item) { |
| | | return false; |
| | | } |
| | | |
| | | if (m_titleBar) { |
| | | // Since the title bar is changed, all items inside it should be dereferenced right away |
| | | for (auto &button : m_systemButtons) { |
| | | button = nullptr; |
| | | } |
| | | m_hitTestVisibleItems.clear(); |
| | | } |
| | | |
| | | m_titleBar = item; |
| | | return true; |
| | | } |
| | | |
| | | void AbstractWindowContext::showSystemMenu(const QPoint &pos) { |
| | | virtual_hook(ShowSystemMenuHook, &const_cast<QPoint &>(pos)); |
| | | #ifdef Q_OS_MAC |
| | | void AbstractWindowContext::setSystemButtonAreaCallback(const ScreenRectCallback &callback) { |
| | | m_systemButtonAreaCallback = callback; |
| | | virtual_hook(SystemButtonAreaChangedHook, nullptr); |
| | | } |
| | | |
| | | QRegion AbstractWindowContext::hitTestShape() const { |
| | | if (hitTestVisibleShapeDirty) { |
| | | hitTestVisibleShape = {}; |
| | | for (const auto &rect : m_hitTestVisibleRects) { |
| | | hitTestVisibleShape += rect; |
| | | } |
| | | hitTestVisibleShapeDirty = false; |
| | | } |
| | | return hitTestVisibleShape; |
| | | } |
| | | #endif |
| | | |
| | | bool AbstractWindowContext::isInSystemButtons(const QPoint &pos, |
| | | WindowAgentBase::SystemButton *button) const { |
| | |
| | | } |
| | | } |
| | | |
| | | if (!m_hitTestVisibleRects.isEmpty() && hitTestShape().contains(pos)) { |
| | | return false; |
| | | } |
| | | return true; |
| | | } |
| | | |
| | |
| | | return {}; |
| | | } |
| | | |
| | | static constexpr const struct { |
| | | const quint32 lilac = MAKE_RGBA_COLOR(210, 233, 189, 226); |
| | | const quint32 pink = MAKE_RGBA_COLOR(177, 205, 190, 240); |
| | | const quint32 tile = MAKE_RGBA_COLOR(193, 195, 211, 203); |
| | | const quint32 azure = MAKE_RGBA_COLOR(25, 90, 190, 255); |
| | | } kSampleColorSet{}; |
| | | QWK_USED static constexpr const struct { |
| | | const quint32 activeLight = MAKE_RGBA_COLOR(210, 233, 189, 226); |
| | | const quint32 activeDark = MAKE_RGBA_COLOR(177, 205, 190, 240); |
| | | const quint32 inactiveLight = MAKE_RGBA_COLOR(193, 195, 211, 203); |
| | | const quint32 inactiveDark = MAKE_RGBA_COLOR(240, 240, 250, 255); |
| | | } kSampleColorSet; |
| | | |
| | | void AbstractWindowContext::virtual_hook(int id, void *data) { |
| | | switch (id) { |
| | | case ShowSystemMenuHook: { |
| | | const auto &pos = *reinterpret_cast<const QPoint *>(data); |
| | | // ... |
| | | case CentralizeHook: { |
| | | if (!m_windowHandle) |
| | | return; |
| | | |
| | | QRect windowGeometry = m_delegate->getGeometry(m_host); |
| | | QRect screenGeometry = m_windowHandle->screen()->geometry(); |
| | | int x = (screenGeometry.width() - windowGeometry.width()) / 2; |
| | | int y = (screenGeometry.height() - windowGeometry.height()) / 2; |
| | | QPoint pos(x, y); |
| | | pos += screenGeometry.topLeft(); |
| | | m_delegate->setGeometry(m_host, QRect(pos, windowGeometry.size())); |
| | | return; |
| | | } |
| | | case NeedsDrawBordersHook: { |
| | | auto &result = *reinterpret_cast<bool *>(data); |
| | | result = false; |
| | | |
| | | case RaiseWindowHook: { |
| | | if (!m_windowHandle) |
| | | return; |
| | | |
| | | m_delegate->setWindowVisible(m_host, true); |
| | | Qt::WindowStates state = m_delegate->getWindowState(m_host); |
| | | if (state & Qt::WindowMinimized) { |
| | | m_delegate->setWindowState(m_host, state & ~Qt::WindowMinimized); |
| | | } |
| | | m_delegate->bringWindowToTop(m_host); |
| | | return; |
| | | } |
| | | case DrawBordersHook: { |
| | | auto args = reinterpret_cast<void **>(data); |
| | | auto &painter = *reinterpret_cast<QPainter *>(args[0]); |
| | | auto &rect = *reinterpret_cast<const QRect *>(args[1]); |
| | | |
| | | QPen pen{}; |
| | | pen.setWidth(1); |
| | | |
| | | // Top |
| | | pen.setColor(kSampleColorSet.lilac); |
| | | painter.setPen(pen); |
| | | painter.drawLine(rect.topLeft(), rect.topRight()); |
| | | |
| | | // Right |
| | | pen.setColor(kSampleColorSet.pink); |
| | | painter.setPen(pen); |
| | | painter.drawLine(rect.topRight(), rect.bottomRight()); |
| | | |
| | | // Bottom |
| | | pen.setColor(kSampleColorSet.tile); |
| | | painter.setPen(pen); |
| | | painter.drawLine(rect.bottomLeft(), rect.bottomRight()); |
| | | |
| | | // Right |
| | | pen.setColor(kSampleColorSet.azure); |
| | | painter.setPen(pen); |
| | | painter.drawLine(rect.topLeft(), rect.bottomLeft()); |
| | | case DefaultColorsHook: { |
| | | auto &map = *static_cast<QMap<QString, QColor> *>(data); |
| | | map.clear(); |
| | | map.insert(QStringLiteral("activeLight"), kSampleColorSet.activeLight); |
| | | map.insert(QStringLiteral("activeDark"), kSampleColorSet.activeDark); |
| | | map.insert(QStringLiteral("inactiveLight"), kSampleColorSet.inactiveLight); |
| | | map.insert(QStringLiteral("inactiveDark"), kSampleColorSet.inactiveDark); |
| | | return; |
| | | } |
| | | |
| | | default: |
| | | break; |
| | | } |
| | | } |
| | | |
| | | } |
| | | void AbstractWindowContext::showSystemMenu(const QPoint &pos) { |
| | | virtual_hook(ShowSystemMenuHook, &const_cast<QPoint &>(pos)); |
| | | } |
| | | |
| | | void AbstractWindowContext::notifyWinIdChange() { |
| | | auto windowEventFilter = static_cast<WindowEventFilter *>(m_windowEventFilter.get()); |
| | | windowEventFilter->setWindow(nullptr); |
| | | |
| | | // In Qt6, after QWidget::close() is called, the related QWindow's all surfaces and the |
| | | // platform window will be removed, and the WinId will be set to 0. After that, when the |
| | | // QWidget is shown again, the whole things will be recreated again. |
| | | // As a result, we must update our WindowContext each time the WinId changes. |
| | | m_windowHandle = m_delegate->hostWindow(m_host); |
| | | winIdChanged(); |
| | | |
| | | if (m_windowHandle) { |
| | | windowEventFilter->setWindow(m_windowHandle); |
| | | |
| | | // Refresh window attributes |
| | | auto attributes = m_windowAttributes; |
| | | m_windowAttributes.clear(); |
| | | for (auto it = attributes.begin(); it != attributes.end(); ++it) { |
| | | if (!windowAttributeChanged(it.key(), it.value(), {})) { |
| | | continue; |
| | | } |
| | | m_windowAttributes.insert(it.key(), it.value()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | QVariant AbstractWindowContext::windowAttribute(const QString &key) const { |
| | | return m_windowAttributes.value(key); |
| | | } |
| | | |
| | | bool AbstractWindowContext::setWindowAttribute(const QString &key, const QVariant &attribute) { |
| | | auto it = m_windowAttributes.find(key); |
| | | if (it == m_windowAttributes.end()) { |
| | | if (!attribute.isValid()) { |
| | | return true; |
| | | } |
| | | if (!m_windowHandle || !windowAttributeChanged(key, attribute, {})) { |
| | | return false; |
| | | } |
| | | m_windowAttributes.insert(key, attribute); |
| | | return true; |
| | | } |
| | | |
| | | if (it.value() == attribute) |
| | | return true; |
| | | if (!m_windowHandle || !windowAttributeChanged(key, attribute, it.value())) { |
| | | return false; |
| | | } |
| | | |
| | | if (attribute.isValid()) { |
| | | it.value() = attribute; |
| | | } else { |
| | | m_windowAttributes.erase(it); |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | bool AbstractWindowContext::windowAttributeChanged(const QString &key, |
| | | const QVariant &attribute, |
| | | const QVariant &oldAttribute) { |
| | | return false; |
| | | } |
| | | |
| | | } |