From 8f73b75e79a7c5abc923e0314cb34b2132afb514 Mon Sep 17 00:00:00 2001 From: KingToolbox Date: Fri, 28 Aug 2020 15:06:48 +0800 Subject: [PATCH] A popup widget. --- src/README.md | 4 + src/Widgets/PopupWidget.cpp | 226 ++++++++++++++++++++++++++++++++++++ src/Widgets/PopupWidget.h | 47 ++++++++ 3 files changed, 277 insertions(+) create mode 100644 src/Widgets/PopupWidget.cpp create mode 100644 src/Widgets/PopupWidget.h diff --git a/src/README.md b/src/README.md index 5193974..8363eaf 100644 --- a/src/README.md +++ b/src/README.md @@ -36,6 +36,10 @@ A high-performance spin mutex and locker. A high-performance thread local storage. +## Widgets/PopupWidget.h/cpp + +A popup widget. + ## Widgets/Scrollbar.h/cpp A scrollbar supports 64-bit ranges. diff --git a/src/Widgets/PopupWidget.cpp b/src/Widgets/PopupWidget.cpp new file mode 100644 index 0000000..b01e1d7 --- /dev/null +++ b/src/Widgets/PopupWidget.cpp @@ -0,0 +1,226 @@ + /* + * Copyright 2020, WindTerm. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "PopupWidget.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +#define TRIANGLE_HEIGHT 18 + +PopupWidget::PopupWidget(QWidget *parent /*= nullptr*/) + : QFrame(parent) +{ + setWindowFlags(Qt::Window + | Qt::FramelessWindowHint + | Qt::WindowStaysOnTopHint + | Qt::X11BypassWindowManagerHint + | Qt::WindowDoesNotAcceptFocus); + + setAttribute(Qt::WA_ShowWithoutActivating, true); + setAttribute(Qt::WA_X11DoNotAcceptFocus, true); + setAttribute(Qt::WA_DeleteOnClose, true); + setFocusPolicy(Qt::NoFocus); + setFrameShape(QFrame::StyledPanel); + setMouseTracking(true); + createLayout(); +} + +PopupWidget::~PopupWidget() { + if (QAbstractButton *button = dynamic_cast(parent())) { + if (QToolButton *toolButton = dynamic_cast(parent())) { + if (QAction *action = toolButton->defaultAction()) { + action->setChecked(false); + } + } + button->setChecked(false); + } + // Kill any running effect + qFadeEffect(0); +} + +void PopupWidget::createLayout() { + QVBoxLayout *vBoxLayout = new QVBoxLayout; + vBoxLayout->setSpacing(0); + + setLayout(vBoxLayout); +} + +bool PopupWidget::eventFilter(QObject *watched, QEvent *event) { + switch (event->type()) { + case QEvent::FocusOut: + if (QWidget *widget = dynamic_cast(watched)) { + if (isAncestorOf(widget) == false && QApplication::activePopupWidget() == nullptr) { + bool hasMenuAncestor = false; + + do { + if (widget->inherits("QMenu")) { + hasMenuAncestor = true; + break; + } + } while (widget = widget->parentWidget()); + + if (hasMenuAncestor == false) { + close(); + } + } + } + break; + case QEvent::KeyPress: { + if (QKeyEvent *keyEvent = dynamic_cast(event)) { + QWidget *activePopupWidget = QApplication::activePopupWidget(); + + if (keyEvent->matches(QKeySequence::Cancel)) { + if (activePopupWidget) { + activePopupWidget->close(); + } else { + close(); + } + return true; + } else if (isAncestorOf(QApplication::focusWidget()) == false) { + if (activePopupWidget == nullptr) { + close(); + } + } + } + break; + } + case QEvent::NonClientAreaMouseButtonPress: + case QEvent::WindowStateChange: + close(); + break; + case QEvent::MouseButtonPress: { + if (QMouseEvent *mouseEvent = dynamic_cast(event)) { + do { + QPoint globalPos = mouseEvent->globalPos(); + + if (rect().contains(mapFromGlobal(globalPos))) + break; + + if (QAbstractButton *button = dynamic_cast(parentWidget())) { + if (button->rect().contains(button->mapFromGlobal(globalPos))) { + break; + } + } + setAttribute(Qt::WA_NoMouseReplay); + close(); + } while (0); + } + break; + } + case QEvent::WindowDeactivate: { + if (QWidget *parentWidget = this->parentWidget()) { + if (parentWidget->isAncestorOf(QApplication::focusWidget()) == false) { + close(); + } + } + break; + } + } + return false; +} + +void PopupWidget::mousePressEvent(QMouseEvent *event) { + setAttribute(Qt::WA_NoMouseReplay); + QWidget::mousePressEvent(event); +} + +void PopupWidget::setCentralWidget(QWidget *widget) { + widget->layout()->setContentsMargins(QMargins()); + widget->setParent(this); + layout()->addWidget(widget); + adjustSize(); +} + +void PopupWidget::show(Area hArea, Area vArea) { + Q_ASSERT(parentWidget() != nullptr); + + if (QWidget *parent = parentWidget()) { + QPoint newPos; + QPolygon triPolygon; + QRect rectPolygon; + + QPoint pos = parent->mapToGlobal(QPoint()); + QRect rect = parent->rect(); + QRect screenRect = QApplication::desktop()->availableGeometry(this); + + int topMargin = style()->pixelMetric(QStyle::PM_LayoutTopMargin); + int bottomMargin = style()->pixelMetric(QStyle::PM_LayoutBottomMargin); + int leftMargin = style()->pixelMetric(QStyle::PM_LayoutLeftMargin); + int rightMargin = style()->pixelMetric(QStyle::PM_LayoutRightMargin); + int triangleHeight = std::max(TRIANGLE_HEIGHT, topMargin); + + if (pos.y() < height()) { + if (vArea == TopArea) { + vArea = BottomArea; + } + } else { + if (vArea == BottomArea && screenRect.height() - pos.y() - rect.height() < height()) { + vArea = TopArea; + } + } + + if (hArea == MiddleArea) { + newPos.setX(qBound(0, pos.x() + (rect.width() - width()) / 2, screenRect.width() - width())); + } else { + if (hArea == LeftArea) { + if (pos.x() + rect.width() < width()) { + hArea = RightArea; + } + } else { + if (screenRect.width() - pos.x() < width()) { + hArea = LeftArea; + } + } + newPos.setX((hArea == LeftArea) ? pos.x() + rect.width() - width() : pos.x()); + } + newPos.setY((vArea == BottomArea) ? pos.y() + rect.height() : pos.y() - height() - triangleHeight); + + int xCenter = pos.x() + (rect.width() / 2) - newPos.x(); + + if (vArea == TopArea) { + triPolygon << QPoint(xCenter - triangleHeight, height()) + << QPoint(xCenter, height() + triangleHeight) + << QPoint(xCenter + triangleHeight, height()); + rectPolygon = QRect(0, 0, width(), height()); + layout()->setContentsMargins(leftMargin, topMargin, rightMargin, bottomMargin + triangleHeight * 2); + } else { + triPolygon << QPoint(xCenter - triangleHeight, triangleHeight) + << QPoint(xCenter, 0) + << QPoint(xCenter + triangleHeight, triangleHeight); + rectPolygon = QRect(0, triangleHeight, width(), height()); + layout()->setContentsMargins(leftMargin, topMargin + triangleHeight, rightMargin, bottomMargin); + } + + QRegion triangle(triPolygon); + QRegion rectangle(rectPolygon, QRegion::Rectangle); + QRegion mask = rectangle.united(triangle); + setMask(mask); + + move(newPos); + QWidget::show(); + + qFadeEffect(this, 200); + qApp->installEventFilter(this); + } +} \ No newline at end of file diff --git a/src/Widgets/PopupWidget.h b/src/Widgets/PopupWidget.h new file mode 100644 index 0000000..708f3d7 --- /dev/null +++ b/src/Widgets/PopupWidget.h @@ -0,0 +1,47 @@ + /* + * Copyright 2020, WindTerm. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef POPUPWIDGET_H +#define POPUPWIDGET_H + +#pragma once + +#include + +class PopupWidget : public QFrame +{ + Q_OBJECT + +public: + explicit PopupWidget(QWidget *parent = nullptr); + virtual ~PopupWidget(); + + enum Area { LeftArea, MiddleArea, RightArea, TopArea, BottomArea }; + +public: + void setCentralWidget(QWidget *widget); + void show(Area hArea, Area vArea); + +private: + void createLayout(); + bool eventFilter(QObject *watched, QEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + +private: + Q_DISABLE_COPY(PopupWidget) +}; + +#endif // POPUPWIDGET_H \ No newline at end of file