diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index cad3b89d..669ef58b 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -40,8 +40,8 @@ jobs: - name: Install prerequisites run: | sudo apt update - sudo apt install -y make gcc patchelf chrpath qt5-default libxcb-cursor0 - + sudo apt install -y make gcc patchelf chrpath qt5-default libxcb-cursor0 build-essential libgtk-3-dev + - name: build fcitx_qt6 run: | cd depend diff --git a/lib/QGoodWindow/QGoodCentralWidget/QGoodCentralWidget b/lib/QGoodWindow/QGoodCentralWidget/QGoodCentralWidget new file mode 100644 index 00000000..366e79ac --- /dev/null +++ b/lib/QGoodWindow/QGoodCentralWidget/QGoodCentralWidget @@ -0,0 +1,25 @@ +/* +The MIT License (MIT) + +Copyright © 2022-2023 Antonio Dias (https://github.com/antonypro) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "src/qgoodcentralwidget.h" diff --git a/lib/QGoodWindow/QGoodCentralWidget/QGoodCentralWidget.pri b/lib/QGoodWindow/QGoodCentralWidget/QGoodCentralWidget.pri new file mode 100644 index 00000000..c88819c1 --- /dev/null +++ b/lib/QGoodWindow/QGoodCentralWidget/QGoodCentralWidget.pri @@ -0,0 +1,54 @@ +#The MIT License (MIT) + +#Copyright © 2022-2023 Antonio Dias (https://github.com/antonypro) + +#Permission is hereby granted, free of charge, to any person obtaining a copy +#of this software and associated documentation files (the "Software"), to deal +#in the Software without restriction, including without limitation the rights +#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +#copies of the Software, and to permit persons to whom the Software is +#furnished to do so, subject to the following conditions: + +#The above copyright notice and this permission notice shall be included in all +#copies or substantial portions of the Software. + +#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +#SOFTWARE. + +QT += core gui widgets + +CONFIG += c++11 + +SOURCES += \ + $$PWD/src/qgoodcentralwidget.cpp + +HEADERS += \ + $$PWD/src/qgoodcentralwidget.h + +INCLUDEPATH += $$PWD #include + +DEFINES += QGOODCENTRALWIDGET + +qgoodwindow { +QT += svg + +SOURCES += \ + $$PWD/src/captionbutton.cpp \ + $$PWD/src/iconwidget.cpp \ + $$PWD/src/titlebar.cpp \ + $$PWD/src/titlewidget.cpp + +HEADERS += \ + $$PWD/src/captionbutton.h \ + $$PWD/src/iconwidget.h \ + $$PWD/src/titlebar.h \ + $$PWD/src/titlewidget.h + +RESOURCES += \ + $$PWD/src/qgoodcentralwidget_icons.qrc +} diff --git a/lib/QGoodWindow/QGoodCentralWidget/lib-helper/qgoodcentralwidget_global.h b/lib/QGoodWindow/QGoodCentralWidget/lib-helper/qgoodcentralwidget_global.h new file mode 100644 index 00000000..a6b33c2d --- /dev/null +++ b/lib/QGoodWindow/QGoodCentralWidget/lib-helper/qgoodcentralwidget_global.h @@ -0,0 +1,30 @@ +/* +The MIT License (MIT) + +Copyright © 2023 Antonio Dias (https://github.com/antonypro) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef QGOODCENTRALWIDGET_GLOBAL_H +#define QGOODCENTRALWIDGET_GLOBAL_H + +#include "qgoodcentralwidget_helper.h" + +#endif // QGOODCENTRALWIDGET_GLOBAL_H diff --git a/lib/QGoodWindow/QGoodCentralWidget/lib-helper/qgoodcentralwidget_helper.h b/lib/QGoodWindow/QGoodCentralWidget/lib-helper/qgoodcentralwidget_helper.h new file mode 100644 index 00000000..5fa0a6fb --- /dev/null +++ b/lib/QGoodWindow/QGoodCentralWidget/lib-helper/qgoodcentralwidget_helper.h @@ -0,0 +1,32 @@ +/* +The MIT License (MIT) + +Copyright © 2023 Antonio Dias (https://github.com/antonypro) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef QGOODCENTRALWIDGET_GLOBAL_H +#define QGOODCENTRALWIDGET_GLOBAL_H + +#ifndef QGOODCENTRALWIDGET +#define QGOODCENTRALWIDGET +#endif + +#endif // QGOODCENTRALWIDGET_GLOBAL_H diff --git a/lib/QGoodWindow/QGoodCentralWidget/src/captionbutton.cpp b/lib/QGoodWindow/QGoodCentralWidget/src/captionbutton.cpp new file mode 100644 index 00000000..5415eb4c --- /dev/null +++ b/lib/QGoodWindow/QGoodCentralWidget/src/captionbutton.cpp @@ -0,0 +1,274 @@ +/* +The MIT License (MIT) + +Copyright © 2018-2023 Antonio Dias (https://github.com/antonypro) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "captionbutton.h" + +CaptionButton::CaptionButton(IconType type, QWidget *parent) : QWidget(parent) +{ + setVisible(false); + + m_type = type; + m_is_active = false; + m_is_under_mouse = false; + m_is_pressed = false; + m_icon_dark = false; + + setColors(); + drawIcons(); +} + +CaptionButton::~CaptionButton() +{ + +} + +QPixmap CaptionButton::loadSVG(const QString &svg_path, int w, int h) +{ + QPixmap pix = QIcon(svg_path).pixmap(w, h); + return pix; +} + +void CaptionButton::paintIcons(const QPixmap &pix_in, bool dark, + QPixmap *pix_active_out, QPixmap *pix_inactive_out) +{ + QImage img_active = pix_in.toImage(); + QImage img_inactive = img_active; + + const qreal grayed_reduction = qreal(0.40); + + for (int y = 0; y < img_inactive.height(); y++) + { + QRgb *pixel_ptr = reinterpret_cast(img_inactive.scanLine(y)); + + for (int x = 0; x < img_inactive.width(); x++) + { + QRgb pixel = pixel_ptr[x]; + + QRgb rgba = qRgba(qRound(qRed(pixel) * grayed_reduction), + qRound(qGreen(pixel) * grayed_reduction), + qRound(qBlue(pixel) * grayed_reduction), + qAlpha(pixel)); + + pixel_ptr[x] = rgba; + } + } + + if (dark) + { + img_active.invertPixels(); + img_inactive.invertPixels(); + } + + if (pix_active_out) + *pix_active_out = QPixmap::fromImage(img_active); + + if (pix_inactive_out) + *pix_inactive_out = QPixmap::fromImage(img_inactive); +} + +void CaptionButton::drawIcons() +{ + const int w = 8; + const int h = 6; + + switch (m_type) + { + case IconType::Minimize: + { + QPixmap icon = loadSVG(":/icons/minimize.svg", w, h); + + paintIcons(icon, m_icon_dark, &m_active_icon, &m_inactive_icon); + + break; + } + case IconType::Restore: + { + QPixmap icon = loadSVG(":/icons/restore.svg", w, h); + + paintIcons(icon, m_icon_dark, &m_active_icon, &m_inactive_icon); + + break; + } + case IconType::Maximize: + { + QPixmap icon = loadSVG(":/icons/maximize.svg", w, h); + + paintIcons(icon, m_icon_dark, &m_active_icon, &m_inactive_icon); + + break; + } + case IconType::Close: + { + QPixmap icon = loadSVG(":/icons/close.svg", w, h); + + paintIcons(icon, m_icon_dark, &m_active_icon, &m_inactive_icon); + + if (m_icon_dark) + paintIcons(icon, false/*dark*/, &m_close_icon_hover, nullptr); + + break; + } + } +} + +void CaptionButton::setColors() +{ + if (m_icon_dark) + { + if (m_type == IconType::Close) + { + m_normal = QColor("transparent"); + m_hover = QColor("#F00000"); + m_pressed = QColor("#F1707A"); + } + else + { + m_normal = QColor("transparent"); + m_hover = QColor("#E5E5E5"); + m_pressed = QColor("#CACACB"); + } + } + else + { + if (m_type == IconType::Close) + { + m_normal = QColor("transparent"); + m_hover = QColor("#F00000"); + m_pressed = QColor("#F1707A"); + } + else + { + m_normal = QColor("transparent"); + m_hover = QColor("#505050"); + m_pressed = QColor("#3F3F3F"); + } + } + + update(); +} + +void CaptionButton::setIconMode(bool icon_dark) +{ + m_icon_dark = icon_dark; + + drawIcons(); + setColors(); + + update(); +} + +void CaptionButton::setActive(bool is_active) +{ + m_is_active = is_active; + + update(); +} + +void CaptionButton::setState(int state) +{ + switch (state) + { + case QEvent::HoverEnter: + { + m_is_under_mouse = true; + + update(); + + break; + } + case QEvent::HoverLeave: + { + m_is_under_mouse = false; + + update(); + + break; + } + case QEvent::MouseButtonPress: + { + m_is_pressed = true; + + m_is_under_mouse = true; + + update(); + + break; + } + case QEvent::MouseButtonRelease: + { + m_is_pressed = false; + + m_is_under_mouse = false; + + update(); + + break; + } + default: + break; + } +} + +void CaptionButton::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + + QPixmap current_icon = m_active_icon; + QColor current_color = m_normal; + + //Change icon if needed + if (m_is_under_mouse) + { + if (m_type == IconType::Close && m_icon_dark) + current_icon = m_close_icon_hover; + } + else + { + if (!m_is_active) + current_icon = m_inactive_icon; + } + + //Change background color if needed + if (m_is_pressed) + { + if (m_is_under_mouse) + current_color = m_pressed; + } + else + { + if (m_is_under_mouse) + current_color = m_hover; + } + + QPainter painter(this); + painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); + painter.setCompositionMode(QPainter::CompositionMode_SourceOver); + + painter.fillRect(rect(), current_color); + + QRect target_rect; + target_rect = current_icon.rect(); + target_rect.moveCenter(rect().center()); + painter.drawPixmap(target_rect, current_icon); +} diff --git a/lib/QGoodWindow/QGoodCentralWidget/src/captionbutton.h b/lib/QGoodWindow/QGoodCentralWidget/src/captionbutton.h new file mode 100644 index 00000000..e67e92d8 --- /dev/null +++ b/lib/QGoodWindow/QGoodCentralWidget/src/captionbutton.h @@ -0,0 +1,84 @@ +/* +The MIT License (MIT) + +Copyright © 2018-2023 Antonio Dias (https://github.com/antonypro) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef CAPTIONBUTTON_H +#define CAPTIONBUTTON_H + +#include +#include +#include +#include + +//\cond HIDDEN_SYMBOLS +class CaptionButton : public QWidget +{ + Q_OBJECT +public: + enum class IconType + { + Minimize, + Restore, + Maximize, + Close + }; + + explicit CaptionButton(IconType type, QWidget *parent = nullptr); + ~CaptionButton(); + +Q_SIGNALS: + void clicked(); + +public Q_SLOTS: + void setIconMode(bool icon_dark); + void setActive(bool is_active); + void setState(int state); + +private: + //Functions + QPixmap loadSVG(const QString &svg_path, int w, int h); + void paintIcons(const QPixmap &pix_in, bool dark, + QPixmap *pix_active_out, QPixmap *pix_inactive_out); + void drawIcons(); + void setColors(); + void paintEvent(QPaintEvent *event); + + //Variables + QPixmap m_inactive_icon; + QPixmap m_active_icon; + + QPixmap m_close_icon_hover; + + QColor m_normal; + QColor m_hover; + QColor m_pressed; + + IconType m_type; + bool m_is_active; + bool m_is_under_mouse; + bool m_is_pressed; + bool m_icon_dark; +}; +//\endcond + +#endif // CAPTIONBUTTON_H diff --git a/lib/QGoodWindow/QGoodCentralWidget/src/icons/close.svg b/lib/QGoodWindow/QGoodCentralWidget/src/icons/close.svg new file mode 100644 index 00000000..57865212 --- /dev/null +++ b/lib/QGoodWindow/QGoodCentralWidget/src/icons/close.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/lib/QGoodWindow/QGoodCentralWidget/src/icons/maximize.svg b/lib/QGoodWindow/QGoodCentralWidget/src/icons/maximize.svg new file mode 100644 index 00000000..ff5bd5f0 --- /dev/null +++ b/lib/QGoodWindow/QGoodCentralWidget/src/icons/maximize.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/lib/QGoodWindow/QGoodCentralWidget/src/icons/minimize.svg b/lib/QGoodWindow/QGoodCentralWidget/src/icons/minimize.svg new file mode 100644 index 00000000..7ea7ffd0 --- /dev/null +++ b/lib/QGoodWindow/QGoodCentralWidget/src/icons/minimize.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/lib/QGoodWindow/QGoodCentralWidget/src/icons/restore.svg b/lib/QGoodWindow/QGoodCentralWidget/src/icons/restore.svg new file mode 100644 index 00000000..ef051c69 --- /dev/null +++ b/lib/QGoodWindow/QGoodCentralWidget/src/icons/restore.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + diff --git a/lib/QGoodWindow/QGoodCentralWidget/src/iconwidget.cpp b/lib/QGoodWindow/QGoodCentralWidget/src/iconwidget.cpp new file mode 100644 index 00000000..f49890dc --- /dev/null +++ b/lib/QGoodWindow/QGoodCentralWidget/src/iconwidget.cpp @@ -0,0 +1,80 @@ +/* +The MIT License (MIT) + +Copyright © 2018-2023 Antonio Dias (https://github.com/antonypro) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "iconwidget.h" + +#define ICONWIDTH 16 +#define ICONHEIGHT 16 + +IconWidget::IconWidget(QWidget *parent) : QWidget(parent) +{ + m_active = true; +} + +void IconWidget::setPixmap(const QPixmap &pixmap) +{ + m_pixmap = pixmap; + + QImage tmp = m_pixmap.toImage(); + + for (int y = 0; y < tmp.height(); y++) + { + QRgb *pixel_ptr = reinterpret_cast(tmp.scanLine(y)); + + for (int x = 0; x < tmp.width(); x++) + { + QRgb pixel = pixel_ptr[x]; + + int gray = qGray(pixel); + + int alpha = qAlpha(pixel); + + QRgb rgba = qRgba(gray, gray, gray, alpha); + + pixel_ptr[x] = rgba; + } + } + + m_grayed_pixmap = QPixmap::fromImage(tmp); + + update(); +} + +void IconWidget::setActive(bool active) +{ + m_active = active; + update(); +} + +void IconWidget::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + + QPainter painter(this); + painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); + + painter.drawPixmap((width() - ICONWIDTH) / 2, (height() - ICONHEIGHT) / 2, + ICONWIDTH, ICONHEIGHT, + (m_active ? m_pixmap : m_grayed_pixmap)); +} diff --git a/lib/QGoodWindow/QGoodCentralWidget/src/iconwidget.h b/lib/QGoodWindow/QGoodCentralWidget/src/iconwidget.h new file mode 100644 index 00000000..5ac06dcd --- /dev/null +++ b/lib/QGoodWindow/QGoodCentralWidget/src/iconwidget.h @@ -0,0 +1,54 @@ +/* +The MIT License (MIT) + +Copyright © 2018-2023 Antonio Dias (https://github.com/antonypro) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef ICONWIDGET_H +#define ICONWIDGET_H + +#include +#include +#include + +//\cond HIDDEN_SYMBOLS +class IconWidget : public QWidget +{ + Q_OBJECT +public: + explicit IconWidget(QWidget *parent = nullptr); + +public Q_SLOTS: + void setPixmap(const QPixmap &pixmap); + void setActive(bool active); + +private: + //Functions + void paintEvent(QPaintEvent *event); + + //Variables + QPixmap m_pixmap; + QPixmap m_grayed_pixmap; + bool m_active; +}; +//\endcond + +#endif // ICONWIDGET_H diff --git a/lib/QGoodWindow/QGoodCentralWidget/src/qgoodcentralwidget.cpp b/lib/QGoodWindow/QGoodCentralWidget/src/qgoodcentralwidget.cpp new file mode 100644 index 00000000..d4b8c5ff --- /dev/null +++ b/lib/QGoodWindow/QGoodCentralWidget/src/qgoodcentralwidget.cpp @@ -0,0 +1,780 @@ +/* +The MIT License (MIT) + +Copyright © 2022-2023 Antonio Dias (https://github.com/antonypro) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "qgoodcentralwidget.h" + +#ifdef QGOODWINDOW +#include "titlebar.h" +#define BORDERCOLOR QColor(24, 131, 215) +#endif + +QGoodCentralWidget::QGoodCentralWidget(QGoodWindow *gw) : QWidget(gw) +{ + m_gw = gw; + + m_gw->installEventFilter(this); + + m_central_widget_place_holder = new QWidget(this); + +#ifdef QGOODWINDOW + m_left_widget_transparent_for_mouse = false; + m_right_widget_transparent_for_mouse = false; + m_center_widget_transparent_for_mouse = false; + + m_frame_style = QString("QFrame#GoodFrame {%0}"); + + m_active_border_color = BORDERCOLOR; + + m_caption_button_width = 36; + + m_title_bar = new TitleBar(m_gw, this); + m_title_bar->setCaptionButtonWidth(m_caption_button_width); + + m_title_bar->installEventFilter(this); + + connect(m_gw, &QGoodWindow::captionButtonStateChanged, m_title_bar, &TitleBar::captionButtonStateChanged); + + connect(m_title_bar, &TitleBar::showMinimized, m_gw, &QGoodWindow::showMinimized); + connect(m_title_bar, &TitleBar::showNormal, m_gw, &QGoodWindow::showNormal); + connect(m_title_bar, &TitleBar::showMaximized, m_gw, &QGoodWindow::showMaximized); + connect(m_title_bar, &TitleBar::closeWindow, m_gw, &QGoodWindow::close); + + connect(m_gw, &QGoodWindow::windowTitleChanged, m_title_bar, [=](const QString &title){ + m_title_bar->setTitle(title); + }); + + connect(m_gw, &QGoodWindow::windowIconChanged, m_title_bar, [=](const QIcon &icon){ + if (!icon.isNull()) + { + const int pix_size = 16; + m_title_bar->setIcon(icon.pixmap(pix_size, pix_size)); + } + }); + + m_draw_borders = !QGoodWindow::shouldBordersBeDrawnBySystem(); + +#ifdef Q_OS_MAC + auto caption_buttons_visibility_changed_func = [=]{ + if (m_gw->isNativeCaptionButtonsVisibleOnMac()) + { + setCaptionButtonsVisible(false); + setIconVisible(false); + + //QRect rect = m_gw->titleBarButtonsRectOnMacOS(); + //m_title_bar->m_left_margin_widget_place_holder->setFixedSize(rect.x() * 2 + rect.width(), rect.y() + rect.height()); + //m_title_bar->m_left_widget_place_holder->setVisible(true); + } + else + { + setCaptionButtonsVisible(true); + setIconVisible(true); + + //m_title_bar->m_left_margin_widget_place_holder->setFixedSize(0, 0); + //m_title_bar->m_left_widget_place_holder->setVisible(false); + } + }; + + connect(m_gw, &QGoodWindow::captionButtonsVisibilityChangedOnMacOS, this, caption_buttons_visibility_changed_func); + + caption_buttons_visibility_changed_func(); +#endif + + m_title_bar_visible = true; + m_caption_buttons_visible = true; + m_title_visible = true; + m_icon_visible = true; + m_icon_width = 0; + + m_frame = new QFrame(this); + m_frame->setObjectName("GoodFrame"); + + setUnifiedTitleBarAndCentralWidget(false); + + QVBoxLayout *central_layout = new QVBoxLayout(this); + central_layout->setContentsMargins(0, 0, 0, 0); + central_layout->setSpacing(0); + central_layout->addWidget(m_frame); +#else + QVBoxLayout *main_layout = new QVBoxLayout(this); + main_layout->setContentsMargins(0, 0, 0, 0); + main_layout->setSpacing(0); + main_layout->addWidget(m_central_widget_place_holder); +#endif +} + +QGoodCentralWidget::~QGoodCentralWidget() +{ + +} + +int QGoodCentralWidget::execDialogWithWindow(QDialog *dialog, QGoodWindow *parent_gw, + QGoodCentralWidget *base_gcw, + QWidget *left_title_bar_widget, + QWidget *right_title_bar_widget, + bool title_visible, bool icon_visible) +{ +#ifdef QGOODWINDOW + dialog->setWindowFlags(Qt::Widget); + + QGoodWindow *gw = new QGoodWindow(parent_gw); + gw->setGeometry(dialog->frameGeometry()); + gw->setAttribute(Qt::WA_DeleteOnClose); + +#ifdef Q_OS_MAC + gw->setNativeCaptionButtonsVisibleOnMac(parent_gw->isNativeCaptionButtonsVisibleOnMac()); +#endif + + QGoodCentralWidget *gcw = new QGoodCentralWidget(gw); + gcw->setCentralWidget(dialog); + + gcw->setLeftTitleBarWidget(left_title_bar_widget); + gcw->setRightTitleBarWidget(right_title_bar_widget); + + if (base_gcw) + { + if (base_gcw->titleBarColor() != QColor(Qt::transparent)) + gcw->setTitleBarColor(base_gcw->titleBarColor()); + gcw->setActiveBorderColor(base_gcw->activeBorderColor()); + gcw->setTitleAlignment(base_gcw->titleAlignment()); + } + + gcw->setTitleVisible(title_visible); + gcw->setIconVisible(icon_visible); + + gw->setCentralWidget(gcw); + + return QGoodWindow::execDialog(dialog, gw, parent_gw); +#else + Q_UNUSED(parent_gw) + Q_UNUSED(base_gcw) + Q_UNUSED(left_title_bar_widget) + Q_UNUSED(right_title_bar_widget) + Q_UNUSED(title_visible) + Q_UNUSED(icon_visible) + return dialog->exec(); +#endif +} + +void QGoodCentralWidget::setUnifiedTitleBarAndCentralWidget(bool unified) +{ +#ifdef QGOODWINDOW + if (m_frame->layout()) + delete m_frame->layout(); + + if (!unified) + { + m_unified_title_bar_and_central_widget = false; + setTitleBarColor(QColor()); + + QVBoxLayout *main_layout = new QVBoxLayout(m_frame); + main_layout->setContentsMargins(0, 0, 0, 0); + main_layout->setSpacing(0); + main_layout->addWidget(m_title_bar); + main_layout->addWidget(m_central_widget_place_holder); + } + else + { + setTitleBarColor(QColor(Qt::transparent)); + m_unified_title_bar_and_central_widget = true; + + QStackedLayout *main_layout = new QStackedLayout(m_frame); + main_layout->setStackingMode(QStackedLayout::StackAll); + main_layout->setContentsMargins(0, 0, 0, 0); + main_layout->setSpacing(0); + main_layout->addWidget(m_title_bar); + main_layout->addWidget(m_central_widget_place_holder); + } +#else + Q_UNUSED(unified) +#endif +} + +void QGoodCentralWidget::setTitleBarMask(const QRegion &mask) +{ +#ifdef QGOODWINDOW + m_title_bar_mask = mask; + updateWindow(); +#else + Q_UNUSED(mask) +#endif +} + +QWidget *QGoodCentralWidget::setLeftTitleBarWidget(QWidget *widget, bool transparent_for_mouse) +{ +#ifdef QGOODWINDOW + QWidget *return_widget = m_title_bar_left_widget; + + m_title_bar_left_widget = widget; + + m_left_widget_transparent_for_mouse = transparent_for_mouse; + + m_title_bar->setLeftTitleBarWidget(m_title_bar_left_widget); + + updateWindow(); + + return return_widget; +#else + Q_UNUSED(widget) + Q_UNUSED(transparent_for_mouse) + return nullptr; +#endif +} + +QWidget *QGoodCentralWidget::setRightTitleBarWidget(QWidget *widget, bool transparent_for_mouse) +{ +#ifdef QGOODWINDOW + QWidget *return_widget = m_title_bar_right_widget; + + m_title_bar_right_widget = widget; + + m_right_widget_transparent_for_mouse = transparent_for_mouse; + + m_title_bar->setRightTitleBarWidget(m_title_bar_right_widget); + + updateWindow(); + + return return_widget; +#else + Q_UNUSED(widget) + Q_UNUSED(transparent_for_mouse) + return nullptr; +#endif +} + +QWidget *QGoodCentralWidget::setCenterTitleBarWidget(QWidget *widget, bool transparent_for_mouse) +{ +#ifdef QGOODWINDOW + QWidget *return_widget = m_title_bar_center_widget; + + m_title_bar_center_widget = widget; + + m_center_widget_transparent_for_mouse = transparent_for_mouse; + + m_title_bar->setCenterTitleBarWidget(m_title_bar_center_widget); + + updateWindow(); + + return return_widget; +#else + Q_UNUSED(widget) + Q_UNUSED(transparent_for_mouse) + return nullptr; +#endif +} + +void QGoodCentralWidget::setCentralWidget(QWidget *widget) +{ + if (!m_central_widget_place_holder) + return; + + m_central_widget = widget; + + if (m_central_widget_place_holder->layout()) + delete m_central_widget_place_holder->layout(); + + QGridLayout *layout = new QGridLayout(m_central_widget_place_holder); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(0); + layout->addWidget(m_central_widget); +} + +void QGoodCentralWidget::setTitleAlignment(const Qt::Alignment &alignment) +{ +#ifdef QGOODWINDOW + m_title_bar->setTitleAlignment(alignment); +#else + Q_UNUSED(alignment) +#endif +} + +void QGoodCentralWidget::setTitleBarColor(const QColor &color) +{ +#ifdef QGOODWINDOW + if (m_unified_title_bar_and_central_widget) + return; + + m_title_bar_color = color; + m_title_bar->m_title_bar_color = m_title_bar_color; + m_title_bar->setTheme(); +#else + Q_UNUSED(color) +#endif +} + +void QGoodCentralWidget::setActiveBorderColor(const QColor &color) +{ +#ifdef QGOODWINDOW + if (color.isValid()) + m_active_border_color = color; + else + m_active_border_color = BORDERCOLOR; + + updateWindow(); +#else + Q_UNUSED(color) +#endif +} + +void QGoodCentralWidget::setTitleBarVisible(bool visible) +{ +#ifdef QGOODWINDOW + m_title_bar_visible = visible; + m_title_bar->setVisible(m_title_bar_visible); + m_title_bar->setEnabled(m_title_bar_visible); + if (!m_title_bar_visible) + m_gw->setNativeCaptionButtonsVisibleOnMac(false); + updateWindow(); +#else + Q_UNUSED(visible) +#endif +} + +void QGoodCentralWidget::setCaptionButtonsVisible(bool visible) +{ +#ifdef QGOODWINDOW + m_caption_buttons_visible = visible; + + m_title_bar->m_caption_buttons->setVisible(m_caption_buttons_visible); + m_title_bar->m_caption_buttons->setEnabled(m_caption_buttons_visible); + + if (!m_caption_buttons_visible) + { + m_gw->setMinimizeMask(QRegion()); + m_gw->setMaximizeMask(QRegion()); + m_gw->setCloseMask(QRegion()); + } + + updateWindow(); +#else + Q_UNUSED(visible) +#endif +} + +void QGoodCentralWidget::setTitleVisible(bool visible) +{ +#ifdef QGOODWINDOW + m_title_visible = visible; + m_title_bar->m_title_widget->setVisible(m_title_visible); + m_title_bar->m_title_widget->setEnabled(m_title_visible); + updateWindow(); +#else + Q_UNUSED(visible) +#endif +} + +void QGoodCentralWidget::setIconVisible(bool visible) +{ +#ifdef QGOODWINDOW + m_icon_visible = visible; + m_title_bar->m_icon_widget->setVisible(m_icon_visible); + m_title_bar->m_icon_widget->setEnabled(m_icon_visible); + updateWindow(); +#else + Q_UNUSED(visible) +#endif +} + +void QGoodCentralWidget::setIconWidth(int width) +{ +#ifdef QGOODWINDOW + m_icon_width = width; + updateWindow(); +#else + Q_UNUSED(width) +#endif +} + +void QGoodCentralWidget::setTitleBarHeight(int height) +{ +#ifdef QGOODWINDOW + m_title_bar->setFixedHeight(height); + m_title_bar->setCaptionButtonWidth(m_caption_button_width); + updateWindow(); +#else + Q_UNUSED(height) +#endif +} + +void QGoodCentralWidget::setCaptionButtonWidth(int width) +{ +#ifdef QGOODWINDOW + m_caption_button_width = width; + m_title_bar->setCaptionButtonWidth(m_caption_button_width); + updateWindow(); +#else + Q_UNUSED(width) +#endif +} + +bool QGoodCentralWidget::isUnifiedTitleBarAndCentralWidget() const +{ +#ifdef QGOODWINDOW + return m_unified_title_bar_and_central_widget; +#else + return false; +#endif +} + +QWidget *QGoodCentralWidget::leftTitleBarWidget() const +{ +#ifdef QGOODWINDOW + return m_title_bar_left_widget; +#else + return nullptr; +#endif +} + +QWidget *QGoodCentralWidget::rightTitleBarWidget() const +{ +#ifdef QGOODWINDOW + return m_title_bar_right_widget; +#else + return nullptr; +#endif +} + +QWidget *QGoodCentralWidget::centerTitleBarWidget() const +{ +#ifdef QGOODWINDOW + return m_title_bar_center_widget; +#else + return nullptr; +#endif +} + +QWidget *QGoodCentralWidget::centralWidget() const +{ + return m_central_widget; +} + +Qt::Alignment QGoodCentralWidget::titleAlignment() const +{ +#ifdef QGOODWINDOW + return m_title_bar->titleAlignment(); +#else + return Qt::Alignment(0); +#endif +} + +QColor QGoodCentralWidget::titleBarColor() const +{ +#ifdef QGOODWINDOW + return m_title_bar_color; +#else + return QColor(); +#endif +} + +QColor QGoodCentralWidget::activeBorderColor() const +{ +#ifdef QGOODWINDOW + return m_active_border_color; +#else + return QColor(); +#endif +} + +bool QGoodCentralWidget::isTitleBarVisible() const +{ +#ifdef QGOODWINDOW + return m_title_bar->isVisible(); +#else + return true; +#endif +} + +bool QGoodCentralWidget::isCaptionButtonsVisible() const +{ +#ifdef QGOODWINDOW + return m_caption_buttons_visible; +#else + return true; +#endif +} + +bool QGoodCentralWidget::isTitleVisible() const +{ +#ifdef QGOODWINDOW + return m_title_visible; +#else + return true; +#endif +} + +bool QGoodCentralWidget::isIconVisible() const +{ +#ifdef QGOODWINDOW + return m_icon_visible; +#else + return true; +#endif +} + +int QGoodCentralWidget::iconWidth() const +{ +#ifdef QGOODWINDOW + return m_icon_width; +#else + return 0; +#endif +} + +int QGoodCentralWidget::titleBarHeight() const +{ +#ifdef QGOODWINDOW + return m_title_bar->height(); +#else + return 0; +#endif +} + +int QGoodCentralWidget::captionButtonWidth() const +{ +#ifdef QGOODWINDOW + return m_caption_button_width; +#else + return 0; +#endif +} + +void QGoodCentralWidget::updateWindow() +{ +#ifdef QGOODWINDOW + QTimer::singleShot(0, this, &QGoodCentralWidget::updateWindowLater); +#endif +} + +void QGoodCentralWidget::updateWindowLater() +{ +#ifdef QGOODWINDOW + if (!m_gw) + return; + + if (!m_gw->isVisible() || m_gw->isMinimized()) + return; + + bool window_active = m_gw->isActiveWindow(); + bool window_no_state = m_gw->windowState().testFlag(Qt::WindowNoState); + bool draw_borders = m_draw_borders; + bool is_maximized = m_gw->isMaximized(); + bool is_full_screen = m_gw->isFullScreen(); + int title_bar_width = m_title_bar->width(); + int title_bar_height = m_title_bar->height(); + + int icon_width = m_icon_width; + + if (m_icon_visible) + icon_width = m_title_bar->m_icon_widget->width(); + + QString border_str = "none;"; + + if (draw_borders && window_no_state) + { + if (window_active) + border_str = QString("border: 1px solid %0;").arg(m_active_border_color.name()); + else + border_str = "border: 1px solid #AAAAAA;"; + } + +#ifdef Q_OS_LINUX + border_str.append("border-radius: 10px;"); +#endif + + m_frame->setStyleSheet(m_frame_style.arg(border_str)); + + m_title_bar->setMaximized(is_maximized && !is_full_screen); + + m_title_bar->setVisible(m_title_bar_visible && !is_full_screen); + + if (!is_full_screen) + { + m_gw->setTitleBarHeight(m_title_bar_visible ? title_bar_height : 0); + + m_gw->setIconWidth(icon_width); + + QRegion left_mask; + QRegion right_mask; + QRegion center_mask; + + if (m_title_bar_left_widget) + { + QWidgetList list; + + if (!m_left_widget_transparent_for_mouse) + list.append(m_title_bar_left_widget); + + list.append(m_title_bar_left_widget->findChildren()); + + for (QWidget *widget : list) + { + if (!widget->testAttribute(Qt::WA_TransparentForMouseEvents)) + { + if (!widget->mask().isNull()) + { + left_mask += widget->mask().translated(m_title_bar->m_left_widget_place_holder->pos()); + } + else + { + QRect geom = widget->geometry(); + + if (geom.width() > m_title_bar_left_widget->width()) + geom.setWidth(m_title_bar_left_widget->width()); + + left_mask += geom.translated(m_title_bar->m_left_widget_place_holder->pos()); + } + } + } + } + + if (m_title_bar_right_widget) + { + QWidgetList list; + + if (!m_right_widget_transparent_for_mouse) + list.append(m_title_bar_right_widget); + + list.append(m_title_bar_right_widget->findChildren()); + + for (QWidget *widget : list) + { + if (!widget->testAttribute(Qt::WA_TransparentForMouseEvents)) + { + if (!widget->mask().isNull()) + { + right_mask += widget->mask().translated(m_title_bar->m_right_widget_place_holder->pos()); + } + else + { + QRect geom = widget->geometry(); + + if (geom.width() > m_title_bar_right_widget->width()) + geom.setWidth(m_title_bar_right_widget->width()); + + right_mask += geom.translated(m_title_bar->m_right_widget_place_holder->pos()); + } + } + } + } + + if (m_title_bar_center_widget) + { + QWidgetList list; + + if (!m_center_widget_transparent_for_mouse) + list.append(m_title_bar_center_widget); + + list.append(m_title_bar_center_widget->findChildren()); + + for (QWidget *widget : list) + { + if (!widget->testAttribute(Qt::WA_TransparentForMouseEvents)) + { + if (!widget->mask().isNull()) + { + center_mask += widget->mask().translated(m_title_bar->m_center_widget_place_holder->pos()); + } + else + { + QRect geom = widget->geometry(); + + if (geom.width() > m_title_bar_center_widget->width()) + geom.setWidth(m_title_bar_center_widget->width()); + + center_mask += geom.translated(m_title_bar->m_center_widget_place_holder->pos()); + } + } + } + } + + QRegion title_bar_mask; + + if (!m_title_bar_mask.isEmpty()) + title_bar_mask = m_title_bar_mask; + else + title_bar_mask -= m_gw->titleBarRect(); + + title_bar_mask += left_mask; + title_bar_mask += center_mask; + title_bar_mask += right_mask; + + m_gw->setTitleBarMask(title_bar_mask); + + if (m_caption_buttons_visible) + { + QRect min_rect = m_title_bar->minimizeButtonRect(); + QRect max_rect = m_title_bar->maximizeButtonRect(); + QRect cls_rect = m_title_bar->closeButtonRect(); + + min_rect.moveLeft(title_bar_width - cls_rect.width() - max_rect.width() - min_rect.width()); + max_rect.moveLeft(title_bar_width - cls_rect.width() - max_rect.width()); + cls_rect.moveLeft(title_bar_width - cls_rect.width()); + + m_gw->setMinimizeMask(min_rect); + m_gw->setMaximizeMask(max_rect); + m_gw->setCloseMask(cls_rect); + } + } + + m_title_bar->setActive(window_active); + + m_title_bar->updateWindow(); +#endif +} + +bool QGoodCentralWidget::eventFilter(QObject *watched, QEvent *event) +{ +#ifdef QGOODWINDOW + if (watched == m_gw) + { + switch (event->type()) + { + case QEvent::Show: + case QEvent::Resize: + case QEvent::WindowStateChange: + case QEvent::WindowActivate: + case QEvent::WindowDeactivate: + { + updateWindow(); + break; + } + default: + break; + } + } + else if (watched == m_title_bar) + { + switch (event->type()) + { + case QEvent::Show: + { + updateWindow(); + break; + } + default: + break; + } + } +#endif + return QWidget::eventFilter(watched, event); +} diff --git a/lib/QGoodWindow/QGoodCentralWidget/src/qgoodcentralwidget.h b/lib/QGoodWindow/QGoodCentralWidget/src/qgoodcentralwidget.h new file mode 100644 index 00000000..305c064f --- /dev/null +++ b/lib/QGoodWindow/QGoodCentralWidget/src/qgoodcentralwidget.h @@ -0,0 +1,203 @@ +/* +The MIT License (MIT) + +Copyright © 2022-2023 Antonio Dias (https://github.com/antonypro) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef QGOODCENTRALWIDGET_H +#define QGOODCENTRALWIDGET_H + +#include + +#include "qgoodcentralwidget_global.h" + +#ifdef QGOODWINDOW +class TitleBar; +#endif + +/** **QGoodCentralWidget** class contains the public API's to control the behavior of **QGoodWindow**. */ +class QGOODWINDOW_SHARED_EXPORT QGoodCentralWidget : public QWidget +{ + Q_OBJECT +public: + /** Constructor of *QGoodCentralWidget*, is mandatory pass a valid instance of the parent *QGoodWindow*. */ + explicit QGoodCentralWidget(QGoodWindow *gw); + /** Destructor of *QGoodCentralWidget*. */ + ~QGoodCentralWidget(); + + /** Utility for showing a modal *QDialog* with customized title bar and borders. + Pass the *QDialog* and the parent *QGoodWindow* and optionally a *QGoodCentralWidget* for mimic + it's colors on the new window. Optionally pass a left title bar widget, + a right title bar widget and set the visibility + of title and icon on the new window. */ + static int execDialogWithWindow(QDialog *dialog, QGoodWindow *parent_gw, + QGoodCentralWidget *base_gcw = nullptr, + QWidget *left_title_bar_widget = nullptr, + QWidget *right_title_bar_widget = nullptr, + bool title_visible = true, bool icon_visible = true); + +public Q_SLOTS: + /** Set the title bar and the central widget unified. */ + void setUnifiedTitleBarAndCentralWidget(bool unified); + + /** Set the title bar mask, the title bar widgets masks united with this mask. */ + void setTitleBarMask(const QRegion &mask); + + /** Set the left title bar widget and returns the previous widget or nullptr if none, delete this widget as needed. + If the widget is transparent for mouse, but not it's children's set \e transparent_for_mouse to true. */ + QWidget *setLeftTitleBarWidget(QWidget *widget, bool transparent_for_mouse = false); + + /** Set the right title bar widget and returns the previous widget or nullptr if none, delete this widget as needed. + If the widget is transparent for mouse, but not it's children's set \e transparent_for_mouse to true. */ + QWidget *setRightTitleBarWidget(QWidget *widget, bool transparent_for_mouse = false); + + /** Set the center title bar widget and returns the previous widget or nullptr if none, delete this widget as needed. + If the widget is transparent for mouse, but not it's children's set \e transparent_for_mouse to true. */ + QWidget *setCenterTitleBarWidget(QWidget *widget, bool transparent_for_mouse = false); + + /** Set the central widget of *QGoodCentralWidget*. */ + void setCentralWidget(QWidget *widget); + + /** Set the alignment of *QGoodCentralWidget* title on the title bar. + Note: If align to center and also set a central title bar widget + the title will be aligned to the left.*/ + void setTitleAlignment(const Qt::Alignment &alignment); + + /** Set the color of *QGoodCentralWidget* title bar. */ + void setTitleBarColor(const QColor &color); + + /** Set the color of *QGoodCentralWidget* border. */ + void setActiveBorderColor(const QColor &color); + + /** Change the visibility of *QGoodCentralWidget* title bar. */ + void setTitleBarVisible(bool visible); + + /** Change the visibility of *QGoodCentralWidget* caption buttons. */ + void setCaptionButtonsVisible(bool visible); + + /** Change the visibility of *QGoodCentralWidget* title bar title. */ + void setTitleVisible(bool visible); + + /** Change the visibility of *QGoodCentralWidget* title bar icon. */ + void setIconVisible(bool visible); + + /** Change the width of *QGoodCentralWidget* title bar icon, + * width higher than 0 will hide the *QGoodCentralWidget* provided icon, + * this is useful when showing own icons. */ + void setIconWidth(int width); + + /** Change the title bar height to \e height multiplied to current pixel ratio. */ + void setTitleBarHeight(int height); + + /** Change the caption button width to \e width multiplied to current pixel ratio. */ + void setCaptionButtonWidth(int width); + + /** Returns if the title bar and the central widget are unified. */ + bool isUnifiedTitleBarAndCentralWidget() const; + + /** Returns the left *QGoodCentralWidget* title bar widget or nullptr if none is set. */ + QWidget *leftTitleBarWidget() const; + + /** Returns the right *QGoodCentralWidget* title bar widget or nullptr if none is set. */ + QWidget *rightTitleBarWidget() const; + + /** Returns the center *QGoodCentralWidget* title bar widget or nullptr if none is set. */ + QWidget *centerTitleBarWidget() const; + + /** Returns the *QGoodCentralWidget* central widget or nullptr if none is set. */ + QWidget *centralWidget() const; + + /** Returns the alignment of *QGoodCentralWidget* title on the title bar. */ + Qt::Alignment titleAlignment() const; + + /** Returns the *QGoodCentralWidget* title bar color. */ + QColor titleBarColor() const; + + /** Returns the *QGoodCentralWidget* border color. */ + QColor activeBorderColor() const; + + /** Returns if the *QGoodCentralWidget* title bar is visible or not. */ + bool isTitleBarVisible() const; + + /** Returns if the *QGoodCentralWidget* caption buttons are visible or not. */ + bool isCaptionButtonsVisible() const; + + /** Returns if the *QGoodCentralWidget* title bar title is visible or not. */ + bool isTitleVisible() const; + + /** Returns if the *QGoodCentralWidget* title bar icon is visible or not. */ + bool isIconVisible() const; + + /** Returns the customized icon width. */ + int iconWidth() const; + + /** Returns the *QGoodCentralWidget* title bar height. */ + int titleBarHeight() const; + + /** Returns the *QGoodCentralWidget* title bar caption button width. */ + int captionButtonWidth() const; + + /** Update the *QGoodCentralWidget* state, it's called internally + and must be called if changing *QGoodCentralWidget* metrics + like hide or show a title bar widget. */ + void updateWindow(); + +protected: + //\cond HIDDEN_SYMBOLS + //Functions + bool eventFilter(QObject *watched, QEvent *event); + //\endcond + +private: + //\cond HIDDEN_SYMBOLS + //Functions + void updateWindowLater(); + + //Variables + QPointer m_gw; + QPointer m_central_widget_place_holder; + QPointer m_central_widget; +#ifdef QGOODWINDOW + bool m_unified_title_bar_and_central_widget; + QRegion m_title_bar_mask; + bool m_left_widget_transparent_for_mouse; + bool m_right_widget_transparent_for_mouse; + bool m_center_widget_transparent_for_mouse; + int m_caption_button_width; + bool m_draw_borders; + QFrame *m_frame; + QString m_frame_style; + TitleBar *m_title_bar; + QPointer m_title_bar_left_widget; + QPointer m_title_bar_right_widget; + QPointer m_title_bar_center_widget; + QColor m_title_bar_color; + QColor m_active_border_color; + bool m_title_bar_visible; + bool m_caption_buttons_visible; + bool m_title_visible; + bool m_icon_visible; + int m_icon_width; +#endif + //\endcond +}; + +#endif // QGOODCENTRALWIDGET_H diff --git a/lib/QGoodWindow/QGoodCentralWidget/src/qgoodcentralwidget_global.h b/lib/QGoodWindow/QGoodCentralWidget/src/qgoodcentralwidget_global.h new file mode 100644 index 00000000..b3e131c2 --- /dev/null +++ b/lib/QGoodWindow/QGoodCentralWidget/src/qgoodcentralwidget_global.h @@ -0,0 +1,31 @@ +/* +The MIT License (MIT) + +Copyright © 2023 Antonio Dias (https://github.com/antonypro) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef QGOODCENTRALWIDGET_GLOBAL_H +#define QGOODCENTRALWIDGET_GLOBAL_H + +#include "qgoodcentralwidget_helper.h" + +#endif // QGOODCENTRALWIDGET_GLOBAL_H + diff --git a/lib/QGoodWindow/QGoodCentralWidget/src/qgoodcentralwidget_helper.h b/lib/QGoodWindow/QGoodCentralWidget/src/qgoodcentralwidget_helper.h new file mode 100644 index 00000000..62334faa --- /dev/null +++ b/lib/QGoodWindow/QGoodCentralWidget/src/qgoodcentralwidget_helper.h @@ -0,0 +1,28 @@ +/* +The MIT License (MIT) + +Copyright © 2023 Antonio Dias (https://github.com/antonypro) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef QGOODCENTRALWIDGET_GLOBAL_H +#define QGOODCENTRALWIDGET_GLOBAL_H + +#endif // QGOODCENTRALWIDGET_GLOBAL_H diff --git a/lib/QGoodWindow/QGoodCentralWidget/src/qgoodcentralwidget_icons.qrc b/lib/QGoodWindow/QGoodCentralWidget/src/qgoodcentralwidget_icons.qrc new file mode 100644 index 00000000..d5434f55 --- /dev/null +++ b/lib/QGoodWindow/QGoodCentralWidget/src/qgoodcentralwidget_icons.qrc @@ -0,0 +1,8 @@ + + + icons/minimize.svg + icons/restore.svg + icons/maximize.svg + icons/close.svg + + diff --git a/lib/QGoodWindow/QGoodCentralWidget/src/titlebar.cpp b/lib/QGoodWindow/QGoodCentralWidget/src/titlebar.cpp new file mode 100644 index 00000000..1337c711 --- /dev/null +++ b/lib/QGoodWindow/QGoodCentralWidget/src/titlebar.cpp @@ -0,0 +1,598 @@ +/* +The MIT License (MIT) + +Copyright © 2018-2023 Antonio Dias (https://github.com/antonypro) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "titlebar.h" + +TitleBar::TitleBar(QGoodWindow *gw, QWidget *parent) : QFrame(parent) +{ + m_layout_spacing = 0; + + // Get Layout Spacing + { + QString old_style; + + if (!qApp->style()->objectName().startsWith("fusion")) + { + old_style = qApp->style()->objectName(); + qApp->setStyle(QStyleFactory::create("fusion")); + } + + QWidget widget; + + QHBoxLayout *layout = new QHBoxLayout(&widget); + + m_layout_spacing = layout->spacing(); + + if (!old_style.isEmpty()) + { + qApp->setStyle(QStyleFactory::create(old_style)); + } + } + + m_gw = gw; + + m_style = QString("TitleBar {background-color: %0;" + #ifdef Q_OS_LINUX + "border-top-left-radius: 10px; border-top-right-radius: 10px;" + #endif + "}"); + + connect(qGoodStateHolder, &QGoodStateHolder::currentThemeChanged, this, &TitleBar::setTheme); + + setFixedHeight(29); + + m_left_margin_widget_place_holder = new QWidget(this); + m_left_margin_widget_place_holder->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); + + m_icon_widget = new IconWidget(this); + m_icon_widget->setFixedWidth(29); + + m_title_widget = new TitleWidget(this, this); + + m_caption_buttons = new QWidget(this); + m_caption_buttons->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); + + m_min_btn = new CaptionButton(CaptionButton::IconType::Minimize, m_caption_buttons); + + m_restore_btn = new CaptionButton(CaptionButton::IconType::Restore, m_caption_buttons); + + m_max_btn = new CaptionButton(CaptionButton::IconType::Maximize, m_caption_buttons); + + m_cls_btn = new CaptionButton(CaptionButton::IconType::Close, m_caption_buttons); + + connect(m_min_btn, &CaptionButton::clicked, this, &TitleBar::showMinimized); + connect(m_restore_btn, &CaptionButton::clicked, this, &TitleBar::showNormal); + connect(m_max_btn, &CaptionButton::clicked, this, &TitleBar::showMaximized); + connect(m_cls_btn, &CaptionButton::clicked, this, &TitleBar::closeWindow); + + QHBoxLayout *hlayout = new QHBoxLayout(m_caption_buttons); + hlayout->setContentsMargins(0, 0, 0, 0); + hlayout->setSpacing(0); + + hlayout->addWidget(m_min_btn); + hlayout->addWidget(m_restore_btn); + hlayout->addWidget(m_max_btn); + hlayout->addWidget(m_cls_btn); + + m_caption_buttons->adjustSize(); + + QStackedLayout *stacked_layout = new QStackedLayout(this); + stacked_layout->setStackingMode(QStackedLayout::StackAll); + + QWidget *widget = new QWidget(this); + + QHBoxLayout *layout = new QHBoxLayout(widget); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(0); + + stacked_layout->addWidget(widget); + stacked_layout->addWidget(m_title_widget); + + m_left_widget_place_holder = new QWidget(this); + m_left_widget_place_holder->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); + m_left_widget_place_holder->setVisible(false); + m_left_widget_place_holder->setEnabled(false); + + m_right_widget_place_holder = new QWidget(this); + m_right_widget_place_holder->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); + m_right_widget_place_holder->setVisible(false); + m_right_widget_place_holder->setEnabled(false); + + m_center_widget_place_holder = new QWidget(this); + m_center_widget_place_holder->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); + m_center_widget_place_holder->setVisible(false); + m_center_widget_place_holder->setEnabled(false); + + m_center_spacer_item_left = new QSpacerItem(0, 0); + m_center_spacer_item_right = new QSpacerItem(0, 0); + + layout->addWidget(m_left_margin_widget_place_holder); + layout->addWidget(m_icon_widget); + layout->addWidget(m_left_widget_place_holder); + layout->addStretch(); + layout->addSpacerItem(m_center_spacer_item_left); + layout->addWidget(m_center_widget_place_holder); + layout->addSpacerItem(m_center_spacer_item_right); + layout->addStretch(); + layout->addWidget(m_right_widget_place_holder); + layout->addWidget(m_caption_buttons); + + QTimer::singleShot(0, this, [=]{ + //All caption button starts in hidden state, + //showing them as needed. + + if (isMinimizedButtonEnabled()) + { + m_min_btn->setVisible(true); + } + + if (isMaximizeButtonEnabled()) + { + if (!isMaximized()) + m_max_btn->setVisible(true); + else + m_restore_btn->setVisible(true); + } + + m_cls_btn->setVisible(true); + + m_caption_buttons->adjustSize(); + }); + + m_active = true; + + m_is_maximized = false; + + setTheme(); +} + +void TitleBar::setTitle(const QString &title) +{ + m_title_widget->setText(title); +} + +void TitleBar::setIcon(const QPixmap &icon) +{ + m_icon_widget->setPixmap(icon); +} + +void TitleBar::setActive(bool active) +{ + m_active = active; + + m_icon_widget->setActive(active); + m_title_widget->setActive(active); + m_min_btn->setActive(active); + m_restore_btn->setActive(active); + m_max_btn->setActive(active); + m_cls_btn->setActive(active); +} + +void TitleBar::setTitleAlignment(const Qt::Alignment &alignment) +{ + m_title_widget->setTitleAlignment(alignment); +} + +void TitleBar::setMaximized(bool maximized) +{ + m_is_maximized = maximized; + + if (!isMaximizeButtonEnabled()) + return; + + if (maximized) + { + m_max_btn->setVisible(false); + m_restore_btn->setVisible(true); + } + else + { + m_restore_btn->setVisible(false); + m_max_btn->setVisible(true); + } + + m_caption_buttons->adjustSize(); +} + +void TitleBar::setTheme() +{ + bool dark = qGoodStateHolder->isCurrentThemeDark(); + + setAttribute(Qt::WA_TranslucentBackground, false); + + if (dark) + { + QTimer::singleShot(0, this, [=]{ + if (m_title_bar_color == QColor(Qt::transparent)) + setAttribute(Qt::WA_TranslucentBackground, true); + else if (m_title_bar_color.isValid()) + setStyleSheet(m_style.arg(m_title_bar_color.name())); + else if (qApp->style()->objectName().startsWith("fusion")) + setStyleSheet(m_style.arg(qApp->palette().base().color().name())); + else + setStyleSheet(m_style.arg("#000000")); + }); + + //Light mode to contrast + m_min_btn->setIconMode(false); + m_max_btn->setIconMode(false); + m_restore_btn->setIconMode(false); + m_cls_btn->setIconMode(false); + + m_title_widget->setTitleColor(QColor(255, 255, 255), QColor(150, 150, 150)); + + setActive(m_active); + } + else + { + QTimer::singleShot(0, this, [=]{ + if (m_title_bar_color == QColor(Qt::transparent)) + setAttribute(Qt::WA_TranslucentBackground, true); + else if (m_title_bar_color.isValid()) + setStyleSheet(m_style.arg(m_title_bar_color.name())); + else if (qApp->style()->objectName().startsWith("fusion")) + setStyleSheet(m_style.arg(qApp->palette().base().color().name())); + else + setStyleSheet(m_style.arg("#FFFFFF")); + }); + + //Dark mode to contrast + m_min_btn->setIconMode(true); + m_max_btn->setIconMode(true); + m_restore_btn->setIconMode(true); + m_cls_btn->setIconMode(true); + + m_title_widget->setTitleColor(QColor(0, 0, 0), QColor(150, 150, 150)); + + setActive(m_active); + } +} + +bool TitleBar::eventFilter(QObject *obj, QEvent *event) +{ + if (obj == m_left_widget && event->type() == QEvent::Resize) { + updateWindow(); + update(); + return QObject::eventFilter(obj, event); + } else { + return QObject::eventFilter(obj, event); + } +} + +void TitleBar::setLeftTitleBarWidget(QWidget *widget) +{ + if (m_left_widget_place_holder->layout()) + delete m_left_widget_place_holder->layout(); + + m_left_widget = widget; + + if (m_left_widget) + { + QGridLayout *layout = new QGridLayout(m_left_widget_place_holder); + layout->setContentsMargins(0, 0, m_layout_spacing, 0); + layout->setSpacing(0); + layout->addWidget(m_left_widget); + + m_left_widget->installEventFilter(this); + + m_left_widget_place_holder->setVisible(true); + m_left_widget_place_holder->setEnabled(true); + } + else + { + m_left_widget_place_holder->setVisible(false); + m_left_widget_place_holder->setEnabled(false); + } +} + +void TitleBar::setRightTitleBarWidget(QWidget *widget) +{ + if (m_right_widget_place_holder->layout()) + delete m_right_widget_place_holder->layout(); + + m_right_widget = widget; + + if (m_right_widget) + { + QGridLayout *layout = new QGridLayout(m_right_widget_place_holder); + layout->setContentsMargins(0, 0, m_layout_spacing, 0); + layout->setSpacing(0); + layout->addWidget(m_right_widget); + + m_right_widget_place_holder->setVisible(true); + m_right_widget_place_holder->setEnabled(true); + } + else + { + m_right_widget_place_holder->setVisible(false); + m_right_widget_place_holder->setEnabled(false); + } +} + +void TitleBar::setCenterTitleBarWidget(QWidget *widget) +{ + if (m_center_widget_place_holder->layout()) + delete m_center_widget_place_holder->layout(); + + m_center_widget = widget; + + if (m_center_widget) + { + QGridLayout *layout = new QGridLayout(m_center_widget_place_holder); + layout->setContentsMargins(0, 0, m_layout_spacing, 0); + layout->setSpacing(0); + layout->addWidget(m_center_widget); + + m_center_widget_place_holder->setVisible(true); + m_center_widget_place_holder->setEnabled(true); + } + else + { + m_center_widget_place_holder->setVisible(false); + m_center_widget_place_holder->setEnabled(false); + } +} + +void TitleBar::setCaptionButtonWidth(int width) +{ + m_min_btn->setFixedSize(width, height()); + m_restore_btn->setFixedSize(width, height()); + m_max_btn->setFixedSize(width, height()); + m_cls_btn->setFixedSize(width, height()); + + m_caption_buttons->adjustSize(); +} + +Qt::Alignment TitleBar::titleAlignment() +{ + return m_title_widget->titleAlignment(); +} + +int TitleBar::captionButtonsWidth() +{ + return m_caption_buttons->width(); +} + +int TitleBar::leftWidth() +{ + QRect rect; + + rect = rect.united(m_icon_widget->geometry()); + + rect = rect.united(m_left_widget_place_holder->geometry()); + + return rect.width(); +} + +int TitleBar::rightWidth() +{ + QRect rect; + + rect = rect.united(m_caption_buttons->geometry()); + + rect = rect.united(m_right_widget_place_holder->geometry()); + + int width = rect.width() + layoutSpacing(); + + return width; +} + +int TitleBar::layoutSpacing() +{ + return m_layout_spacing; +} + +bool TitleBar::isMinimizedButtonEnabled() +{ + if (!m_gw) + return false; + + return (m_gw->windowFlags() & Qt::WindowMinimizeButtonHint); +} + +bool TitleBar::isMaximizeButtonEnabled() +{ + if (!m_gw) + return false; + + return (m_gw->windowFlags() & Qt::WindowMaximizeButtonHint); +} + +bool TitleBar::isCloseButtonEnabled() +{ + if (!m_gw) + return false; + + return (m_gw->windowFlags() & Qt::WindowCloseButtonHint); +} + +QRect TitleBar::minimizeButtonRect() +{ + if (m_min_btn->isVisible()) + return m_min_btn->geometry(); + + return QRect(); +} + +QRect TitleBar::maximizeButtonRect() +{ + if (m_max_btn->isVisible()) + return m_max_btn->geometry(); + if (m_restore_btn->isVisible()) + return m_restore_btn->geometry(); + + return QRect(); +} + +QRect TitleBar::closeButtonRect() +{ + if (m_cls_btn->isVisible()) + return m_cls_btn->geometry(); + + return QRect(); +} + +void TitleBar::updateWindow() +{ + int left_width = leftWidth(); + + int right_width = rightWidth(); + + int distance = right_width - left_width; + + int left_distance = 0; + + int right_distance = 0; + + if (distance > 0) + left_distance = qAbs(distance); + else + right_distance = qAbs(distance); + + m_center_spacer_item_left->changeSize(left_distance, 0, QSizePolicy::Preferred, QSizePolicy::Expanding); + + m_center_spacer_item_right->changeSize(right_distance, 0, QSizePolicy::Preferred, QSizePolicy::Expanding); +} + +void TitleBar::captionButtonStateChanged(const QGoodWindow::CaptionButtonState &state) +{ + if (!m_caption_buttons->isVisible()) + return; + + switch (state) + { + // Hover enter + case QGoodWindow::CaptionButtonState::MinimizeHoverEnter: + { + m_min_btn->setState(QEvent::HoverEnter); + + break; + } + case QGoodWindow::CaptionButtonState::MaximizeHoverEnter: + { + if (!m_is_maximized) + m_max_btn->setState(QEvent::HoverEnter); + else + m_restore_btn->setState(QEvent::HoverEnter); + + break; + } + case QGoodWindow::CaptionButtonState::CloseHoverEnter: + { + m_cls_btn->setState(QEvent::HoverEnter); + + break; + } + // Hover leave + case QGoodWindow::CaptionButtonState::MinimizeHoverLeave: + { + m_min_btn->setState(QEvent::HoverLeave); + + break; + } + case QGoodWindow::CaptionButtonState::MaximizeHoverLeave: + { + if (!m_is_maximized) + m_max_btn->setState(QEvent::HoverLeave); + else + m_restore_btn->setState(QEvent::HoverLeave); + + break; + } + case QGoodWindow::CaptionButtonState::CloseHoverLeave: + { + m_cls_btn->setState(QEvent::HoverLeave); + + break; + } + // Mouse button press + case QGoodWindow::CaptionButtonState::MinimizePress: + { + m_min_btn->setState(QEvent::MouseButtonPress); + + break; + } + case QGoodWindow::CaptionButtonState::MaximizePress: + { + if (!m_is_maximized) + m_max_btn->setState(QEvent::MouseButtonPress); + else + m_restore_btn->setState(QEvent::MouseButtonPress); + + break; + } + case QGoodWindow::CaptionButtonState::ClosePress: + { + m_cls_btn->setState(QEvent::MouseButtonPress); + + break; + } + // Mouse button release + case QGoodWindow::CaptionButtonState::MinimizeRelease: + { + m_min_btn->setState(QEvent::MouseButtonRelease); + + break; + } + case QGoodWindow::CaptionButtonState::MaximizeRelease: + { + if (!m_is_maximized) + m_max_btn->setState(QEvent::MouseButtonRelease); + else + m_restore_btn->setState(QEvent::MouseButtonRelease); + + break; + } + case QGoodWindow::CaptionButtonState::CloseRelease: + { + m_cls_btn->setState(QEvent::MouseButtonRelease); + + break; + } + // Mouse button clicked + case QGoodWindow::CaptionButtonState::MinimizeClicked: + { + Q_EMIT m_min_btn->clicked(); + + break; + } + case QGoodWindow::CaptionButtonState::MaximizeClicked: + { + if (!m_is_maximized) + Q_EMIT m_max_btn->clicked(); + else + Q_EMIT m_restore_btn->clicked(); + + break; + } + case QGoodWindow::CaptionButtonState::CloseClicked: + { + Q_EMIT m_cls_btn->clicked(); + + break; + } + default: + break; + } +} diff --git a/lib/QGoodWindow/QGoodCentralWidget/src/titlebar.h b/lib/QGoodWindow/QGoodCentralWidget/src/titlebar.h new file mode 100644 index 00000000..f6e845a5 --- /dev/null +++ b/lib/QGoodWindow/QGoodCentralWidget/src/titlebar.h @@ -0,0 +1,104 @@ +/* +The MIT License (MIT) + +Copyright © 2018-2023 Antonio Dias (https://github.com/antonypro) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef TITLEBAR_H +#define TITLEBAR_H + +#include +#include +#include +#include +#include "iconwidget.h" +#include "titlewidget.h" +#include "captionbutton.h" + +//\cond HIDDEN_SYMBOLS +class TitleBar : public QFrame +{ + Q_OBJECT +public: + explicit TitleBar(QGoodWindow *gw, QWidget *parent = nullptr); + +Q_SIGNALS: + void showMinimized(); + void showNormal(); + void showMaximized(); + void closeWindow(); + +public Q_SLOTS: + void setTitle(const QString &title); + void setIcon(const QPixmap &icon); + void setActive(bool active); + void setTitleAlignment(const Qt::Alignment &alignment); + void setMaximized(bool maximized); + void setTheme(); + bool eventFilter(QObject *obj, QEvent *event); + void setLeftTitleBarWidget(QWidget *widget); + void setRightTitleBarWidget(QWidget *widget); + void setCenterTitleBarWidget(QWidget *widget); + void setCaptionButtonWidth(int width); + Qt::Alignment titleAlignment(); + int captionButtonsWidth(); + int leftWidth(); + int rightWidth(); + int layoutSpacing(); + bool isMinimizedButtonEnabled(); + bool isMaximizeButtonEnabled(); + bool isCloseButtonEnabled(); + QRect minimizeButtonRect(); + QRect maximizeButtonRect(); + QRect closeButtonRect(); + void updateWindow(); + void captionButtonStateChanged(const QGoodWindow::CaptionButtonState &state); + +private: + QPointer m_gw; + QWidget *m_left_margin_widget_place_holder; + IconWidget *m_icon_widget; + TitleWidget *m_title_widget; + QWidget *m_caption_buttons; + CaptionButton *m_min_btn; + CaptionButton *m_restore_btn; + CaptionButton *m_max_btn; + CaptionButton *m_cls_btn; + QPointer m_left_widget; + QPointer m_right_widget; + QPointer m_center_widget; + QPointer m_left_widget_place_holder; + QPointer m_right_widget_place_holder; + QPointer m_center_widget_place_holder; + QSpacerItem *m_center_spacer_item_left; + QSpacerItem *m_center_spacer_item_right; + int m_layout_spacing; + QString m_style; + bool m_active; + bool m_is_maximized; + QColor m_title_bar_color; + + friend class QGoodCentralWidget; + friend class TitleWidget; +}; +//\endcond + +#endif // TITLEBAR_H diff --git a/lib/QGoodWindow/QGoodCentralWidget/src/titlewidget.cpp b/lib/QGoodWindow/QGoodCentralWidget/src/titlewidget.cpp new file mode 100644 index 00000000..427dcb54 --- /dev/null +++ b/lib/QGoodWindow/QGoodCentralWidget/src/titlewidget.cpp @@ -0,0 +1,207 @@ +/* +The MIT License (MIT) + +Copyright © 2018-2023 Antonio Dias (https://github.com/antonypro) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "titlewidget.h" +#include "titlebar.h" + +TitleWidget::TitleWidget(TitleBar *title_bar, QWidget *parent) : QWidget(parent) +{ + m_title_bar = title_bar; + + m_active = false; + m_alignment = Qt::AlignLeft; +} + +void TitleWidget::setText(const QString &text) +{ + m_title = text; + update(); +} + +void TitleWidget::setActive(bool active) +{ + m_active = active; + update(); +} + +void TitleWidget::setTitleAlignment(const Qt::Alignment &alignment) +{ + switch (alignment) + { + case Qt::AlignLeft: + case Qt::AlignCenter: + case Qt::AlignRight: + { + m_alignment = alignment; + break; + } + default: + { + m_alignment = Qt::AlignLeft; + break; + } + } + + update(); +} + +void TitleWidget::setTitleColor(const QColor &active_color, const QColor &inactive_color) +{ + m_active_color = active_color; + m_inactive_color = inactive_color; + + update(); +} + +Qt::Alignment TitleWidget::titleAlignment() +{ + return m_alignment; +} + +void TitleWidget::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + + const int spacing = m_title_bar->layoutSpacing(); + + QRect left_rect; + QRect right_rect; + QRect center_rect; + + if (m_title_bar->m_left_margin_widget_place_holder->isVisible()) + left_rect = left_rect.united(m_title_bar->m_left_margin_widget_place_holder->geometry()); + if (m_title_bar->m_icon_widget->isVisible()) + left_rect = left_rect.united(m_title_bar->m_icon_widget->geometry()); + if (m_title_bar->m_left_widget_place_holder->isVisible()) + left_rect = left_rect.united(m_title_bar->m_left_widget_place_holder->geometry()); + + if (m_title_bar->m_right_widget_place_holder->isVisible()) + right_rect = right_rect.united(m_title_bar->m_right_widget_place_holder->geometry()); + if (m_title_bar->m_caption_buttons->isVisible()) + right_rect = right_rect.united(m_title_bar->m_caption_buttons->geometry()); + else + right_rect = right_rect.united(QRect(width(), 0, 1, height())); + + if (m_title_bar->m_center_widget_place_holder->isVisible()) + center_rect = center_rect.united(m_title_bar->m_center_widget_place_holder->geometry()); + + QPainter painter(this); + painter.setRenderHints(QPainter::Antialiasing); + + QFont font = this->font(); +#ifdef Q_OS_WIN + font.setFamily("Segoe UI"); +#else + font.setFamily(qApp->font().family()); +#endif + + painter.setFont(font); + + QPen pen; + pen.setColor(m_active ? m_active_color : m_inactive_color); + + painter.setPen(pen); + + bool center_widget_visible = m_title_bar->m_center_widget_place_holder->isVisible(); + + Qt::Alignment alignment = m_alignment; + + if (center_widget_visible && alignment == Qt::AlignCenter) + alignment = Qt::AlignLeft; + + QFontMetrics metrics(painter.font()); + + int title_space_width; + + if (center_widget_visible) + { + if (alignment == Qt::AlignLeft) + title_space_width = center_rect.left() - left_rect.right(); + else + title_space_width = right_rect.left() - center_rect.right(); + } + else + { + title_space_width = right_rect.left() - left_rect.right(); + } + + QString title_elided = metrics.elidedText(m_title, Qt::ElideRight, title_space_width - spacing); + + QSize title_size = metrics.size(0, title_elided); + + int title_width = title_size.width(); + int title_height = title_size.height(); + + QRect title_rect = QRect(0, 0, title_width, title_height); + + switch (alignment) + { + case Qt::AlignLeft: + { + title_rect.setTop((height() - title_height) / 2); + + title_rect.setHeight(title_height); + + title_rect.setLeft(left_rect.right()); + title_rect.setRight(left_rect.right() + title_width); + + break; + } + case Qt::AlignRight: + { + title_rect.setTop((height() - title_height) / 2); + + title_rect.setHeight(title_height); + + title_rect.setLeft(right_rect.left() - title_width - spacing); + title_rect.setRight(right_rect.left()); + + break; + } + case Qt::AlignCenter: + { + title_rect.setWidth(title_width + spacing); + title_rect.setHeight(title_height); + + title_rect.moveTop((height() - title_height) / 2); + title_rect.moveLeft((right_rect.left() - left_rect.right())/2 + left_rect.right() - title_width/2); + + bool left_collide = (title_rect.left() < left_rect.right()); + bool right_collide = (title_rect.right() > right_rect.left()); + if (left_collide) { + if(m_title != title_elided) { + title_rect.moveLeft(left_rect.right()); + } + } else if (right_collide) { + title_rect.moveRight(right_rect.left()); + } + + break; + } + default: + break; + } + + painter.drawText(title_rect, title_elided); +} diff --git a/lib/QGoodWindow/QGoodCentralWidget/src/titlewidget.h b/lib/QGoodWindow/QGoodCentralWidget/src/titlewidget.h new file mode 100644 index 00000000..b79d1d63 --- /dev/null +++ b/lib/QGoodWindow/QGoodCentralWidget/src/titlewidget.h @@ -0,0 +1,62 @@ +/* +The MIT License (MIT) + +Copyright © 2018-2023 Antonio Dias (https://github.com/antonypro) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef TITLEWIDGET_H +#define TITLEWIDGET_H + +#include +#include +#include + +class TitleBar; + +//\cond HIDDEN_SYMBOLS +class TitleWidget : public QWidget +{ + Q_OBJECT +public: + explicit TitleWidget(TitleBar *title_bar, QWidget *parent = nullptr); + +public Q_SLOTS: + void setText(const QString &text); + void setActive(bool active); + void setTitleAlignment(const Qt::Alignment &alignment); + void setTitleColor(const QColor &active_color, const QColor &inactive_color); + Qt::Alignment titleAlignment(); + +private: + //Functions + void paintEvent(QPaintEvent *event); + + //Variables + QPointer m_title_bar; + QString m_title; + bool m_active; + Qt::Alignment m_alignment; + QColor m_active_color; + QColor m_inactive_color; +}; +//\endcond + +#endif // TITLEWIDGET_H diff --git a/lib/QGoodWindow/QGoodWindow/QGoodWindow b/lib/QGoodWindow/QGoodWindow/QGoodWindow new file mode 100644 index 00000000..3e543d6c --- /dev/null +++ b/lib/QGoodWindow/QGoodWindow/QGoodWindow @@ -0,0 +1,25 @@ +/* +The MIT License (MIT) + +Copyright © 2021-2023 Antonio Dias (https://github.com/antonypro) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "src/qgoodwindow.h" diff --git a/lib/QGoodWindow/QGoodWindow/QGoodWindow.pri b/lib/QGoodWindow/QGoodWindow/QGoodWindow.pri new file mode 100644 index 00000000..8624618e --- /dev/null +++ b/lib/QGoodWindow/QGoodWindow/QGoodWindow.pri @@ -0,0 +1,133 @@ +#The MIT License (MIT) + +#Copyright © 2021-2023 Antonio Dias (https://github.com/antonypro) + +#Permission is hereby granted, free of charge, to any person obtaining a copy +#of this software and associated documentation files (the "Software"), to deal +#in the Software without restriction, including without limitation the rights +#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +#copies of the Software, and to permit persons to whom the Software is +#furnished to do so, subject to the following conditions: + +#The above copyright notice and this permission notice shall be included in all +#copies or substantial portions of the Software. + +#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +#SOFTWARE. + +QT += core gui widgets + +win32:equals(QT_MAJOR_VERSION, 5){ +QT += winextras +} + +CONFIG += c++11 + +SOURCES += \ + $$PWD/src/qgoodwindow.cpp \ + $$PWD/src/qgoodstateholder.cpp \ + $$PWD/src/lightstyle.cpp \ + $$PWD/src/darkstyle.cpp \ + $$PWD/src/stylecommon.cpp + +HEADERS += \ + $$PWD/src/qgoodwindow.h \ + $$PWD/src/qgoodstateholder.h \ + $$PWD/src/intcommon.h \ + $$PWD/src/lightstyle.h \ + $$PWD/src/darkstyle.h \ + $$PWD/src/stylecommon.h + +RESOURCES += \ + $$PWD/src/qgoodwindow_style.qrc + +INCLUDEPATH += $$PWD #include + +equals(QT_MAJOR_VERSION, 5){ +DEFINES += QT_VERSION_QT5 +} +equals(QT_MAJOR_VERSION, 6) { +DEFINES += QT_VERSION_QT6 +} + +win32 { +LIBS += -lUser32 -lGdi32 +} + +!no_qgoodwindow{ + +win32 { #Windows +SOURCES += \ + $$PWD/src/shadow.cpp \ + $$PWD/src/qgooddialog.cpp + +HEADERS += \ + $$PWD/src/common.h \ + $$PWD/src/shadow.h \ + $$PWD/src/qgooddialog.h + +DEFINES += QGOODWINDOW +CONFIG += qgoodwindow +} #Windows + +unix:!mac:!android { #Linux +QT += testlib + +equals(QT_MAJOR_VERSION, 5){ +QT += x11extras +} + +equals(QT_MAJOR_VERSION, 6){ +QT += gui-private +} + +SOURCES += \ + $$PWD/src/shadow.cpp \ + $$PWD/src/qgooddialog.cpp + +HEADERS += \ + $$PWD/src/shadow.h \ + $$PWD/src/qgooddialog.h + +QMAKE_CXXFLAGS += -Wno-deprecated-declarations + +LIBS += -lX11 + +CONFIG += link_pkgconfig + +equals(QT_MAJOR_VERSION, 5){ +PKGCONFIG += gtk+-2.0 +} +equals(QT_MAJOR_VERSION, 6){ +PKGCONFIG += gtk+-3.0 +} + +DEFINES += QGOODWINDOW +CONFIG += qgoodwindow +} #Linux + +mac { #macOS +OBJECTIVE_SOURCES += \ + $$PWD/src/macosnative.mm + +SOURCES += \ + $$PWD/src/notification.cpp \ + $$PWD/src/qgooddialog.cpp + +HEADERS += \ + $$PWD/src/macosnative.h \ + $$PWD/src/notification.h \ + $$PWD/src/qgooddialog.h + +LIBS += -framework Foundation -framework Cocoa -framework AppKit + +DEFINES += QGOODWINDOW +CONFIG += qgoodwindow +} #macOS + +}#!no_qgoodwindow diff --git a/lib/QGoodWindow/QGoodWindow/lib-helper/qgoodwindow_global.h b/lib/QGoodWindow/QGoodWindow/lib-helper/qgoodwindow_global.h new file mode 100644 index 00000000..d0c88ab6 --- /dev/null +++ b/lib/QGoodWindow/QGoodWindow/lib-helper/qgoodwindow_global.h @@ -0,0 +1,48 @@ +/* +The MIT License (MIT) + +Copyright © 2023 Antonio Dias (https://github.com/antonypro) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef QGOODWINDOW_GLOBAL_H +#define QGOODWINDOW_GLOBAL_H + +#include + +#include "qgoodwindow_helper.h" + +#ifndef QGOODWINDOW_LIBRARY +#define QGOODWINDOW_LIBRARY //Using shared library +#endif + +#ifndef QGOODWINDOW_SHARED_EXPORT +#ifdef QGOODWINDOW_LIBRARY +#ifdef QGOODWINDOW_SHARED_LIBRARY +#define QGOODWINDOW_SHARED_EXPORT Q_DECL_EXPORT +#else +#define QGOODWINDOW_SHARED_EXPORT Q_DECL_IMPORT +#endif +#else +#define QGOODWINDOW_SHARED_EXPORT +#endif +#endif + +#endif // QGOODWINDOW_GLOBAL_H diff --git a/lib/QGoodWindow/QGoodWindow/lib-helper/qgoodwindow_helper.h b/lib/QGoodWindow/QGoodWindow/lib-helper/qgoodwindow_helper.h new file mode 100644 index 00000000..a25bc309 --- /dev/null +++ b/lib/QGoodWindow/QGoodWindow/lib-helper/qgoodwindow_helper.h @@ -0,0 +1,46 @@ +/* +The MIT License (MIT) + +Copyright © 2023 Antonio Dias (https://github.com/antonypro) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef QGOODWINDOW_HELPER_H +#define QGOODWINDOW_HELPER_H + +#include + +#ifndef QGOODWINDOW +#define QGOODWINDOW +#endif + +#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0) +#ifndef QT_VERSION_QT5 +#define QT_VERSION_QT5 +#endif +#endif + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) && QT_VERSION < QT_VERSION_CHECK(7, 0, 0) +#ifndef QT_VERSION_QT6 +#define QT_VERSION_QT6 +#endif +#endif + +#endif // QGOODWINDOW_HELPER_H diff --git a/lib/QGoodWindow/QGoodWindow/src/common.h b/lib/QGoodWindow/QGoodWindow/src/common.h new file mode 100644 index 00000000..4064b6e0 --- /dev/null +++ b/lib/QGoodWindow/QGoodWindow/src/common.h @@ -0,0 +1,89 @@ +/* +The MIT License (MIT) + +Copyright © 2018-2023 Antonio Dias (https://github.com/antonypro) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef COMMON_H +#define COMMON_H + +#ifdef _WIN32 + +#ifdef _WIN32_WINNT +#undef _WIN32_WINNT +#endif + +#define _WIN32_WINNT _WIN32_WINNT_VISTA + +#include + +inline int BORDERWIDTH() +{ + return (GetSystemMetrics(SM_CXFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER)); +} + +inline int BORDERHEIGHT() +{ + return (GetSystemMetrics(SM_CXFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER)); +} + +#ifndef WM_DPICHANGED +#define WM_DPICHANGED 0x02E0 +#endif + +#endif + +#include +#include +#include + +#ifdef Q_OS_LINUX + +#define BORDERWIDTH 10 //PIXELS + +#define BORDERWIDTHDPI BORDERWIDTH + +#define MOVERESIZE_MOVE 8 //X11 Fixed Value + +#endif + +#if defined Q_OS_LINUX || defined Q_OS_MAC +//The positive values are mandatory on Linux and arbitrary on macOS, +//using the same for convenience. +//The negative values are arbitrary on both platforms. + +#define HTNOWHERE -1 +#define HTMINBUTTON -2 +#define HTMAXBUTTON -3 +#define HTCLOSE -4 +#define HTTOPLEFT 0 +#define HTTOP 1 +#define HTTOPRIGHT 2 +#define HTLEFT 7 +#define HTRIGHT 3 +#define HTBOTTOMLEFT 6 +#define HTBOTTOM 5 +#define HTBOTTOMRIGHT 4 +#define HTCAPTION 8 + +#endif + +#endif // COMMON_H diff --git a/lib/QGoodWindow/QGoodWindow/src/darkstyle.cpp b/lib/QGoodWindow/QGoodWindow/src/darkstyle.cpp new file mode 100644 index 00000000..8d051259 --- /dev/null +++ b/lib/QGoodWindow/QGoodWindow/src/darkstyle.cpp @@ -0,0 +1,136 @@ +/* +The MIT License (MIT) + +Copyright © 2018, Juergen Skrotzky (https://github.com/Jorgen-VikingGod, JorgenVikingGod@gmail.com) +Copyright © 2022-2023 Antonio Dias (https://github.com/antonypro) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "darkstyle.h" +#include "stylecommon.h" + +DarkStyle::DarkStyle() : DarkStyle(styleBase()) +{ +#ifdef Q_OS_WIN + m_hash_pixmap_cache[SP_MessageBoxInformation] = StyleCommon::winStandardPixmap(SP_MessageBoxInformation); + m_hash_pixmap_cache[SP_MessageBoxWarning] = StyleCommon::winStandardPixmap(SP_MessageBoxWarning); + m_hash_pixmap_cache[SP_MessageBoxCritical] = StyleCommon::winStandardPixmap(SP_MessageBoxCritical); + m_hash_pixmap_cache[SP_MessageBoxQuestion] = StyleCommon::winStandardPixmap(SP_MessageBoxQuestion); +#endif +} + +DarkStyle::DarkStyle(QStyle *style) : QProxyStyle(style) +{ + +} + +DarkStyle::~DarkStyle() +{ + +} + +QStyle *DarkStyle::styleBase() const +{ + QStyle *base = QStyleFactory::create(QStringLiteral("Fusion")); + return base; +} + +QIcon DarkStyle::standardIcon(StandardPixmap standardPixmap, const QStyleOption *option, const QWidget *widget) const +{ +#ifdef Q_OS_WIN + switch (standardPixmap) + { + case SP_MessageBoxInformation: + case SP_MessageBoxWarning: + case SP_MessageBoxCritical: + case SP_MessageBoxQuestion: + { + QPixmap pixmap = m_hash_pixmap_cache.value(standardPixmap, QPixmap()); + + if (!pixmap.isNull()) + return QIcon(pixmap); + + break; + } + default: + break; + } +#endif + + return QProxyStyle::standardIcon(standardPixmap, option, widget); +} + +void DarkStyle::polish(QPalette &palette) +{ + palette.setColor(QPalette::Window, QColor(53, 53, 53)); + palette.setColor(QPalette::WindowText, QColor(255, 255, 255)); + palette.setColor(QPalette::Disabled, QPalette::WindowText, QColor(127, 127, 127)); + palette.setColor(QPalette::Base, QColor(42, 42, 42)); + palette.setColor(QPalette::AlternateBase, QColor(66, 66, 66)); + palette.setColor(QPalette::ToolTipBase, QColor(255, 255, 255)); + palette.setColor(QPalette::ToolTipText, QColor(255, 255, 255)); + palette.setColor(QPalette::Text, QColor(255, 255, 255)); + palette.setColor(QPalette::Disabled, QPalette::Text, QColor(127, 127, 127)); + palette.setColor(QPalette::Dark, QColor(35, 35, 35)); + palette.setColor(QPalette::Shadow, QColor(20, 20, 20)); + palette.setColor(QPalette::Button, QColor(53, 53, 53)); + palette.setColor(QPalette::ButtonText, QColor(255, 255, 255)); + palette.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(127, 127, 127)); + palette.setColor(QPalette::BrightText, QColor(255, 0, 0)); + palette.setColor(QPalette::Link, QColor(42, 130, 218)); + palette.setColor(QPalette::Highlight, QColor(42, 130, 218)); + palette.setColor(QPalette::Disabled, QPalette::Highlight, QColor(80, 80, 80)); + palette.setColor(QPalette::HighlightedText, QColor(255, 255, 255)); + palette.setColor(QPalette::Disabled, QPalette::HighlightedText, QColor(127, 127, 127)); +} + +void DarkStyle::polish(QApplication *app) +{ + if (!app) + return; + + QFont defaultFont = app->font(); + defaultFont.setPointSize(defaultFont.pointSize() + 2); + app->setFont(defaultFont); + + QFile file(QStringLiteral(":/darkstyle.qss")); + + if (file.open(QFile::ReadOnly | QFile::Text)) + { + QString style_sheet = QLatin1String(file.readAll()); + app->setStyleSheet(style_sheet); + file.close(); + } +} + +void DarkStyle::unpolish(QApplication *app) +{ + if (!app) + return; + + QFont defaultFont = app->font(); + defaultFont.setPointSize(defaultFont.pointSize() - 2); + app->setFont(defaultFont); +} + +QStyle *DarkStyle::baseStyle() const +{ + return styleBase(); +} diff --git a/lib/QGoodWindow/QGoodWindow/src/darkstyle.h b/lib/QGoodWindow/QGoodWindow/src/darkstyle.h new file mode 100644 index 00000000..ebb16cfe --- /dev/null +++ b/lib/QGoodWindow/QGoodWindow/src/darkstyle.h @@ -0,0 +1,65 @@ +/* +The MIT License (MIT) + +Copyright © 2018, Juergen Skrotzky (https://github.com/Jorgen-VikingGod, JorgenVikingGod@gmail.com) +Copyright © 2022-2023 Antonio Dias (https://github.com/antonypro) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef DARKSTYLE +#define DARKSTYLE + +#include +#include +#include + +//\cond HIDDEN_SYMBOLS +class DarkStyle : public QProxyStyle +{ + Q_OBJECT +public: + explicit DarkStyle(); + + DarkStyle(QStyle *style); + + ~DarkStyle(); + + QStyle *styleBase() const; + + QIcon standardIcon(StandardPixmap standardPixmap, const QStyleOption *option, const QWidget *widget) const; + + void polish(QPalette &palette); + + void polish(QApplication *app); + + void unpolish(QApplication *app); + +private: + //Functions + QStyle *baseStyle() const; + + //Variables +#ifdef Q_OS_WIN + QHash m_hash_pixmap_cache; +#endif +}; +//\endcond + +#endif // DARKSTYLE diff --git a/lib/QGoodWindow/QGoodWindow/src/darkstyle.qss b/lib/QGoodWindow/QGoodWindow/src/darkstyle.qss new file mode 100644 index 00000000..e6485d62 --- /dev/null +++ b/lib/QGoodWindow/QGoodWindow/src/darkstyle.qss @@ -0,0 +1,309 @@ +QToolTip{ + color:palette(text); + background-color:palette(base); + border:1px solid palette(highlight); + border-radius:0px; +} +QStatusBar{ + background-color:qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75)); + color:palette(mid); +} +QMenuBar{ + background-color:transparent; + spacing:0px; +} +QMenuBar::item{ + spacing:2px; + padding:0px 4px; + background:transparent; +} +QMenuBar::item:selected{ + background-color:rgba(80,80,80,255); +} +QMenuBar::item:pressed{ + background-color:palette(highlight); + color:rgba(255,255,255,255); + border-left:1px solid rgba(25,25,25,127); + border-right:1px solid rgba(25,25,25,127); +} +QMenu{ + background-color:palette(window); + border:1px solid palette(shadow); +} +QMenu::item{ + padding:3px 15px 3px 15px; + border:1px solid transparent; +} +QMenu::item:disabled{ + background-color:rgba(35,35,35,127); + color:palette(disabled); +} +QMenu::item:selected{ + border-color:rgba(147,191,236,127); + background:palette(highlight); + color:rgba(255,255,255,255); +} +QMenu::icon:checked{ + background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75)); + border:1px solid palette(highlight); + border-radius:2px; +} +QMenu::separator{ + height:1px; + background:palette(alternate-base); + margin-left:5px; + margin-right:5px; +} +QMenu::indicator{ + width:15px; + height:15px; +} +QMenu::indicator:non-exclusive:checked{ + padding-left:2px; +} +QMenu::indicator:non-exclusive:unchecked{ + padding-left:2px; +} +QMenu::indicator:exclusive:checked{ + padding-left:2px; +} +QMenu::indicator:exclusive:unchecked{ + padding-left:2px; +} +QToolBar::top{ + background-color:qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75)); + border-bottom:3px solid qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75)); +} +QToolBar::bottom{ + background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75)); + border-top:3px solid qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75)); +} +QToolBar::left{ + background-color:qlineargradient(x1:0,y1:0,x2:1,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75)); + border-right:3px solid qlineargradient(x1:0,y1:0,x2:1,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75)); +} +QToolBar::right{ + background-color:qlineargradient(x1:1,y1:0,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75)); + border-left:3px solid qlineargradient(x1:1,y1:0,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75)); +} +QMainWindow::separator{ + width:6px; + height:5px; + padding:2px; +} +QComboBox QAbstractItemView { + border: 1px solid palette(shadow); +} +QComboBox:item:selected { + border: none; + background:palette(highlight); + color:rgba(255,255,255,255); +} +QComboBox::indicator{ + background-color:transparent; + selection-background-color:transparent; + color:transparent; + selection-color:transparent; +} +QSplitter::handle:horizontal{ + width:10px; +} +QSplitter::handle:vertical{ + height:10px; +} +QMainWindow::separator:hover,QSplitter::handle:hover{ + background:palette(highlight); +} +QDockWidget::title{ + padding:4px; + background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75)); + border:1px solid rgba(25,25,25,75); + border-bottom:2px solid rgba(25,25,25,75); +} +QDockWidget{ + titlebar-close-icon:url(:/darkstyle/icon_close.png); + titlebar-normal-icon:url(:/darkstyle/icon_restore.png); +} +QDockWidget::close-button,QDockWidget::float-button{ + subcontrol-position:top right; + subcontrol-origin:margin; + position:absolute; + top:3px; + bottom:0px; + width:20px; + height:20px; +} +QDockWidget::close-button{ + right:3px; +} +QDockWidget::float-button{ + right:25px; +} +QGroupBox{ + background-color:rgba(66,66,66,50%); + margin-top:27px; + border:1px solid rgba(25,25,25,127); + border-radius:4px; +} +QGroupBox::title{ + subcontrol-origin:margin; + subcontrol-position:left top; + padding:4px 6px; + margin-left:3px; + background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75)); + border:1px solid rgba(25,25,25,75); + border-bottom:2px solid rgb(127,127,127); + border-top-left-radius:4px; + border-top-right-radius:4px; +} +QTabWidget::pane{ + background-color:rgba(66,66,66,50%); + border-top:1px solid rgba(25,25,25,50%); +} +QTabWidget::tab-bar{ + left:3px; + top:1px; +} +QTabBar{ + background-color:transparent; + qproperty-drawBase:0; + border-bottom:1px solid rgba(25,25,25,50%); +} +QTabBar::tab{ + padding:4px 6px; + background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75)); + border:1px solid rgba(25,25,25,75); + border-top-left-radius:4px; + border-top-right-radius:4px; +} +QTabBar::tab:selected,QTabBar::tab:hover{ + background-color:qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0 rgba(53,53,53,127),stop:1 rgba(66,66,66,50%)); + border-bottom-color:rgba(66,66,66,75%); +} +QTabBar::tab:selected{ + border-bottom:2px solid palette(highlight); +} +QTabBar::tab::selected:disabled{ + border-bottom:2px solid rgb(127,127,127); +} +QTabBar::tab:!selected{ + margin-top:2px; +} +QTabBar::close-button { + border:1px solid transparent; + border-radius:2px; +} +QTabBar::close-button:hover { + background-color:qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0 rgba(106,106,106,255),stop:1 rgba(106,106,106,75)); + border:1px solid palette(base); +} +QTabBar::close-button:pressed { + background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75)); + border:1px solid palette(base); +} +QCheckBox::indicator{ + width:18px; + height:18px; +} +QRadioButton::indicator{ + width:18px; + height:18px; +} +QTreeView, QTableView{ + alternate-background-color:palette(window); + background:palette(base); +} +QTreeView QHeaderView::section, QTableView QHeaderView::section{ + background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(25,25,25,127),stop:1 rgba(53,53,53,75)); + border-style:none; + border-bottom:1px solid palette(dark); + padding-left:5px; + padding-right:5px; +} +QTreeView::item:selected:disabled, QTableView::item:selected:disabled{ + background:rgb(80,80,80); +} +QTreeView::branch{ + background-color:palette(base); +} +QTreeView::branch:has-children:!has-siblings:closed, +QTreeView::branch:closed:has-children:has-siblings{ + border-image:none; +} +QTreeView::branch:open:has-children:!has-siblings, +QTreeView::branch:open:has-children:has-siblings{ + border-image:none; +} +QScrollBar:vertical{ + background:palette(base); + border-top-right-radius:2px; + border-bottom-right-radius:2px; + width:16px; + margin:0px; +} +QScrollBar::handle:vertical{ + background-color:palette(alternate-base); + border-radius:2px; + min-height:20px; + margin:2px 4px 2px 4px; +} +QScrollBar::handle:vertical:hover{ + background-color:palette(highlight); +} +QScrollBar::add-line:vertical{ + background:none; + height:0px; + subcontrol-position:right; + subcontrol-origin:margin; +} +QScrollBar::sub-line:vertical{ + background:none; + height:0px; + subcontrol-position:left; + subcontrol-origin:margin; +} +QScrollBar:horizontal{ + background:palette(base); + height:16px; + margin:0px; +} +QScrollBar::handle:horizontal{ + background-color:palette(alternate-base); + border-radius:2px; + min-width:20px; + margin:4px 2px 4px 2px; +} +QScrollBar::handle:horizontal:hover{ + background-color:palette(highlight); +} +QScrollBar::add-line:horizontal{ + background:none; + width:0px; + subcontrol-position:bottom; + subcontrol-origin:margin; +} +QScrollBar::sub-line:horizontal{ + background:none; + width:0px; + subcontrol-position:top; + subcontrol-origin:margin; +} +QSlider::handle:horizontal{ + border-radius:4px; + border:1px solid rgba(25,25,25,255); + background-color:palette(alternate-base); + min-height:20px; + margin:0 -4px; +} +QSlider::handle:horizontal:hover{ + background:palette(highlight); +} +QSlider::add-page:horizontal{ + background:palette(base); +} +QSlider::sub-page:horizontal{ + background:palette(highlight); +} +QSlider::sub-page:horizontal:disabled{ + background:rgb(80,80,80); +} diff --git a/lib/QGoodWindow/QGoodWindow/src/intcommon.h b/lib/QGoodWindow/QGoodWindow/src/intcommon.h new file mode 100644 index 00000000..9401607c --- /dev/null +++ b/lib/QGoodWindow/QGoodWindow/src/intcommon.h @@ -0,0 +1,37 @@ +/* +The MIT License (MIT) + +Copyright © 2023 Antonio Dias (https://github.com/antonypro) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef INTCOMMON +#define INTCOMMON + +#include "qgoodwindow_helper.h" + +#ifdef QT_VERSION_QT5 +typedef long qgoodintptr; +#endif +#ifdef QT_VERSION_QT6 +typedef qintptr qgoodintptr; +#endif + +#endif // INTCOMMON diff --git a/lib/QGoodWindow/QGoodWindow/src/lightstyle.cpp b/lib/QGoodWindow/QGoodWindow/src/lightstyle.cpp new file mode 100644 index 00000000..6e2cdaf1 --- /dev/null +++ b/lib/QGoodWindow/QGoodWindow/src/lightstyle.cpp @@ -0,0 +1,136 @@ +/* +The MIT License (MIT) + +Copyright © 2018, Juergen Skrotzky (https://github.com/Jorgen-VikingGod, JorgenVikingGod@gmail.com) +Copyright © 2022-2023 Antonio Dias (https://github.com/antonypro) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "lightstyle.h" +#include "stylecommon.h" + +LightStyle::LightStyle() : LightStyle(styleBase()) +{ +#ifdef Q_OS_WIN + m_hash_pixmap_cache[SP_MessageBoxInformation] = StyleCommon::winStandardPixmap(SP_MessageBoxInformation); + m_hash_pixmap_cache[SP_MessageBoxWarning] = StyleCommon::winStandardPixmap(SP_MessageBoxWarning); + m_hash_pixmap_cache[SP_MessageBoxCritical] = StyleCommon::winStandardPixmap(SP_MessageBoxCritical); + m_hash_pixmap_cache[SP_MessageBoxQuestion] = StyleCommon::winStandardPixmap(SP_MessageBoxQuestion); +#endif +} + +LightStyle::LightStyle(QStyle *style) : QProxyStyle(style) +{ + +} + +LightStyle::~LightStyle() +{ + +} + +QStyle *LightStyle::styleBase() const +{ + QStyle *base = QStyleFactory::create(QStringLiteral("Fusion")); + return base; +} + +QIcon LightStyle::standardIcon(StandardPixmap standardPixmap, const QStyleOption *option, const QWidget *widget) const +{ +#ifdef Q_OS_WIN + switch (standardPixmap) + { + case SP_MessageBoxInformation: + case SP_MessageBoxWarning: + case SP_MessageBoxCritical: + case SP_MessageBoxQuestion: + { + QPixmap pixmap = m_hash_pixmap_cache.value(standardPixmap, QPixmap()); + + if (!pixmap.isNull()) + return QIcon(pixmap); + + break; + } + default: + break; + } +#endif + + return QProxyStyle::standardIcon(standardPixmap, option, widget); +} + +void LightStyle::polish(QPalette &palette) +{ + palette.setColor(QPalette::Window, QColor(240, 240, 240)); + palette.setColor(QPalette::WindowText, QColor(0, 0, 0)); + palette.setColor(QPalette::Disabled, QPalette::WindowText, QColor(120, 120, 120)); + palette.setColor(QPalette::Base, QColor(255, 255, 255)); + palette.setColor(QPalette::AlternateBase, QColor(233, 231, 227)); + palette.setColor(QPalette::ToolTipBase, QColor(255, 255, 220)); + palette.setColor(QPalette::ToolTipText, QColor(0, 0, 0)); + palette.setColor(QPalette::Text, QColor(0, 0, 0)); + palette.setColor(QPalette::Disabled, QPalette::Text, QColor(120, 120, 120)); + palette.setColor(QPalette::Dark, QColor(160, 160, 160)); + palette.setColor(QPalette::Shadow, QColor(105, 105, 105)); + palette.setColor(QPalette::Button, QColor(240, 240, 240)); + palette.setColor(QPalette::ButtonText, QColor(0, 0, 0)); + palette.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(120, 120, 120)); + palette.setColor(QPalette::BrightText, QColor(0, 0, 255)); + palette.setColor(QPalette::Link, QColor(51, 153, 255)); + palette.setColor(QPalette::Highlight, QColor(0, 0, 255)); + palette.setColor(QPalette::Disabled, QPalette::Highlight, QColor(51, 153, 255)); + palette.setColor(QPalette::HighlightedText, QColor(255, 255, 255)); + palette.setColor(QPalette::Disabled, QPalette::HighlightedText, QColor(255, 255, 255)); +} + +void LightStyle::polish(QApplication *app) +{ + if (!app) + return; + + QFont defaultFont = app->font(); + defaultFont.setPointSize(defaultFont.pointSize() + 2); + app->setFont(defaultFont); + + QFile file(QStringLiteral(":/lightstyle.qss")); + + if (file.open(QIODevice::ReadOnly | QIODevice::Text)) + { + QString style_sheet = QString::fromLatin1(file.readAll()); + app->setStyleSheet(style_sheet); + file.close(); + } +} + +void LightStyle::unpolish(QApplication *app) +{ + if (!app) + return; + + QFont defaultFont = app->font(); + defaultFont.setPointSize(defaultFont.pointSize() - 2); + app->setFont(defaultFont); +} + +QStyle *LightStyle::baseStyle() const +{ + return styleBase(); +} diff --git a/lib/QGoodWindow/QGoodWindow/src/lightstyle.h b/lib/QGoodWindow/QGoodWindow/src/lightstyle.h new file mode 100644 index 00000000..95a619a2 --- /dev/null +++ b/lib/QGoodWindow/QGoodWindow/src/lightstyle.h @@ -0,0 +1,65 @@ +/* +The MIT License (MIT) + +Copyright © 2018, Juergen Skrotzky (https://github.com/Jorgen-VikingGod, JorgenVikingGod@gmail.com) +Copyright © 2022-2023 Antonio Dias + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef LIGHTSTYLE +#define LIGHTSTYLE + +#include +#include +#include + +//\cond HIDDEN_SYMBOLS +class LightStyle : public QProxyStyle +{ + Q_OBJECT +public: + explicit LightStyle(); + + LightStyle(QStyle *style); + + ~LightStyle(); + + QStyle *styleBase() const; + + QIcon standardIcon(StandardPixmap standardPixmap, const QStyleOption *option, const QWidget *widget) const; + + void polish(QPalette &palette); + + void polish(QApplication *app); + + void unpolish(QApplication *app); + +private: + //Functions + QStyle *baseStyle() const; + + //Variables +#ifdef Q_OS_WIN + QHash m_hash_pixmap_cache; +#endif +}; +//\endcond + +#endif // LIGHTSTYLE diff --git a/lib/QGoodWindow/QGoodWindow/src/lightstyle.qss b/lib/QGoodWindow/QGoodWindow/src/lightstyle.qss new file mode 100644 index 00000000..c4be8620 --- /dev/null +++ b/lib/QGoodWindow/QGoodWindow/src/lightstyle.qss @@ -0,0 +1,309 @@ +QToolTip{ + color:palette(text); + background-color:palette(base); + border:1px solid palette(highlight); + border-radius:0px; +} +QStatusBar{ + background-color:qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0 rgba(220,220,220,127),stop:1 rgba(200,200,200,75)); + color:palette(mid); +} +QMenuBar{ + background-color:transparent; + spacing:0px; +} +QMenuBar::item{ + spacing:2px; + padding:0px 4px; + background:transparent; +} +QMenuBar::item:selected{ + background-color:rgba(229,229,229,255); +} +QMenuBar::item:pressed{ + background-color:palette(highlight); + color:rgba(255,255,255,255); + border-left:1px solid rgba(220,220,220,127); + border-right:1px solid rgba(220,220,220,127); +} +QMenu{ + background-color:palette(window); + border:1px solid palette(shadow); +} +QMenu::item{ + padding:3px 15px 3px 15px; + border:1px solid transparent; +} +QMenu::item:disabled{ + background-color:rgba(175,175,175,127); + color:palette(disabled); +} +QMenu::item:selected{ + border-color:rgba(147,191,236,127); + background:palette(highlight); + color:rgba(255,255,255,255); +} +QMenu::icon:checked{ + background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(220,220,220,127),stop:1 rgba(200,200,200,75)); + border:1px solid palette(highlight); + border-radius:2px; +} +QMenu::separator{ + height:1px; + background:palette(alternate-base); + margin-left:5px; + margin-right:5px; +} +QMenu::indicator{ + width:15px; + height:15px; +} +QMenu::indicator:non-exclusive:checked{ + padding-left:2px; +} +QMenu::indicator:non-exclusive:unchecked{ + padding-left:2px; +} +QMenu::indicator:exclusive:checked{ + padding-left:2px; +} +QMenu::indicator:exclusive:unchecked{ + padding-left:2px; +} +QToolBar::top{ + background-color:qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0 rgba(220,220,220,127),stop:1 rgba(200,200,200,75)); + border-bottom:3px solid qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0 rgba(220,220,220,127),stop:1 rgba(200,200,200,75)); +} +QToolBar::bottom{ + background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(220,220,220,127),stop:1 rgba(200,200,200,75)); + border-top:3px solid qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(220,220,220,127),stop:1 rgba(200,200,200,75)); +} +QToolBar::left{ + background-color:qlineargradient(x1:0,y1:0,x2:1,y2:0,stop:0 rgba(220,220,220,127),stop:1 rgba(200,200,200,75)); + border-right:3px solid qlineargradient(x1:0,y1:0,x2:1,y2:0,stop:0 rgba(220,220,220,127),stop:1 rgba(200,200,200,75)); +} +QToolBar::right{ + background-color:qlineargradient(x1:1,y1:0,x2:0,y2:0,stop:0 rgba(220,220,220,127),stop:1 rgba(200,200,200,75)); + border-left:3px solid qlineargradient(x1:1,y1:0,x2:0,y2:0,stop:0 rgba(220,220,220,127),stop:1 rgba(200,200,200,75)); +} +QMainWindow::separator{ + width:6px; + height:5px; + padding:2px; +} +QComboBox QAbstractItemView { + border: 1px solid palette(shadow); +} +QComboBox:item:selected { + border: none; + background:palette(highlight); + color:rgba(255,255,255,255); +} +QComboBox::indicator{ + background-color:transparent; + selection-background-color:transparent; + color:transparent; + selection-color:transparent; +} +QSplitter::handle:horizontal{ + width:10px; +} +QSplitter::handle:vertical{ + height:10px; +} +QMainWindow::separator:hover,QSplitter::handle:hover{ + background:palette(highlight); +} +QDockWidget::title{ + padding:4px; + background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(220,220,220,127),stop:1 rgba(200,200,200,75)); + border:1px solid rgba(220,220,220,75); + border-bottom:2px solid rgba(220,220,220,75); +} +QDockWidget{ + titlebar-close-icon:url(:/darkstyle/icon_close.png); + titlebar-normal-icon:url(:/darkstyle/icon_restore.png); +} +QDockWidget::close-button,QDockWidget::float-button{ + subcontrol-position:top right; + subcontrol-origin:margin; + position:absolute; + top:3px; + bottom:0px; + width:20px; + height:20px; +} +QDockWidget::close-button{ + right:3px; +} +QDockWidget::float-button{ + right:25px; +} +QGroupBox{ + background-color:rgba(200,200,200,50%); + margin-top:27px; + border:1px solid rgba(220,220,220,127); + border-radius:4px; +} +QGroupBox::title{ + subcontrol-origin:margin; + subcontrol-position:left top; + padding:4px 6px; + margin-left:3px; + background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(220,220,220,127),stop:1 rgba(200,200,200,75)); + border:1px solid rgba(220,220,220,75); + border-bottom:2px solid rgb(127,127,127); + border-top-left-radius:4px; + border-top-right-radius:4px; +} +QTabWidget::pane{ + background-color:rgba(200,200,200,50%); + border-top:1px solid rgba(220,220,220,50%); +} +QTabWidget::tab-bar{ + left:3px; + top:1px; +} +QTabBar{ + background-color:transparent; + qproperty-drawBase:0; + border-bottom:1px solid rgba(220,220,220,50%); +} +QTabBar::tab{ + padding:4px 6px; + background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(220,220,220,127),stop:1 rgba(200,200,200,75)); + border:1px solid rgba(220,220,220,75); + border-top-left-radius:4px; + border-top-right-radius:4px; +} +QTabBar::tab:selected,QTabBar::tab:hover{ + background-color:qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0 rgba(200,200,200,127),stop:1 rgba(200,200,200,50%)); + border-bottom-color:rgba(200,200,200,75%); +} +QTabBar::tab:selected{ + border-bottom:2px solid palette(highlight); +} +QTabBar::tab::selected:disabled{ + border-bottom:2px solid rgb(127,127,127); +} +QTabBar::tab:!selected{ + margin-top:2px; +} +QTabBar::close-button { + border:1px solid transparent; + border-radius:2px; +} +QTabBar::close-button:hover { + background-color:qlineargradient(x1:0,y1:0,x2:0,y2:1,stop:0 rgba(176,176,176,255),stop:1 rgba(176,176,176,75)); + border:1px solid palette(base); +} +QTabBar::close-button:pressed { + background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(220,220,220,127),stop:1 rgba(200,200,200,75)); + border:1px solid palette(base); +} +QCheckBox::indicator{ + width:18px; + height:18px; +} +QRadioButton::indicator{ + width:18px; + height:18px; +} +QTreeView, QTableView{ + alternate-background-color:palette(window); + background:palette(base); +} +QTreeView QHeaderView::section, QTableView QHeaderView::section{ + background-color:qlineargradient(x1:0,y1:1,x2:0,y2:0,stop:0 rgba(220,220,220,127),stop:1 rgba(200,200,200,75)); + border-style:none; + border-bottom:1px solid palette(dark); + padding-left:5px; + padding-right:5px; +} +QTreeView::item:selected:disabled, QTableView::item:selected:disabled{ + background:rgb(80,80,80); +} +QTreeView::branch{ + background-color:palette(base); +} +QTreeView::branch:has-children:!has-siblings:closed, +QTreeView::branch:closed:has-children:has-siblings{ + border-image:none; +} +QTreeView::branch:open:has-children:!has-siblings, +QTreeView::branch:open:has-children:has-siblings{ + border-image:none; +} +QScrollBar:vertical{ + background:palette(base); + border-top-right-radius:2px; + border-bottom-right-radius:2px; + width:16px; + margin:0px; +} +QScrollBar::handle:vertical{ + background-color:palette(alternate-base); + border-radius:2px; + min-height:20px; + margin:2px 4px 2px 4px; +} +QScrollBar::handle:vertical:hover{ + background-color:palette(highlight); +} +QScrollBar::add-line:vertical{ + background:none; + height:0px; + subcontrol-position:right; + subcontrol-origin:margin; +} +QScrollBar::sub-line:vertical{ + background:none; + height:0px; + subcontrol-position:left; + subcontrol-origin:margin; +} +QScrollBar:horizontal{ + background:palette(base); + height:16px; + margin:0px; +} +QScrollBar::handle:horizontal{ + background-color:palette(alternate-base); + border-radius:2px; + min-width:20px; + margin:4px 2px 4px 2px; +} +QScrollBar::handle:horizontal:hover{ + background-color:palette(highlight); +} +QScrollBar::add-line:horizontal{ + background:none; + width:0px; + subcontrol-position:bottom; + subcontrol-origin:margin; +} +QScrollBar::sub-line:horizontal{ + background:none; + width:0px; + subcontrol-position:top; + subcontrol-origin:margin; +} +QSlider::handle:horizontal{ + border-radius:4px; + border:1px solid rgba(220,220,220,255); + background-color:palette(alternate-base); + min-height:20px; + margin:0 -4px; +} +QSlider::handle:horizontal:hover{ + background:palette(highlight); +} +QSlider::add-page:horizontal{ + background:palette(base); +} +QSlider::sub-page:horizontal{ + background:palette(highlight); +} +QSlider::sub-page:horizontal:disabled{ + background:rgb(80,80,80); +} diff --git a/lib/QGoodWindow/QGoodWindow/src/macosnative.h b/lib/QGoodWindow/QGoodWindow/src/macosnative.h new file mode 100644 index 00000000..7d3c1cd6 --- /dev/null +++ b/lib/QGoodWindow/QGoodWindow/src/macosnative.h @@ -0,0 +1,67 @@ +/* +The MIT License (MIT) + +Copyright © 2021-2023 Antonio Dias (https://github.com/antonypro) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef MACOSNATIVE_H +#define MACOSNATIVE_H + +#include "notification.h" + +extern Notification notification; + +//\cond HIDDEN_SYMBOLS +namespace macOSNative +{ +enum class StyleType +{ + NoState, + Disabled, + Fullscreen +}; + +class Style +{ +public: + StyleType m_style = StyleType::NoState; + bool m_is_native_caption_buttons_visible = true; + bool m_is_dialog = false; +}; + +void registerThemeChangeNotification(); +void registerNotification(const char *notification_name, long wid); +void unregisterNotification(); + +inline void handleNotification(const char *notification_name, long wid) +{ + notification.notification(notification_name, wid); +} + +void setStyle(long winid, Style *style); + +const char *themeName(); +void frameGeometry(long wid, int *x, int *y, int *w, int *h); +void titleBarButtonsRect(long wid, int *x, int *y, int *w, int *h); +} +//\endcond + +#endif // MACOSNATIVE_H diff --git a/lib/QGoodWindow/QGoodWindow/src/macosnative.mm b/lib/QGoodWindow/QGoodWindow/src/macosnative.mm new file mode 100644 index 00000000..788a4d40 --- /dev/null +++ b/lib/QGoodWindow/QGoodWindow/src/macosnative.mm @@ -0,0 +1,213 @@ +/* +The MIT License (MIT) + +Copyright © 2021-2023 Antonio Dias (https://github.com/antonypro) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "macosnative.h" +#include + +void macOSNative::setStyle(long winid, Style *style) +{ + NSView *nativeView = reinterpret_cast(winid); + NSWindow *nativeWindow = [nativeView window]; + + switch (style->m_style) + { + case StyleType::NoState: + { + if (!style->m_is_dialog) + { + [nativeWindow setStyleMask:NSWindowStyleMaskResizable | + NSWindowStyleMaskMiniaturizable | + NSWindowStyleMaskClosable | + NSWindowStyleMaskTitled | + NSWindowStyleMaskFullSizeContentView]; + } + else + { + [nativeWindow setStyleMask:NSWindowStyleMaskClosable | + NSWindowStyleMaskTitled | + NSWindowStyleMaskFullSizeContentView]; + } + [nativeWindow setMovableByWindowBackground:NO]; + [nativeWindow setMovable:NO]; + [nativeWindow setTitlebarAppearsTransparent:YES]; + [nativeWindow setShowsToolbarButton:NO]; + [nativeWindow setTitleVisibility:NSWindowTitleHidden]; + [nativeWindow standardWindowButton:NSWindowMiniaturizeButton].hidden = !style->m_is_native_caption_buttons_visible; + [nativeWindow standardWindowButton:NSWindowZoomButton].hidden = !style->m_is_native_caption_buttons_visible; + [nativeWindow standardWindowButton:NSWindowCloseButton].hidden = !style->m_is_native_caption_buttons_visible; + [nativeWindow makeKeyWindow]; + + break; + } + case StyleType::Disabled: + { + [nativeWindow setStyleMask:NSWindowStyleMaskFullSizeContentView]; + [nativeWindow setMovableByWindowBackground:NO]; + [nativeWindow setMovable:NO]; + [nativeWindow setTitlebarAppearsTransparent:YES]; + [nativeWindow setShowsToolbarButton:NO]; + [nativeWindow setTitleVisibility:NSWindowTitleHidden]; + [nativeWindow standardWindowButton:NSWindowMiniaturizeButton].hidden = !style->m_is_native_caption_buttons_visible; + [nativeWindow standardWindowButton:NSWindowZoomButton].hidden = !style->m_is_native_caption_buttons_visible; + [nativeWindow standardWindowButton:NSWindowCloseButton].hidden = !style->m_is_native_caption_buttons_visible; + [nativeWindow makeKeyWindow]; + + break; + } + case StyleType::Fullscreen: + { + [nativeWindow setStyleMask:NSWindowStyleMaskFullScreen]; + + break; + } + } +} + +//\cond HIDDEN_SYMBOLS +@interface Handler : NSObject +{ +} +@end +//\endcond + +Handler *m_handler; + +void macOSNative::registerNotification(const char *notification_name, long wid) +{ + NSView *nativeView = reinterpret_cast(wid); + NSWindow *nativeWindow = [nativeView window]; + + [[NSNotificationCenter defaultCenter] + addObserver:m_handler + selector:@selector(NotificationHandler:) + name:[NSString stringWithUTF8String:notification_name] + object:nativeWindow]; +} + +void macOSNative::unregisterNotification() +{ + [[NSNotificationCenter defaultCenter] + removeObserver:m_handler]; + + [m_handler release]; +} + +//\cond HIDDEN_SYMBOLS +@implementation Handler ++(void)load +{ + m_handler = static_cast(self); +} + ++(void)NotificationHandler:(NSNotification*)notification +{ + const NSString *str = [notification name]; + + const NSWindow *nativeWindow = [notification object]; + + const NSView *nativeView = [nativeWindow contentView]; + + const char *cstr = [str cStringUsingEncoding:NSUTF8StringEncoding]; + + macOSNative::handleNotification(cstr, long(nativeView)); +} +@end +//\endcond + +//\cond HIDDEN_SYMBOLS +@interface ThemeChangeHandler : NSObject +{ +} +@end +//\endcond + +ThemeChangeHandler *m_theme_change_handler; + +//\cond HIDDEN_SYMBOLS +@implementation ThemeChangeHandler ++(void)load +{ + m_theme_change_handler = static_cast(self); +} + ++(void)ThemeChangeNotification:(NSNotification*)notification +{ + const NSString *str = [notification name]; + + const char *cstr = [str cStringUsingEncoding:NSUTF8StringEncoding]; + + macOSNative::handleNotification(cstr, 0); +} +@end +//\endcond + +void macOSNative::registerThemeChangeNotification() +{ + [[NSDistributedNotificationCenter defaultCenter] + addObserver:m_theme_change_handler + selector:@selector(ThemeChangeNotification:) + name:@"AppleInterfaceThemeChangedNotification" + object:nil]; +} + +const char *macOSNative::themeName() +{ + NSString *str = [[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"]; + const char *cstr = [str cStringUsingEncoding:NSUTF8StringEncoding]; + return cstr; +} + +void macOSNative::frameGeometry(long wid, int *x, int *y, int *w, int *h) +{ + NSRect nsScreenRect = [[[NSScreen screens] firstObject] frame]; + + NSView *nativeView = reinterpret_cast(wid); + NSWindow *nativeWindow = [nativeView window]; + NSRect nsWindowRect = [nativeWindow frame]; + + int pos_x = nsWindowRect.origin.x; + int pos_y = nsScreenRect.size.height - nsWindowRect.size.height - nsWindowRect.origin.y; + + *x = pos_x; + *y = pos_y; + *w = nsWindowRect.size.width; + *h = nsWindowRect.size.height; +} + +void macOSNative::titleBarButtonsRect(long wid, int *x, int *y, int *w, int *h) +{ + NSView *nativeView = reinterpret_cast(wid); + NSWindow *nativeWindow = [nativeView window]; + + NSButton *closeButton = [nativeWindow standardWindowButton:NSWindowCloseButton]; + NSButton *minimizeButton = [nativeWindow standardWindowButton:NSWindowMiniaturizeButton]; + NSButton *zoomButton = [nativeWindow standardWindowButton:NSWindowZoomButton]; + + NSRect rect = NSUnionRect(NSUnionRect([closeButton frame], [minimizeButton frame]), [zoomButton frame]); + + *x = rect.origin.x; + *y = rect.origin.y; + *w = rect.size.width; + *h = rect.size.height; +} diff --git a/lib/QGoodWindow/QGoodWindow/src/notification.cpp b/lib/QGoodWindow/QGoodWindow/src/notification.cpp new file mode 100644 index 00000000..12a08ccf --- /dev/null +++ b/lib/QGoodWindow/QGoodWindow/src/notification.cpp @@ -0,0 +1,77 @@ +/* +The MIT License (MIT) + +Copyright © 2021-2023 Antonio Dias (https://github.com/antonypro) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "notification.h" +#include "qgoodwindow.h" +#include "macosnative.h" + +Notification::Notification() +{ + +} + +Notification::~Notification() +{ + unregisterNotification(); +} + +void Notification::addWindow(void *ptr) +{ + m_ptr_list.append(ptr); +} + +void Notification::removeWindow(void *ptr) +{ + m_ptr_list.removeAll(ptr); +} + +void Notification::registerNotification(const QByteArray &name, WId wid) +{ + macOSNative::registerNotification(name.constData(), long(wid)); +} + +void Notification::unregisterNotification() +{ + macOSNative::unregisterNotification(); +} + +void Notification::notification(const char *notification_name, long wid) +{ + const QByteArray notification = QByteArray(notification_name); + + for (void *ptr : m_ptr_list) + { + QGoodWindow *gw = static_cast(ptr); + + if (wid == 0) + { + gw->notificationReceiver(notification); + } + else if (gw->winId() == WId(wid)) + { + gw->notificationReceiver(notification); + break; + } + } +} diff --git a/lib/QGoodWindow/QGoodWindow/src/notification.h b/lib/QGoodWindow/QGoodWindow/src/notification.h new file mode 100644 index 00000000..1852d883 --- /dev/null +++ b/lib/QGoodWindow/QGoodWindow/src/notification.h @@ -0,0 +1,49 @@ +/* +The MIT License (MIT) + +Copyright © 2021-2023 Antonio Dias (https://github.com/antonypro) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef NOTIFICATION_H +#define NOTIFICATION_H + +#include +#include + +//\cond HIDDEN_SYMBOLS +class Notification +{ +public: + Notification(); + ~Notification(); + + void addWindow(void *ptr); + void removeWindow(void *ptr); + void notification(const char *notification_name, long wid); + void registerNotification(const QByteArray &name, WId wid); + void unregisterNotification(); + +private: + QList m_ptr_list; +}; +//\endcond + +#endif // NOTIFICATION_H diff --git a/lib/QGoodWindow/QGoodWindow/src/qgooddialog.cpp b/lib/QGoodWindow/QGoodWindow/src/qgooddialog.cpp new file mode 100644 index 00000000..d5d7c832 --- /dev/null +++ b/lib/QGoodWindow/QGoodWindow/src/qgooddialog.cpp @@ -0,0 +1,291 @@ +/* +The MIT License (MIT) + +Copyright © 2022-2023 Antonio Dias (https://github.com/antonypro) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "qgooddialog.h" +#include "qgoodwindow.h" + +#ifdef Q_OS_MAC +#include "macosnative.h" +#endif + +namespace QGoodDialogUtils +{ +static bool m_dialog_is_visible = false; +} + +QGoodDialog::QGoodDialog(QDialog *dialog, QGoodWindow *child_gw, QGoodWindow *parent_gw) : QObject() +{ + m_dialog = dialog; + + m_child_gw = child_gw; + + m_parent_gw = parent_gw; +} + +int QGoodDialog::exec() +{ + if (!m_dialog) + return QDialog::Rejected; + + if (!m_child_gw) + return QDialog::Rejected; + + if (!m_parent_gw) + return QDialog::Rejected; + + if (QGoodDialogUtils::m_dialog_is_visible) + return QDialog::Rejected; + + QGoodDialogUtils::m_dialog_is_visible = true; + + m_child_gw->installEventFilter(this); + + m_dialog->installEventFilter(this); + +#ifdef Q_OS_WIN + m_dialog->setWindowModality(Qt::NonModal); +#endif +#ifdef Q_OS_LINUX + m_child_gw->setWindowModality(Qt::ApplicationModal); +#endif +#ifdef Q_OS_MAC + m_child_gw->setWindowModality(Qt::WindowModal); +#endif + +#if defined Q_OS_WIN || defined Q_OS_LINUX + bool is_message_box = qobject_cast(m_dialog); + bool is_input_dialog = qobject_cast(m_dialog); + + auto func_center = [=]{ + if (!is_message_box && !is_input_dialog) + return; + + QScreen *parent_screen = m_parent_gw->windowHandle()->screen(); + + qreal pixel_ratio = parent_screen->devicePixelRatio(); + QRect screen_geom = parent_screen->availableGeometry(); + screen_geom.moveTop(qFloor(screen_geom.top() / pixel_ratio)); + screen_geom.moveLeft(qFloor(screen_geom.left() / pixel_ratio)); + + QRect child_geom; + child_geom.setSize(m_child_gw->size()); + child_geom.moveCenter(m_parent_gw->geometry().center()); + + if (child_geom.left() < screen_geom.left()) + child_geom.moveLeft(screen_geom.left()); + else if (child_geom.right() > screen_geom.right()) + child_geom.moveRight(screen_geom.right()); + + if (child_geom.top() < screen_geom.top()) + child_geom.moveTop(screen_geom.top()); + else if (child_geom.bottom() > screen_geom.bottom()) + child_geom.moveBottom(screen_geom.bottom()); + + m_child_gw->setGeometry(child_geom); + }; + + auto func_fixed_size = [=]{ + if (is_message_box || is_input_dialog) + { + m_child_gw->setFixedSize(m_child_gw->sizeHint()); + } + else + { + if (m_dialog->minimumSize() == m_dialog->maximumSize()) + { + m_child_gw->setFixedSize(m_child_gw->sizeHint()); + } + else + { + m_child_gw->setMinimumSize(m_dialog->minimumSize()); + m_child_gw->setMaximumSize(m_dialog->maximumSize()); + } + } + }; +#endif + + if (!m_dialog->windowTitle().isNull()) + m_child_gw->setWindowTitle(m_dialog->windowTitle()); + else + m_child_gw->setWindowTitle(qApp->applicationName()); + + if (!m_dialog->windowIcon().isNull()) + m_child_gw->setWindowIcon(m_dialog->windowIcon()); + else + m_child_gw->setWindowIcon(m_parent_gw->windowIcon()); + + if (m_parent_gw->isMinimized()) + m_parent_gw->showNormal(); + +#ifdef Q_OS_MAC + QTimer::singleShot(0, m_child_gw, [=]{ + bool visible = m_child_gw->isNativeCaptionButtonsVisibleOnMac(); + m_child_gw->setNativeCaptionButtonsVisibleOnMac(visible); + m_child_gw->show(); + m_child_gw->setNativeCaptionButtonsVisibleOnMac(visible); + }); +#else + func_fixed_size(); + func_center(); + + QShortcut *shortcut = new QShortcut(QKeySequence(Qt::Key_Escape), m_child_gw); + connect(shortcut, &QShortcut::activated, m_dialog, &QDialog::reject); + + QTimer::singleShot(0, m_child_gw, [=]{ + m_child_gw->show(); +#ifdef Q_OS_LINUX + func_fixed_size(); + func_center(); +#endif + }); +#endif + + m_loop.exec(); + + m_dialog->setParent(nullptr); + m_child_gw->setParent(nullptr); + + QGoodDialogUtils::m_dialog_is_visible = false; + + return m_dialog->result(); +} + +int QGoodDialog::exec(QDialog *dialog, QGoodWindow *child_gw, QGoodWindow *parent_gw) +{ + QGoodDialog good_dialog(dialog, child_gw, parent_gw); + return good_dialog.exec(); +} + +bool QGoodDialog::eventFilter(QObject *watched, QEvent *event) +{ + if (watched == m_child_gw) + { + switch (event->type()) + { + case QEvent::Show: + { +#ifdef Q_OS_WIN + HWND hwnd_gw = HWND(m_child_gw->winId()); + + for (QWindow *w : qApp->topLevelWindows()) + { + //Prevent problems with fullscreen mode. + if (w->type() == Qt::Window) + continue; + + HWND hwnd = HWND(w->winId()); + + if (hwnd == hwnd_gw || IsChild(hwnd_gw, hwnd)) + continue; + + if (m_window_list.contains(w)) + continue; + + m_window_list.append(w); + + EnableWindow(hwnd, FALSE); + } +#endif +#ifdef Q_OS_MAC + for (QWindow *w : qApp->topLevelWindows()) + { + if (!w->isVisible()) + continue; + + if (w == m_child_gw->windowHandle()) + continue; + + if (w == m_parent_gw->windowHandle()) + continue; + + if (w == m_dialog->windowHandle()) + continue; + + m_window_list.append(w); + + w->setModality(Qt::WindowModal); + } + + m_parent_gw->setMacOSStyle(int(macOSNative::StyleType::Disabled)); +#endif + break; + } + case QEvent::Close: + { + QTimer::singleShot(0, m_parent_gw, &QGoodWindow::activateWindow); + + m_loop.quit(); + +#ifdef Q_OS_WIN + for (QWindow *w : m_window_list) + { + HWND hwnd = HWND(w->winId()); + EnableWindow(hwnd, TRUE); + } + + m_window_list.clear(); +#endif +#ifdef Q_OS_MAC + for (QWindow *w : m_window_list) + { + w->setModality(Qt::NonModal); + } + + m_window_list.clear(); + + QTimer::singleShot(500, this, [=]{ + m_parent_gw->setMacOSStyle(int(macOSNative::StyleType::NoState)); + }); +#endif + break; + } + default: + break; + } + } + else if (watched == m_dialog) + { + switch (event->type()) + { + case QEvent::Show: + { + if (m_dialog) + m_dialog->activateWindow(); + + break; + } + case QEvent::Hide: + { + if (m_child_gw) + m_child_gw->close(); + + break; + } + default: + break; + } + } + + return QObject::eventFilter(watched, event); +} diff --git a/lib/QGoodWindow/QGoodWindow/src/qgooddialog.h b/lib/QGoodWindow/QGoodWindow/src/qgooddialog.h new file mode 100644 index 00000000..7c2eacae --- /dev/null +++ b/lib/QGoodWindow/QGoodWindow/src/qgooddialog.h @@ -0,0 +1,60 @@ +/* +The MIT License (MIT) + +Copyright © 2022-2023 Antonio Dias (https://github.com/antonypro) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef QGOODDIALOG_H +#define QGOODDIALOG_H + +#include +#include +#include + +class QGoodWindow; + +//\cond HIDDEN_SYMBOLS +class QGoodDialog : public QObject +{ + Q_OBJECT +private: + explicit QGoodDialog(QDialog *dialog, QGoodWindow *child_gw, QGoodWindow *parent_gw); + +public: + static int exec(QDialog *dialog, QGoodWindow *child_gw, QGoodWindow *parent_gw); + +private: + //Functions + int exec(); + bool eventFilter(QObject *watched, QEvent *event); + + //Variables + QEventLoop m_loop; + QPointer m_dialog; + QPointer m_child_gw; + QPointer m_parent_gw; +#if defined Q_OS_WIN || defined Q_OS_MAC + QWindowList m_window_list; +#endif +}; +//\endcond + +#endif // QGOODDIALOG_H diff --git a/lib/QGoodWindow/QGoodWindow/src/qgoodstateholder.cpp b/lib/QGoodWindow/QGoodWindow/src/qgoodstateholder.cpp new file mode 100644 index 00000000..2dc2bcc7 --- /dev/null +++ b/lib/QGoodWindow/QGoodWindow/src/qgoodstateholder.cpp @@ -0,0 +1,53 @@ +/* +The MIT License (MIT) + +Copyright © 2022-2023 Antonio Dias (https://github.com/antonypro) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "qgoodstateholder.h" + +QGoodStateHolder *QGoodStateHolder::instance() +{ + static QGoodStateHolder instance; + return &instance; +} + +QGoodStateHolder::QGoodStateHolder() : QObject() +{ + m_dark = false; +} + +QGoodStateHolder::~QGoodStateHolder() +{ + +} + +bool QGoodStateHolder::isCurrentThemeDark() const +{ + return m_dark; +} + +void QGoodStateHolder::setCurrentThemeDark(bool dark) +{ + m_dark = dark; + + Q_EMIT currentThemeChanged(); +} diff --git a/lib/QGoodWindow/QGoodWindow/src/qgoodstateholder.h b/lib/QGoodWindow/QGoodWindow/src/qgoodstateholder.h new file mode 100644 index 00000000..692304f8 --- /dev/null +++ b/lib/QGoodWindow/QGoodWindow/src/qgoodstateholder.h @@ -0,0 +1,59 @@ +/* +The MIT License (MIT) + +Copyright © 2022-2023 Antonio Dias (https://github.com/antonypro) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef QGOODSTATEHOLDER +#define QGOODSTATEHOLDER + +#include +#include +#include + +#include "qgoodwindow_global.h" + +//\cond HIDDEN_SYMBOLS +class QGOODWINDOW_SHARED_EXPORT QGoodStateHolder : public QObject +{ + Q_OBJECT +public: + static QGoodStateHolder *instance(); + +private: + explicit QGoodStateHolder(); + ~QGoodStateHolder(); + +Q_SIGNALS: + void currentThemeChanged(); + +public Q_SLOTS: + bool isCurrentThemeDark() const; + void setCurrentThemeDark(bool dark); + +private: + bool m_dark; +}; +//\endcond + +#define qGoodStateHolder QGoodWindow::qGoodStateHolderInstance() + +#endif // QGOODSTATEHOLDER diff --git a/lib/QGoodWindow/QGoodWindow/src/qgoodwindow.cpp b/lib/QGoodWindow/QGoodWindow/src/qgoodwindow.cpp new file mode 100644 index 00000000..acb63fba --- /dev/null +++ b/lib/QGoodWindow/QGoodWindow/src/qgoodwindow.cpp @@ -0,0 +1,4541 @@ +/* +The MIT License (MIT) + +Copyright © 2018-2023 Antonio Dias (https://github.com/antonypro) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#if defined QGOODWINDOW && defined __linux__ +#include +#endif + +#include "common.h" +#include "qgoodwindow.h" +#include "shadow.h" +#include "qgooddialog.h" +#include "../version/version.h" + +#ifndef QGOODWINDOW +#ifdef Q_OS_WIN +#undef Q_OS_WIN +#endif +#ifdef Q_OS_LINUX +#undef Q_OS_LINUX +#endif +#ifdef Q_OS_MAC +#undef Q_OS_MAC +#endif +#endif + +#ifdef QGOODWINDOW +#define FIXED_WIDTH(widget) (widget->minimumWidth() >= widget->maximumWidth()) +#define FIXED_HEIGHT(widget) (widget->minimumHeight() >= widget->maximumHeight()) +#define FIXED_SIZE(widget) (FIXED_WIDTH(widget) && FIXED_HEIGHT(widget)) +#endif + +#ifdef Q_OS_WIN + +#ifdef QT_VERSION_QT5 +#include +#endif +#include +#include +#include + +namespace QGoodWindowUtils +{ +class ParentWindow : public QWidget +{ +public: + explicit ParentWindow(QWidget *parent) : QWidget(parent, Qt::Window) + { + + } + +private: + bool event(QEvent *event) + { + switch (event->type()) + { + case QEvent::ChildRemoved: + { + QWidget *parent_widget = parentWidget(); + + if (parent_widget) + parent_widget->activateWindow(); + + delete this; + return true; + } + default: + break; + } + + return QWidget::event(event); + } +}; + +class NativeEventFilter : public QAbstractNativeEventFilter +{ +public: + NativeEventFilter(QGoodWindow *gw) + { + m_gw = gw; + m_gw_hwnd = HWND(m_gw->winId()); + } + + bool nativeEventFilter(const QByteArray &eventType, void *message, qgoodintptr *result) override + { + Q_UNUSED(eventType) + + MSG *msg = static_cast(message); + + if (!IsWindowVisible(msg->hwnd)) + return false; + + if (!IsChild(m_gw_hwnd, msg->hwnd)) + return false; + + switch (msg->message) + { + case WM_NCHITTEST: + { + QPoint pos = QPoint(GET_X_LPARAM(msg->lParam), GET_Y_LPARAM(msg->lParam)); + + HRESULT lresult = HRESULT(m_gw->ncHitTest(pos.x(), pos.y())); + + if (lresult == HTNOWHERE) + break; + + //If region not contains the mouse, pass the + //WM_NCHITTEST event to main window. + *result = HTTRANSPARENT; + return true; + } + case WM_KEYDOWN: + { + if (GetKeyState(VK_SHIFT) & 0x8000) + { + switch (msg->wParam) + { + case VK_TAB: + //Prevent that SHIFT+TAB crashes the application. + return true; + default: + break; + } + } + + break; + } + case WM_SYSKEYDOWN: + case WM_SYSKEYUP: + { + //Pass to main window... + + if ((GetKeyState(VK_SHIFT) & 0x8000) && msg->wParam == VK_F10) + { + // ...when SHIFT+F10 is pressed. + SendMessageW(m_gw_hwnd, msg->message, msg->wParam, msg->lParam); + return true; + } + + if ((GetKeyState(VK_MENU) & 0x8000) && msg->wParam == VK_SPACE) + { + // ...when ALT+SPACE is pressed. + SendMessageW(m_gw_hwnd, msg->message, msg->wParam, msg->lParam); + return true; + } + + break; + } + default: + break; + } + + return false; + } + +private: + QPointer m_gw; + HWND m_gw_hwnd; +}; + +inline bool isWinXOrGreater(DWORD major_version, DWORD minor_version, DWORD build_number) +{ + bool is_win_x_or_greater = false; + + typedef NTSTATUS(WINAPI *tRtlGetVersion)(LPOSVERSIONINFOEXW); + tRtlGetVersion pRtlGetVersion = tRtlGetVersion(QLibrary::resolve("ntdll", "RtlGetVersion")); + + if (pRtlGetVersion) + { + OSVERSIONINFOEXW os_info; + memset(&os_info, 0, sizeof(OSVERSIONINFOEXW)); + os_info.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEXW); + NTSTATUS status = pRtlGetVersion(&os_info); + if (status == 0) + { + is_win_x_or_greater = (os_info.dwMajorVersion >= major_version && + os_info.dwMinorVersion >= minor_version && + os_info.dwBuildNumber >= build_number); + } + } + + return is_win_x_or_greater; +} + +inline bool isWin11OrGreater() +{ + bool is_win_11_or_greater = isWinXOrGreater(10, 0, 22000); + + return is_win_11_or_greater; +} +} +#endif + +#ifdef Q_OS_LINUX + +#include +#ifdef QT_VERSION_QT5 +#include +#endif +#ifdef QT_VERSION_QT6 +#include +#endif +#include +#include +#include + +namespace QGoodWindowUtils +{ +QList m_gw_list; + +GtkSettings *m_settings = nullptr; + +void themeChangeNotification() +{ + for (QGoodWindow *gw : m_gw_list) + { + QTimer::singleShot(0, gw, &QGoodWindow::themeChanged); + } +} + +void registerThemeChangeNotification() +{ + if (!m_settings) + { + m_settings = gtk_settings_get_default(); + g_signal_connect(m_settings, "notify::gtk-theme-name", themeChangeNotification, nullptr); + } +} +} +#endif + +#ifdef Q_OS_MAC + +#include "macosnative.h" + +Notification notification; + +namespace QGoodWindowUtils +{ +bool m_theme_change_registered = false; +} +#endif + +#ifndef Q_OS_WIN +#define GOODPARENT(parent) parent +#else +#define GOODPARENT(parent) nullptr +#endif + +QGoodWindow::QGoodWindow(QWidget *parent, const QColor &clear_color) : QMainWindow(GOODPARENT(parent)) +{ +#ifdef QGOODWINDOW + qRegisterMetaType("QGoodWindow::CaptionButtonState"); + + m_parent = parent; + + m_is_using_system_borders = shouldBordersBeDrawnBySystem(); + + m_dark = isSystemThemeDark(); + + m_title_bar_height = 30; + m_icon_width = 0; + + m_is_caption_button_pressed = false; + m_last_caption_button_hovered = -1; + m_caption_button_pressed = -1; + + m_hover_timer = new QTimer(this); + m_hover_timer->setSingleShot(true); + m_hover_timer->setInterval(300); + + m_pixel_ratio = qreal(1); +#endif +#ifdef Q_OS_WIN + m_clear_color = clear_color; +#else + Q_UNUSED(clear_color) +#endif +#ifdef Q_OS_WIN + m_minimum_width = 0; + m_minimum_height = 0; + m_maximum_width = 0; + m_maximum_height = 0; +#endif +#if defined Q_OS_LINUX || defined Q_OS_MAC + m_last_move_button = -1; +#endif +#ifdef Q_OS_MAC + m_is_native_caption_buttons_visible_on_mac = true; + m_mouse_button_pressed = false; + m_on_animate_event = false; +#endif +#ifdef Q_OS_WIN + m_window_ready_for_resize = false; + m_closed = false; + m_visible = false; + m_self_generated_close_event = false; + + m_timer_move = new QTimer(this); + connect(m_timer_move, &QTimer::timeout, this, [=]{ + QScreen *screen = screenForWindow(m_hwnd); + + if (windowHandle()->screen() != screen) + updateScreen(screen); + }); + m_timer_move->setSingleShot(true); + m_timer_move->setInterval(0); + + m_window_state = Qt::WindowNoState; + + m_is_menu_visible = false; + m_timer_menu = new QTimer(this); + m_timer_menu->setSingleShot(true); + m_timer_menu->setInterval(qApp->doubleClickInterval()); + + m_active_state = false; + + m_last_state = Qt::WindowNoState; + + m_self_generated_show_event = false; + + m_native_event = nullptr; + + HINSTANCE hInstance = GetModuleHandleW(nullptr); + + HWND parent_hwnd = nullptr; + + if (m_parent) + parent_hwnd = HWND(m_parent->winId()); + + WNDCLASSEXW wcx; + memset(&wcx, 0, sizeof(WNDCLASSEXW)); + wcx.cbSize = sizeof(WNDCLASSEXW); + wcx.style = CS_HREDRAW | CS_VREDRAW; + wcx.hInstance = hInstance; + wcx.lpfnWndProc = WNDPROC(WndProc); + wcx.cbClsExtra = 0; + wcx.cbWndExtra = 0; + wcx.hCursor = LoadCursorW(nullptr, IDC_ARROW); + wcx.hbrBackground = HBRUSH(CreateSolidBrush(RGB(m_clear_color.red(), + m_clear_color.green(), + m_clear_color.blue()))); + + wcx.lpszClassName = L"QGoodWindowClass"; + + RegisterClassExW(&wcx); + + m_hwnd = CreateWindowW(wcx.lpszClassName, nullptr, + WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + parent_hwnd, nullptr, hInstance, nullptr); + + if (!m_hwnd) + { + QMessageBox::critical(nullptr, "Error", "Error creating window"); + return; + } + + SetWindowLongPtrW(m_hwnd, GWLP_USERDATA, reinterpret_cast(this)); + + //Fix QApplication::exit() crash. + connect(qApp, &QApplication::aboutToQuit, this, &QGoodWindow::closeGW); + + m_is_win_11_or_greater = QGoodWindowUtils::isWin11OrGreater(); + + m_window_handle = QWindow::fromWinId(WId(m_hwnd)); + + initGW(); + + m_helper_widget = new QWidget(); + + //Fix bug that prevents window to close on Qt 6. +#ifdef QT_VERSION_QT6 + m_helper_window = new QWindow(); + m_helper_window->setFlags(Qt::Window | Qt::FramelessWindowHint | Qt::Tool); + m_helper_window->setGeometry(0, 0, 1, 1); + m_helper_window->setOpacity(0); +#endif + if (!m_is_using_system_borders) + { + QGoodWindow *parent = m_parent ? this : nullptr; + m_shadow = new Shadow(qintptr(m_hwnd), parent, parent); + m_shadow->createWinId(); + connect(m_shadow, &Shadow::showSignal, this, &QGoodWindow::moveShadow); + } + + m_main_window = static_cast(this); + m_main_window->createWinId(); + m_main_window->installEventFilter(this); + + QWidget *widget_proxy = new QWidget(m_main_window); + m_main_window->setCentralWidget(widget_proxy); + + m_proxy_widget = new QWidget(widget_proxy); + + QScreen *screen = screenForWindow(m_hwnd); + m_pixel_ratio = screen->devicePixelRatio(); + setCurrentScreen(screen); + + if (m_parent) + m_parent->installEventFilter(this); + + if (!m_native_event) + { + m_native_event = new QGoodWindowUtils::NativeEventFilter(this); + qApp->installNativeEventFilter(m_native_event); + } + + //Fix that hides native title bar. + frameChanged(); + +#ifdef QT_VERSION_QT5 + if (!QtWin::isCompositionEnabled()) + { + disableCaption(); + frameChanged(); + } +#endif + +#endif +#ifdef Q_OS_LINUX + m_resize_move = false; + m_resize_move_started = false; + + installEventFilter(this); + setMouseTracking(true); + + createWinId(); + + m_pixel_ratio = windowHandle()->screen()->devicePixelRatio(); + + QGoodWindowUtils::registerThemeChangeNotification(); + + QGoodWindowUtils::m_gw_list.append(this); + + //Fake window flags. + m_window_flags = Qt::Window | Qt::FramelessWindowHint; + + if (!m_parent) + { + QMainWindow::setWindowFlags(Qt::Window | Qt::FramelessWindowHint); + m_window_flags |= Qt::WindowMinMaxButtonsHint; + } + else + { + QMainWindow::setWindowFlags(Qt::Window | Qt::FramelessWindowHint | Qt::Tool); + } + + m_shadow = new Shadow(qintptr(nullptr), this, this); + m_shadow->installEventFilter(this); + m_shadow->setMouseTracking(true); + connect(m_shadow, &Shadow::showSignal, this, &QGoodWindow::sizeMoveBorders); +#endif +#ifdef Q_OS_MAC + installEventFilter(this); + setMouseTracking(true); + + createWinId(); + + style_ptr = new macOSNative::Style(); + + macOSNative::Style *style = static_cast(style_ptr); + style->m_is_dialog = (m_parent != nullptr); + + notification.addWindow(this); + + if (!QGoodWindowUtils::m_theme_change_registered) + { + QGoodWindowUtils::m_theme_change_registered = true; + macOSNative::registerThemeChangeNotification(); + } + + if (!m_parent) + QMainWindow::setWindowFlags(Qt::Window); + else + QMainWindow::setWindowFlags(Qt::Dialog); +#endif +#ifdef QGOODWINDOW + auto func_default_name_icon = [=]{ + if (windowTitle().isEmpty()) + { + setWindowTitle(qApp->applicationName()); + QTimer::singleShot(0, this, [=]{Q_EMIT windowTitleChanged(windowTitle());}); + } + + if (windowIcon().isNull()) + { + setWindowIcon(qApp->style()->standardIcon(QStyle::SP_DesktopIcon)); + QTimer::singleShot(0, this, [=]{Q_EMIT windowIconChanged(windowIcon());}); + } + }; +#ifdef Q_OS_WIN + QTimer::singleShot(0, this, func_default_name_icon); +#else + func_default_name_icon(); +#endif +#endif +} + +QGoodWindow::~QGoodWindow() +{ +#ifdef Q_OS_WIN + if (!m_closed) + { + //Last chance to close window. + close(); + } + + if (m_native_event) + { + qApp->removeNativeEventFilter(m_native_event); + delete m_native_event; + m_native_event = nullptr; + } +#endif +#ifdef Q_OS_LINUX + QGoodWindowUtils::m_gw_list.removeAll(this); +#endif +#ifdef Q_OS_MAC + delete static_cast(style_ptr); + notification.removeWindow(this); +#endif +#if defined Q_OS_LINUX || defined Q_OS_MAC + removeEventFilter(this); +#endif +} + +void QGoodWindow::themeChanged() +{ +#ifdef QGOODWINDOW + bool dark = isSystemThemeDark(); + + if (m_dark != dark) + { + m_dark = dark; + Q_EMIT systemThemeChanged(); + } +#endif +} + +WId QGoodWindow::winId() const +{ +#ifdef Q_OS_WIN + return WId(m_hwnd); +#else + return QMainWindow::winId(); +#endif +} + +void QGoodWindow::setWindowFlags(Qt::WindowFlags type) +{ +#ifdef QGOODWINDOW + Q_UNUSED(type) +#else + QMainWindow::setWindowFlags(type); +#endif +} + +Qt::WindowFlags QGoodWindow::windowFlags() const +{ +#if defined Q_OS_WIN + HWND hwnd = HWND(winId()); + + Qt::WindowFlags flags; + flags |= Qt::Window; + flags |= Qt::WindowTitleHint; + flags |= Qt::WindowSystemMenuHint; + + if (GetWindowLongW(hwnd, GWL_STYLE) & WS_MINIMIZEBOX) + flags |= Qt::WindowMinimizeButtonHint; + + if (GetWindowLongW(hwnd, GWL_STYLE) & WS_MAXIMIZEBOX) + flags |= Qt::WindowMaximizeButtonHint; + + flags |= Qt::WindowCloseButtonHint; + + return flags; +#elif defined Q_OS_LINUX + return m_window_flags; +#else + return QMainWindow::windowFlags(); +#endif +} + +/*** QGOODWINDOW FUNCTIONS BEGIN ***/ + +QString QGoodWindow::version() +{ + return QStringLiteral(QGOODWINDOW_VERSION); +} + +void QGoodWindow::setup() +{ +#ifdef QGOODWINDOW + //Init resources + Q_INIT_RESOURCE(qgoodwindow_style); +#ifdef QGOODCENTRALWIDGET + Q_INIT_RESOURCE(qgoodcentralwidget_icons); +#endif + +#ifdef Q_OS_LINUX + qputenv("XDG_SESSION_TYPE", "xcb"); + qputenv("QT_QPA_PLATFORM", "xcb"); + + int argc = 0; + char **argv = nullptr; + gtk_init(&argc, &argv); +#endif + +#ifndef Q_OS_MAC + +#ifdef Q_OS_WIN +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + if (QGoodWindowUtils::isWinXOrGreater(10, 0, 15063)) //Windows 10 version 1703 or later. + { + static const qintptr vDPI_AWARENESS_CONTEXT = 0; + static const qintptr vDPI_AWARENESS_CONTEXT_UNAWARE = ((vDPI_AWARENESS_CONTEXT)-1); + static const qintptr vDPI_AWARENESS_CONTEXT_SYSTEM_AWARE = ((vDPI_AWARENESS_CONTEXT)-2); + static const qintptr vDPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE = ((vDPI_AWARENESS_CONTEXT)-3); + static const qintptr vDPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = ((vDPI_AWARENESS_CONTEXT)-4); + static const qintptr vDPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED = ((vDPI_AWARENESS_CONTEXT)-5); + + Q_UNUSED(vDPI_AWARENESS_CONTEXT_UNAWARE) + Q_UNUSED(vDPI_AWARENESS_CONTEXT_SYSTEM_AWARE) + Q_UNUSED(vDPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE) + Q_UNUSED(vDPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) + Q_UNUSED(vDPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED) + + typedef BOOL(*tSetProcessDpiAwarenessContext)(HANDLE); + tSetProcessDpiAwarenessContext pSetProcessDpiAwarenessContext = + tSetProcessDpiAwarenessContext(QLibrary::resolve("user32", "SetProcessDpiAwarenessContext")); + + if (pSetProcessDpiAwarenessContext) + pSetProcessDpiAwarenessContext(HANDLE(vDPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)); + } +#endif +#endif + +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + qputenv("QT_ENABLE_HIGHDPI_SCALING", "1"); +#elif QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) + QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); +#elif QT_VERSION >= QT_VERSION_CHECK(5, 4, 0) + qputenv("QT_DEVICE_PIXEL_RATIO", "auto"); +#endif + +#endif + +#ifdef Q_OS_MAC + QApplication::setAttribute(Qt::AA_DontUseNativeDialogs); +#endif +#endif +} + +bool QGoodWindow::isSystemThemeDark() +{ + bool dark = false; +#ifdef Q_OS_WIN + typedef LONG(WINAPI *tRegGetValueW)(HKEY,LPCWSTR,LPCWSTR,DWORD,LPDWORD,PVOID,LPDWORD); + tRegGetValueW pRegGetValueW = tRegGetValueW(QLibrary::resolve("advapi32", "RegGetValueW")); + + if (pRegGetValueW) + { + DWORD value; + DWORD size = sizeof(value); + if (pRegGetValueW(HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", + L"AppsUseLightTheme", RRF_RT_DWORD, nullptr, &value, &size) == ERROR_SUCCESS) + dark = (value == 0); + } +#endif +#ifdef Q_OS_LINUX + GtkSettings *settings = gtk_settings_get_default(); + gchar *theme_name; + g_object_get(settings, "gtk-theme-name", &theme_name, nullptr); + dark = QString(theme_name).endsWith("Dark", Qt::CaseInsensitive); +#endif +#ifdef Q_OS_MAC + dark = QString(macOSNative::themeName()).endsWith("Dark", Qt::CaseInsensitive); +#endif + return dark; +} + +bool QGoodWindow::shouldBordersBeDrawnBySystem() +{ + bool drawn_by_system = true; +#ifdef Q_OS_WIN + drawn_by_system = QGoodWindowUtils::isWin11OrGreater(); +#endif +#ifdef Q_OS_LINUX + drawn_by_system = false; +#endif + return drawn_by_system; +} + +int QGoodWindow::execDialog(QDialog *dialog, QGoodWindow *child_gw, QGoodWindow *parent_gw) +{ +#ifdef QGOODWINDOW + return QGoodDialog::exec(dialog, child_gw, parent_gw); +#else + Q_UNUSED(child_gw) + Q_UNUSED(parent_gw) + return dialog->exec(); +#endif +} + +void QGoodWindow::setAppDarkTheme() +{ + qApp->setStyle(new DarkStyle()); + qApp->style()->setObjectName("fusion"); +} + +void QGoodWindow::setAppLightTheme() +{ + qApp->setStyle(new LightStyle()); + qApp->style()->setObjectName("fusion"); +} + +QGoodStateHolder *QGoodWindow::qGoodStateHolderInstance() +{ + return QGoodStateHolder::instance(); +} + +void QGoodWindow::setNativeCaptionButtonsVisibleOnMac(bool visible) +{ +#ifdef Q_OS_MAC + m_is_native_caption_buttons_visible_on_mac = visible; + + macOSNative::Style *style = static_cast(style_ptr); + style->m_is_native_caption_buttons_visible = m_is_native_caption_buttons_visible_on_mac; + macOSNative::setStyle(long(winId()), style); + + Q_EMIT captionButtonsVisibilityChangedOnMacOS(); +#else + Q_UNUSED(visible) +#endif +} + +bool QGoodWindow::isNativeCaptionButtonsVisibleOnMac() const +{ +#ifdef Q_OS_MAC + return m_is_native_caption_buttons_visible_on_mac; +#else + return false; +#endif +} + +QRect QGoodWindow::titleBarButtonsRectOnMacOS() const +{ +#ifdef Q_OS_MAC + int x, y, w, h; + macOSNative::titleBarButtonsRect(long(winId()), &x, &y, &w, &h); + return QRect(x, y, w, h); +#else + return QRect(); +#endif +} + +void QGoodWindow::setTitleBarHeight(int height) +{ +#ifdef QGOODWINDOW + m_title_bar_height = height; +#else + Q_UNUSED(height) +#endif +} + +void QGoodWindow::setIconWidth(int width) +{ +#ifdef QGOODWINDOW + m_icon_width = width; +#else + Q_UNUSED(width) +#endif +} + +int QGoodWindow::titleBarHeight() const +{ +#ifdef QGOODWINDOW + return m_title_bar_height; +#else + return 0; +#endif +} + +int QGoodWindow::iconWidth() const +{ +#ifdef QGOODWINDOW + return m_icon_width; +#else + return 0; +#endif +} + +QRect QGoodWindow::titleBarRect() const +{ +#ifdef QGOODWINDOW + if (!m_is_using_system_borders) + { + if (windowState().testFlag(Qt::WindowNoState)) + { + return QRect(1, 1, width() - 2, titleBarHeight()); + } + else + { + return QRect(0, 0, width(), titleBarHeight()); + } + } + else + { + return QRect(0, 0, width(), titleBarHeight()); + } +#else + return QRect(); +#endif +} + +void QGoodWindow::setTitleBarMask(const QRegion &mask) +{ +#ifdef QGOODWINDOW + m_title_bar_mask = mask; +#else + Q_UNUSED(mask) +#endif +} + +void QGoodWindow::setMinimizeMask(const QRegion &mask) +{ +#ifdef QGOODWINDOW + m_min_mask = mask; +#else + Q_UNUSED(mask) +#endif +} + +void QGoodWindow::setMaximizeMask(const QRegion &mask) +{ +#ifdef QGOODWINDOW + m_max_mask = mask; +#else + Q_UNUSED(mask) +#endif +} + +void QGoodWindow::setCloseMask(const QRegion &mask) +{ +#ifdef QGOODWINDOW + m_cls_mask = mask; +#else + Q_UNUSED(mask) +#endif +} + +QRegion QGoodWindow::titleBarMask() const +{ +#ifdef QGOODWINDOW + return m_title_bar_mask; +#else + return QRegion(); +#endif +} + +QRegion QGoodWindow::minimizeMask() const +{ +#ifdef QGOODWINDOW + return m_min_mask; +#else + return QRegion(); +#endif +} + +QRegion QGoodWindow::maximizeMask() const +{ +#ifdef QGOODWINDOW + return m_max_mask; +#else + return QRegion(); +#endif +} + +QRegion QGoodWindow::closeMask() const +{ +#ifdef QGOODWINDOW + return m_cls_mask; +#else + return QRegion(); +#endif +} +/*** QGOODWINDOW FUNCTIONS END ***/ + +void QGoodWindow::setCentralWidget(QWidget *widget) +{ +#ifdef Q_OS_WIN + if (m_proxy_widget->layout()) + delete m_proxy_widget->layout(); + + QHBoxLayout *layout = new QHBoxLayout(m_proxy_widget); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(widget); + + m_central_widget = widget; +#else + QMainWindow::setCentralWidget(widget); +#endif +} + +QWidget *QGoodWindow::centralWidget() const +{ +#ifdef Q_OS_WIN + return m_central_widget; +#else + return QMainWindow::centralWidget(); +#endif +} + +QSize QGoodWindow::sizeHint() const +{ +#ifdef Q_OS_WIN + if (!m_central_widget) + return QMainWindow::sizeHint(); + + return m_central_widget->sizeHint(); +#else + return QMainWindow::sizeHint(); +#endif +} + +QSize QGoodWindow::minimumSizeHint() const +{ +#ifdef Q_OS_WIN + if (!m_central_widget) + return QMainWindow::minimumSizeHint(); + + return m_central_widget->minimumSizeHint(); +#else + return QMainWindow::minimumSizeHint(); +#endif +} + +void QGoodWindow::setFixedSize(int w, int h) +{ +#ifdef Q_OS_WIN + bool already_fixed = FIXED_SIZE(this); + + setMinimumWidth(w); + setMaximumWidth(w); + + setMinimumHeight(h); + setMaximumHeight(h); + + if (!already_fixed) + { + SetWindowLongW(m_hwnd, GWL_STYLE, + WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | + (!m_parent ? WS_MINIMIZEBOX : 0)); + } + + resize(w, h); +#else +#ifdef Q_OS_LINUX + m_window_flags ^= Qt::WindowMaximizeButtonHint; +#endif + QMainWindow::setFixedSize(w, h); +#endif +} + +void QGoodWindow::setFixedSize(const QSize &size) +{ +#if defined Q_OS_WIN + setFixedSize(size.width(), size.height()); +#else + QMainWindow::setFixedSize(size); +#endif +} + +void QGoodWindow::setMinimumSize(const QSize &size) +{ +#ifdef Q_OS_WIN + setMinimumWidth(size.width()); + setMinimumHeight(size.height()); +#else + QMainWindow::setMinimumSize(size); +#endif +} + +void QGoodWindow::setMaximumSize(const QSize &size) +{ +#ifdef Q_OS_WIN + setMaximumWidth(size.width()); + setMaximumHeight(size.height()); +#else + QMainWindow::setMaximumSize(size); +#endif +} + +void QGoodWindow::setMinimumWidth(int w) +{ +#ifdef Q_OS_WIN + m_minimum_width = qMax(w, 0); +#else + QMainWindow::setMinimumWidth(w); +#endif +} + +void QGoodWindow::setMinimumHeight(int h) +{ +#ifdef Q_OS_WIN + m_minimum_height = qMax(h, 0); +#else + QMainWindow::setMinimumHeight(h); +#endif +} + +void QGoodWindow::setMaximumWidth(int w) +{ +#ifdef Q_OS_WIN + m_maximum_width = qMax(w, 0); +#else + QMainWindow::setMaximumWidth(w); +#endif +} + +void QGoodWindow::setMaximumHeight(int h) +{ +#ifdef Q_OS_WIN + m_maximum_height = qMax(h, 0); +#else + QMainWindow::setMaximumHeight(h); +#endif +} + +QSize QGoodWindow::minimumSize() const +{ +#ifdef Q_OS_WIN + return QSize(minimumWidth(), minimumHeight()); +#else + return QMainWindow::minimumSize(); +#endif +} + +QSize QGoodWindow::maximumSize() const +{ +#ifdef Q_OS_WIN + return QSize(maximumWidth(), maximumHeight()); +#else + return QMainWindow::maximumSize(); +#endif +} + +int QGoodWindow::minimumWidth() const +{ +#ifdef Q_OS_WIN + return (m_minimum_width > 0 ? m_minimum_width : QMainWindow::minimumWidth()); +#else + return QMainWindow::minimumWidth(); +#endif +} + +int QGoodWindow::minimumHeight() const +{ +#ifdef Q_OS_WIN + return (m_minimum_height > 0 ? m_minimum_height : QMainWindow::minimumHeight()); +#else + return QMainWindow::minimumHeight(); +#endif +} + +int QGoodWindow::maximumWidth() const +{ +#ifdef Q_OS_WIN + return (m_maximum_width > 0 ? m_maximum_width : QMainWindow::maximumWidth()); +#else + return QMainWindow::maximumWidth(); +#endif +} + +int QGoodWindow::maximumHeight() const +{ +#ifdef Q_OS_WIN + return (m_maximum_height > 0 ? m_maximum_height : QMainWindow::maximumHeight()); +#else + return QMainWindow::maximumHeight(); +#endif +} + +QRect QGoodWindow::normalGeometry() const +{ +#ifdef Q_OS_WIN + return m_rect_normal; +#else + return QMainWindow::normalGeometry(); +#endif +} + +QRect QGoodWindow::frameGeometry() const +{ +#ifdef Q_OS_WIN + RECT window_rect; + GetWindowRect(m_hwnd, &window_rect); + + int x = qFloor(window_rect.left / m_pixel_ratio); + int y = qFloor(window_rect.top / m_pixel_ratio); + int w = qFloor((window_rect.right - window_rect.left) / m_pixel_ratio); + int h = qFloor((window_rect.bottom - window_rect.top) / m_pixel_ratio); + + QRect rect = QRect(x, y, w, h); + + if (isMaximized()) + { + const int border_width = BORDERWIDTH(); + rect.adjust(border_width, border_width, -border_width, -border_width); + } + + return rect; +#else + return QMainWindow::frameGeometry(); +#endif +} + +QRect QGoodWindow::geometry() const +{ +#ifdef Q_OS_WIN + RECT client_rect; + GetClientRect(m_hwnd, &client_rect); + + int w = qFloor((client_rect.right - client_rect.left) / m_pixel_ratio); + int h = qFloor((client_rect.bottom - client_rect.top) / m_pixel_ratio); + + QRect rect = QRect(x(), y(), w, h); + + return rect; +#else + return QMainWindow::geometry(); +#endif +} + +QRect QGoodWindow::rect() const +{ +#ifdef Q_OS_WIN + return QRect(0, 0, width(), height()); +#else + return QMainWindow::rect(); +#endif +} + +QPoint QGoodWindow::pos() const +{ +#ifdef Q_OS_WIN + return frameGeometry().topLeft(); +#else + return QMainWindow::pos(); +#endif +} + +QSize QGoodWindow::size() const +{ +#ifdef Q_OS_WIN + return QSize(width(), height()); +#else + return QMainWindow::size(); +#endif +} + +int QGoodWindow::x() const +{ +#ifdef Q_OS_WIN + return pos().x(); +#else + return QMainWindow::x(); +#endif +} + +int QGoodWindow::y() const +{ +#ifdef Q_OS_WIN + return pos().y(); +#else + return QMainWindow::y(); +#endif +} + +int QGoodWindow::width() const +{ +#ifdef Q_OS_WIN + return geometry().width(); +#else + return QMainWindow::width(); +#endif +} + +int QGoodWindow::height() const +{ +#ifdef Q_OS_WIN + return geometry().height(); +#else + return QMainWindow::height(); +#endif +} + +void QGoodWindow::move(int x, int y) +{ +#ifdef Q_OS_WIN + x = qFloor(x * m_pixel_ratio); + y = qFloor(y * m_pixel_ratio); + + SetWindowPos(m_hwnd, nullptr, x, y, 0, 0, SWP_NOSIZE | SWP_NOACTIVATE); +#else + QMainWindow::move(x, y); +#endif +} + +void QGoodWindow::move(const QPoint &pos) +{ +#ifdef Q_OS_WIN + move(pos.x(), pos.y()); +#else + QMainWindow::move(pos); +#endif +} + +void QGoodWindow::resize(int width, int height) +{ +#ifdef Q_OS_WIN + width = qFloor(width * m_pixel_ratio); + height = qFloor(height * m_pixel_ratio); + + if (m_is_using_system_borders) + { + width += BORDERWIDTH() * 2; + height += BORDERHEIGHT(); + } + + SetWindowPos(m_hwnd, nullptr, 0, 0, width, height, SWP_NOMOVE | SWP_NOACTIVATE); +#else + QMainWindow::resize(width, height); +#endif +} + +void QGoodWindow::resize(const QSize &size) +{ +#ifdef Q_OS_WIN + resize(size.width(), size.height()); +#else + QMainWindow::resize(size); +#endif +} + +void QGoodWindow::setGeometry(int x, int y, int w, int h) +{ +#ifdef Q_OS_WIN + move(x, y); + resize(w, h); +#else + QMainWindow::setGeometry(x, y, w, h); +#endif +} + +void QGoodWindow::setGeometry(const QRect &rect) +{ +#ifdef Q_OS_WIN + setGeometry(rect.x(), rect.y(), rect.width(), rect.height()); +#else + QMainWindow::setGeometry(rect); +#endif +} + +void QGoodWindow::activateWindow() +{ +#ifdef Q_OS_WIN + SetForegroundWindow(m_hwnd); +#else + QMainWindow::activateWindow(); +#endif +} + +void QGoodWindow::show() +{ +#ifdef Q_OS_WIN + if (!isVisible() && !m_window_state.testFlag(Qt::WindowNoState)) + { + setWindowStateWin(); + return; + } + + ShowWindow(m_hwnd, SW_SHOW); +#else + QMainWindow::show(); +#endif +} + +void QGoodWindow::showNormal() +{ +#ifdef Q_OS_WIN + if (isFullScreen()) + { + m_window_state ^= Qt::WindowFullScreen; + SetWindowLongW(m_hwnd, GWL_STYLE, GetWindowLongW(m_hwnd, GWL_STYLE) | WS_OVERLAPPEDWINDOW); + setGeometry(normalGeometry()); + setWindowMask(); + } + + ShowWindow(m_hwnd, SW_SHOWNORMAL); +#else + QMainWindow::showNormal(); +#endif +} + +void QGoodWindow::showMaximized() +{ +#if defined Q_OS_WIN || defined Q_OS_LINUX + if (FIXED_SIZE(this)) + return; +#endif +#ifdef Q_OS_WIN + if (isFullScreen()) + showNormal(); + + ShowWindow(m_hwnd, SW_SHOWMAXIMIZED); +#else + QMainWindow::showMaximized(); +#endif +} + +void QGoodWindow::showMinimized() +{ +#ifdef Q_OS_WIN + if (isFullScreen()) + showNormal(); + + ShowWindow(m_hwnd, SW_MINIMIZE); +#else + QMainWindow::showMinimized(); +#endif +} + +void QGoodWindow::showFullScreen() +{ +#if defined Q_OS_WIN || defined Q_OS_LINUX + if (FIXED_SIZE(this)) + return; +#endif +#ifdef Q_OS_WIN + if (isFullScreen()) + return; + + m_window_state = Qt::WindowFullScreen; + + ShowWindow(m_hwnd, SW_SHOWNORMAL); + + SetWindowLongW(m_hwnd, GWL_STYLE, GetWindowLongW(m_hwnd, GWL_STYLE) & ~WS_OVERLAPPEDWINDOW); + + HMONITOR monitor = MonitorFromWindow(m_hwnd, MONITOR_DEFAULTTONEAREST); + + MONITORINFO monitor_info; + memset(&monitor_info, 0, sizeof(MONITORINFO)); + monitor_info.cbSize = sizeof(MONITORINFO); + + GetMonitorInfoW(monitor, &monitor_info); + + QRect screen_geom; + screen_geom.moveTop(monitor_info.rcMonitor.top); + screen_geom.moveLeft(monitor_info.rcMonitor.left); + screen_geom.setWidth(monitor_info.rcMonitor.right - monitor_info.rcMonitor.left); + screen_geom.setHeight(monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top); + + SetWindowPos(m_hwnd, nullptr, screen_geom.x(), screen_geom.y(), + screen_geom.width(), screen_geom.height(), SWP_NOACTIVATE); + + QRect window_rect; + window_rect.moveTop(0); + window_rect.moveLeft(0); + window_rect.setWidth(screen_geom.width()); + window_rect.setHeight(screen_geom.height()); + +#ifdef QT_VERSION_QT5 + HRGN hrgn = QtWin::toHRGN(QRegion(window_rect)); +#endif +#ifdef QT_VERSION_QT6 + HRGN hrgn = QRegion(window_rect).toHRGN(); +#endif + + SetWindowRgn(m_hwnd, hrgn, TRUE); + + sizeMoveWindow(); +#else + QMainWindow::showFullScreen(); +#endif +} + +void QGoodWindow::hide() +{ +#ifdef Q_OS_WIN + ShowWindow(m_hwnd, SW_HIDE); +#else + QMainWindow::hide(); +#endif +} + +bool QGoodWindow::close() +{ +#ifdef Q_OS_WIN + if (m_closed) + return true; + + SendMessageW(m_hwnd, WM_CLOSE, 0, 0); + + return m_closed; +#else + return QMainWindow::close(); +#endif +} + +bool QGoodWindow::isVisible() const +{ +#ifdef Q_OS_WIN + return IsWindowVisible(m_hwnd); +#else + return QMainWindow::isVisible(); +#endif +} + +bool QGoodWindow::isEnabled() const +{ +#ifdef Q_OS_WIN + return IsWindowEnabled(m_hwnd); +#else + return QMainWindow::isEnabled(); +#endif +} + +bool QGoodWindow::isActiveWindow() const +{ +#ifdef Q_OS_WIN + const HWND active_window = GetActiveWindow(); + return ((active_window == m_hwnd) || IsChild(m_hwnd, active_window)); +#else + return QMainWindow::isActiveWindow(); +#endif +} + +bool QGoodWindow::isMaximized() const +{ +#ifdef Q_OS_WIN + WINDOWPLACEMENT wp; + wp.length = sizeof(WINDOWPLACEMENT); + GetWindowPlacement(m_hwnd, &wp); + return (wp.showCmd == SW_SHOWMAXIMIZED); +#else + return QMainWindow::isMaximized(); +#endif +} + +bool QGoodWindow::isMinimized() const +{ +#ifdef Q_OS_WIN + WINDOWPLACEMENT wp; + wp.length = sizeof(WINDOWPLACEMENT); + GetWindowPlacement(m_hwnd, &wp); + return (wp.showCmd == SW_SHOWMINIMIZED); +#else + return QMainWindow::isMinimized(); +#endif +} + +bool QGoodWindow::isFullScreen() const +{ +#ifdef Q_OS_WIN + return windowState().testFlag(Qt::WindowFullScreen); +#else + return QMainWindow::isFullScreen(); +#endif +} + +Qt::WindowStates QGoodWindow::windowState() const +{ +#ifdef Q_OS_WIN + return m_window_state; +#else + return QMainWindow::windowState(); +#endif +} + +void QGoodWindow::setWindowState(Qt::WindowStates state) +{ +#ifdef Q_OS_WIN + m_window_state = state; + + if (!isVisible()) + return; + + setWindowStateWin(); +#else + QMainWindow::setWindowState(state); +#endif +} + +QWindow *QGoodWindow::windowHandle() const +{ +#ifdef Q_OS_WIN + return m_window_handle; +#else + return QMainWindow::windowHandle(); +#endif +} + +qreal QGoodWindow::windowOpacity() const +{ + return windowHandle()->opacity(); +} + +void QGoodWindow::setWindowOpacity(qreal level) +{ + windowHandle()->setOpacity(level); +} + +QString QGoodWindow::windowTitle() const +{ + return QMainWindow::windowTitle(); +} + +void QGoodWindow::setWindowTitle(const QString &title) +{ +#ifdef Q_OS_WIN + SetWindowTextW(m_hwnd, reinterpret_cast(title.utf16())); + + QMainWindow::setWindowTitle(title); +#else + QMainWindow::setWindowTitle(title); +#endif +} + +QIcon QGoodWindow::windowIcon() const +{ + return QMainWindow::windowIcon(); +} + +void QGoodWindow::setWindowIcon(const QIcon &icon) +{ +#ifdef Q_OS_WIN + +#ifdef QT_VERSION_QT5 + HICON hicon_big = QtWin::toHICON(icon.pixmap(GetSystemMetrics(SM_CXICON), + GetSystemMetrics(SM_CYICON))); + HICON hicon_small = QtWin::toHICON(icon.pixmap(GetSystemMetrics(SM_CXSMICON), + GetSystemMetrics(SM_CYSMICON))); +#endif +#ifdef QT_VERSION_QT6 + HICON hicon_big = icon.pixmap(GetSystemMetrics(SM_CXICON), + GetSystemMetrics(SM_CYICON)).toImage().toHICON(); + HICON hicon_small = icon.pixmap(GetSystemMetrics(SM_CXSMICON), + GetSystemMetrics(SM_CYSMICON)).toImage().toHICON(); +#endif + + SendMessageW(m_hwnd, WM_SETICON, ICON_BIG, LPARAM(hicon_big)); + SendMessageW(m_hwnd, WM_SETICON, ICON_SMALL, LPARAM(hicon_small)); + + QMainWindow::setWindowIcon(icon); +#else + QMainWindow::setWindowIcon(icon); +#endif +} + +QByteArray QGoodWindow::saveGeometry() const +{ +#ifdef QGOODWINDOW + QByteArray geometry; + QDataStream stream(&geometry, QIODevice::WriteOnly); + stream.setVersion(QDataStream::Qt_5_0); + + QRect window_geom = normalGeometry(); + window_geom.moveTop(qFloor(window_geom.y() * m_pixel_ratio)); + window_geom.moveLeft(qFloor(window_geom.x() * m_pixel_ratio)); + window_geom.setWidth(qFloor(window_geom.width() * m_pixel_ratio)); + window_geom.setHeight(qFloor(window_geom.height() * m_pixel_ratio)); + + stream << qint32(window_geom.x()); + stream << qint32(window_geom.y()); + stream << qint32(window_geom.width()); + stream << qint32(window_geom.height()); + stream << windowState().testFlag(Qt::WindowMinimized); + stream << windowState().testFlag(Qt::WindowMaximized); + stream << windowState().testFlag(Qt::WindowFullScreen); + + return geometry; +#else + return QMainWindow::saveGeometry(); +#endif +} + +bool QGoodWindow::restoreGeometry(const QByteArray &geometry) +{ +#ifdef QGOODWINDOW + if (geometry.size() != int(sizeof(qint32) * 4 + sizeof(bool) * 3)) + return false; + + QDataStream stream(geometry); + stream.setVersion(QDataStream::Qt_5_0); + + qint32 rect_x; + qint32 rect_y; + qint32 rect_width; + qint32 rect_height; + bool minimized; + bool maximized; + bool fullscreen; + + stream >> rect_x; + stream >> rect_y; + stream >> rect_width; + stream >> rect_height; + stream >> minimized; + stream >> maximized; + stream >> fullscreen; + + if (stream.status() != QDataStream::Ok) + return false; + + QRegion screens; + + for (QScreen *screen : qApp->screens()) + { + QRect rect = screen->geometry(); + qreal pixel_ratio = screen->devicePixelRatio(); + rect.setWidth(qFloor(rect.width() * pixel_ratio)); + rect.setHeight(qFloor(rect.height() * pixel_ratio)); + screens += rect; + } + + QRect window_geom; + window_geom.moveTop(rect_y); + window_geom.moveLeft(rect_x); + window_geom.setWidth(rect_width); + window_geom.setHeight(rect_height); + + if (!screens.boundingRect().contains(window_geom)) + { + QScreen *screen = qApp->primaryScreen(); + + QRect rect = screen->availableGeometry(); + qreal pixel_ratio = screen->devicePixelRatio(); + rect.setWidth(qFloor(rect.width() * pixel_ratio)); + rect.setHeight(qFloor(rect.height() * pixel_ratio)); + + window_geom.moveCenter(rect.center()); + window_geom.setWidth(qMin(rect.width(), window_geom.width())); + window_geom.setHeight(qMin(rect.height(), window_geom.height())); + } + +#ifdef Q_OS_WIN + updateScreen(screenForPoint(window_geom.center())); +#endif + + window_geom.moveTop(qFloor(window_geom.y() / m_pixel_ratio)); + window_geom.moveLeft(qFloor(window_geom.x() / m_pixel_ratio)); + window_geom.setWidth(qFloor(window_geom.width() / m_pixel_ratio)); + window_geom.setHeight(qFloor(window_geom.height() / m_pixel_ratio)); + +#ifdef Q_OS_WIN + m_rect_normal = window_geom; +#endif + + setGeometry(window_geom); + + if (minimized) + setWindowState(Qt::WindowMinimized); + else if (maximized) + setWindowState(Qt::WindowMaximized); + else if (fullscreen) + setWindowState(Qt::WindowFullScreen); + + return true; +#else + return QMainWindow::restoreGeometry(geometry); +#endif +} + +bool QGoodWindow::event(QEvent *event) +{ +#if defined Q_OS_LINUX || defined Q_OS_MAC + switch (event->type()) + { + case QEvent::Leave: + { + buttonLeave(m_last_caption_button_hovered); + m_last_move_button = -1; + + break; + } + case QEvent::Hide: + case QEvent::HoverLeave: + { + if (QApplication::overrideCursor()) + QApplication::restoreOverrideCursor(); + + break; + } + default: + break; + } +#endif +#ifdef Q_OS_WIN + switch (event->type()) + { + case QEvent::ChildPolished: + { + QChildEvent *child_event = static_cast(event); + + QWidget *widget = qobject_cast(child_event->child()); + + if (!widget) + break; + +//Catch QMenu show event to fix show bug. +#if defined QT_VERSION_QT5 && QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + for (QWidget *widget : widget->findChildren()) + { + if (widget->metaObject()->className() == QStringLiteral("QMenu") || + widget->metaObject()->className() == QStringLiteral("QComboBoxPrivateContainer")) + { + widget->installEventFilter(this); + } + } +#endif + + if (!widget->isWindow()) + break; + + if (!FIXED_SIZE(widget)) + break; + + if (!widget->isModal()) + break; + + bool is_file_dialog = bool(qobject_cast(widget)); + + widget->adjustSize(); + widget->installEventFilter(this); + widget->setParent(bestParentForModalWindow(), + (!is_file_dialog ? widget->windowFlags() : Qt::WindowFlags(0))); + + break; + } + case QEvent::WindowBlocked: + { + EnableWindow(m_hwnd, FALSE); + break; + } + case QEvent::WindowUnblocked: + { + EnableWindow(m_hwnd, TRUE); + break; + } + case QEvent::Show: + case QEvent::WindowStateChange: + { + bool window_no_state = windowState().testFlag(Qt::WindowNoState); + + for (QSizeGrip *size_grip : findChildren()) + { + if (!size_grip->window()->windowFlags().testFlag(Qt::SubWindow)) + size_grip->setVisible(window_no_state); + } + + break; + } + case QEvent::Resize: + { + internalWidgetResize(); + + break; + } + case QEvent::Close: + { + if (m_closed) + { + hide(); + return true; + } + + break; + } + default: + break; + } +#endif +#ifdef Q_OS_LINUX + switch (event->type()) + { + case QEvent::Show: + case QEvent::Hide: + case QEvent::WindowActivate: + case QEvent::WindowDeactivate: + case QEvent::WindowStateChange: + case QEvent::Resize: + case QEvent::Move: + { + QRegion mask; + + if (isVisible() && windowState().testFlag(Qt::WindowNoState)) + { + const int radius = 10; + + QBitmap bmp(size()); + bmp.clear(); + + QPainter painter; + painter.begin(&bmp); + painter.setRenderHints(QPainter::Antialiasing); + painter.setPen(Qt::color1); + painter.setBrush(Qt::color1); + painter.drawRoundedRect(rect(), radius, radius, Qt::AbsoluteSize); + painter.end(); + + mask = bmp; + } + + setMask(mask); + + if (isVisible() && windowState().testFlag(Qt::WindowNoState)) + { + sizeMoveBorders(); + + if (isActiveWindow()) + { + m_shadow->showLater(); + } + } + else + { + m_shadow->hide(); + } + + break; + } + case QEvent::WindowBlocked: + { + if (FIXED_SIZE(this)) + break; + + if (!windowState().testFlag(Qt::WindowNoState)) + break; + + m_shadow->hide(); + + break; + } + case QEvent::WindowUnblocked: + { + if (FIXED_SIZE(this)) + break; + + activateWindow(); + + if (!windowState().testFlag(Qt::WindowNoState)) + break; + + m_shadow->show(); + + break; + } + default: + break; + } +#endif +#ifdef Q_OS_MAC + switch (event->type()) + { + case QEvent::WinIdChange: + { + QTimer::singleShot(0, this, [=]{ + notification.unregisterNotification(); + + notification.registerNotification("NSWindowWillEnterFullScreenNotification", winId()); + notification.registerNotification("NSWindowWillExitFullScreenNotification", winId()); + notification.registerNotification("NSWindowDidExitFullScreenNotification", winId()); + }); + + break; + } + case QEvent::Show: + { + if (isFullScreen()) + break; + + setMacOSStyle(int(macOSNative::StyleType::NoState)); + + break; + } + default: + break; + } +#endif + return QMainWindow::event(event); +} + +bool QGoodWindow::eventFilter(QObject *watched, QEvent *event) +{ +#ifdef Q_OS_WIN + if (watched == m_main_window) + { + switch (event->type()) + { + case QEvent::Show: + { + //Prevent send multiple show events at the window show event. + if (!m_self_generated_show_event) + return true; + + //Don't propagate show event if already visible. + if (m_visible) + return true; + + m_visible = true; + + break; + } + case QEvent::Hide: + { + //Don't propagate hide event if already invisible. + if (!m_visible) + return true; + + m_visible = false; + + break; + } + case QEvent::WindowStateChange: + { + if (isMinimized()) + m_visible = false; + + break; + } + case QEvent::Close: + { + //Don't propagate close event if it is generated + //by QGoodWindow. + if (!m_self_generated_close_event) + return true; + + break; + } + case QEvent::WindowActivate: + { + if (!m_main_window->isActiveWindow() || m_active_state) + return true; + + m_active_state = true; + + break; + } + case QEvent::WindowDeactivate: + { + if (m_main_window->isActiveWindow() || !m_active_state) + return true; + + m_active_state = false; + + break; + } + default: + break; + } + } + else if (m_parent && (watched == m_parent)) + { + switch (event->type()) + { + case QEvent::Close: + { + if (event->isAccepted()) + close(); + + break; + } + default: + break; + } + } +#if defined QT_VERSION_QT5 && QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + else if (watched->metaObject()->className() == QStringLiteral("QMenu") || + watched->metaObject()->className() == QStringLiteral("QComboBoxPrivateContainer")) + { + QWidget *widget = qobject_cast(watched); + + if (widget) + { + switch (event->type()) + { + case QEvent::Polish: + { + if (!widget->windowHandle()) + widget->createWinId(); + + break; + } + case QEvent::Show: + { + if (qobject_cast(widget->parentWidget())) + break; + + QScreen *screen = windowHandle()->screen(); + widget->windowHandle()->setScreen(screen); + + int x = qFloor(screen->geometry().x() / m_pixel_ratio); + int y = qFloor(screen->geometry().y() / m_pixel_ratio); + + x += widget->x(); + y += widget->y(); + + widget->move(x, y); + + break; + } + default: + break; + } + } + } +#endif + else if (QWidget *widget = qobject_cast(watched)) + { + if (widget->isWindow()) + { + switch (event->type()) + { + case QEvent::Show: + { + if (isMinimized()) + showNormal(); + + moveCenterWindow(widget); + + break; + } + case QEvent::Resize: + { + if (!FIXED_SIZE(widget)) + break; + + QTimer::singleShot(0, this, [=]{ + moveCenterWindow(widget); + }); + + break; + } + default: + break; + } + } + } +#endif +#if defined Q_OS_LINUX || defined Q_OS_MAC + QWidget *modal_widget = qApp->activeModalWidget(); + + if (modal_widget && modal_widget->window() != this) + return QMainWindow::eventFilter(watched, event); + + QWidget *widget = qobject_cast(watched); + + if (!widget) + return QMainWindow::eventFilter(watched, event); + + if (widget->window() != this + #ifdef Q_OS_LINUX + && widget != m_shadow + #endif + ) + return QMainWindow::eventFilter(watched, event); + + QPoint cursor_pos = QCursor::pos(); + qintptr button = ncHitTest(qFloor(cursor_pos.x() * m_pixel_ratio), qFloor(cursor_pos.y() * m_pixel_ratio)); + + switch (event->type()) + { +#ifdef Q_OS_MAC + case QEvent::ChildAdded: + case QEvent::ChildRemoved: + { + if (qApp->activeModalWidget()) + break; + + if (isFullScreen()) + break; + + if (m_on_animate_event) + break; + + setMacOSStyle(int(macOSNative::StyleType::NoState)); + + break; + } +#endif + case QEvent::ChildPolished: + { + QChildEvent *child_event = static_cast(event); + + QWidget *widget = qobject_cast(child_event->child()); + + if (!widget) + break; + + widget->setMouseTracking(true); + widget->installEventFilter(this); + + for (QWidget *w : widget->findChildren()) + { + w->setMouseTracking(true); + w->installEventFilter(this); + } + + break; + } + case QEvent::MouseButtonPress: + { + QMouseEvent *mouse_event = static_cast(event); + + if (mouse_event->button() != Qt::LeftButton) + break; + + switch (button) + { + case HTMINBUTTON: + case HTMAXBUTTON: + case HTCLOSE: + { + buttonPress(button); + break; + } + default: + break; + } + + break; + } + case QEvent::MouseMove: + { + if (m_last_move_button != button) + { + m_last_move_button = button; + + buttonLeave(m_last_caption_button_hovered); + + switch (button) + { + case HTMINBUTTON: + case HTMAXBUTTON: + case HTCLOSE: + { + if (!m_is_caption_button_pressed || (button == m_caption_button_pressed)) + buttonEnter(button); + + break; + } + default: + break; + } + } + + break; + } + case QEvent::MouseButtonRelease: + { + QMouseEvent *mouse_event = static_cast(event); + + if (mouse_event->button() != Qt::LeftButton) + break; + + m_last_move_button = -1; + + buttonRelease(m_caption_button_pressed, (button == m_caption_button_pressed)); + + break; + } + default: + break; + } +#endif +#ifdef Q_OS_LINUX + if (m_is_caption_button_pressed) + return QMainWindow::eventFilter(watched, event); + + switch (event->type()) + { + case QEvent::MouseButtonDblClick: + { + QMouseEvent *mouse_event = static_cast(event); + + if (mouse_event->button() == Qt::LeftButton) + { + if (m_margin == HTCAPTION) + { + if (!isMaximized()) + showMaximized(); + else + showNormal(); + } + } + + switch (m_margin) + { + case HTCAPTION: + case HTMINBUTTON: + case HTMAXBUTTON: + case HTCLOSE: + return true; + default: + break; + } + + break; + } + case QEvent::MouseButtonPress: + { + setCursorForCurrentPos(); + + QMouseEvent *mouse_event = static_cast(event); + + if (!m_resize_move && mouse_event->button() == Qt::LeftButton) + { + if (m_margin != HTNOWHERE) + m_resize_move = true; + } + + switch (m_margin) + { + case HTCAPTION: + case HTMINBUTTON: + case HTMAXBUTTON: + case HTCLOSE: + return true; + default: + break; + } + + break; + } + case QEvent::MouseMove: + { + setCursorForCurrentPos(); + + QMouseEvent *mouse_event = static_cast(event); + + if (m_resize_move && mouse_event->buttons() == Qt::LeftButton) + sizeMove(); + + switch (m_margin) + { + case HTCAPTION: + case HTMINBUTTON: + case HTMAXBUTTON: + case HTCLOSE: + return true; + default: + break; + } + + break; + } + case QEvent::MouseButtonRelease: + { + setCursorForCurrentPos(); + + QMouseEvent *mouse_event = static_cast(event); + + if (m_resize_move && mouse_event->button() == Qt::LeftButton) + { + if (m_margin != HTNOWHERE) + m_resize_move = false; + } + + switch (m_margin) + { + case HTCAPTION: + case HTMINBUTTON: + case HTMAXBUTTON: + case HTCLOSE: + return true; + default: + break; + } + + break; + } + case QEvent::Wheel: + { + switch (m_margin) + { + case HTCAPTION: + case HTMINBUTTON: + case HTMAXBUTTON: + case HTCLOSE: + return true; + default: + break; + } + + break; + } + case QEvent::ContextMenu: + { + switch (m_margin) + { + case HTCAPTION: + case HTMINBUTTON: + case HTMAXBUTTON: + case HTCLOSE: + return true; + default: + break; + } + + break; + } + default: + break; + } +#endif +#ifdef Q_OS_MAC + if (m_parent) + return QMainWindow::eventFilter(watched, event); + + if (m_is_caption_button_pressed) + return QMainWindow::eventFilter(watched, event); + + int margin = int(ncHitTest(qFloor(cursor_pos.x() * m_pixel_ratio), qFloor(cursor_pos.y() * m_pixel_ratio))); + + switch (event->type()) + { + case QEvent::MouseButtonDblClick: + { + if (margin == HTCAPTION) + { + QMouseEvent *mouse_event = static_cast(event); + + if (!isFullScreen()) + { + if (mouse_event->button() == Qt::LeftButton) + { + if (margin == HTCAPTION) + { + if (!isMaximized()) + showMaximized(); + else + showNormal(); + } + } + } + } + + switch (margin) + { + case HTCAPTION: + case HTMINBUTTON: + case HTMAXBUTTON: + case HTCLOSE: + return true; + default: + break; + } + + break; + } + case QEvent::MouseButtonPress: + { + if (margin == HTCAPTION) + { + QMouseEvent *mouse_event = static_cast(event); + + if (!isFullScreen()) + { + if (mouse_event->button() == Qt::LeftButton) + { + m_cursor_move_pos = cursor_pos - pos(); + } + } + } + + m_mouse_button_pressed = true; + + switch (margin) + { + case HTCAPTION: + case HTMINBUTTON: + case HTMAXBUTTON: + case HTCLOSE: + return true; + default: + break; + } + + break; + } + case QEvent::MouseMove: + { + if (!m_cursor_move_pos.isNull()) + { + QMouseEvent *mouse_event = static_cast(event); + + if (!isFullScreen()) + { + if (mouse_event->buttons() == Qt::LeftButton) + { + move(cursor_pos - m_cursor_move_pos); + } + } + } + + if (!m_mouse_button_pressed) + { + QWidget *widget = QApplication::widgetAt(cursor_pos); + + if (widget) + { + switch (margin) + { + case HTCAPTION: + case HTMINBUTTON: + case HTMAXBUTTON: + case HTCLOSE: + { + if (QApplication::overrideCursor() && + QApplication::overrideCursor()->shape() == Qt::ArrowCursor) + break; + + QApplication::setOverrideCursor(Qt::ArrowCursor); + + break; + } + case HTNOWHERE: + { + if (!QApplication::overrideCursor()) + break; + + if (QApplication::overrideCursor()->shape() != Qt::ArrowCursor) + break; + + QApplication::restoreOverrideCursor(); + + break; + } + default: + break; + } + } + } + + switch (margin) + { + case HTCAPTION: + case HTMINBUTTON: + case HTMAXBUTTON: + case HTCLOSE: + return true; + default: + break; + } + + break; + } + case QEvent::MouseButtonRelease: + { + if (!m_cursor_move_pos.isNull()) + { + QMouseEvent *mouse_event = static_cast(event); + + QWidget *widget = QApplication::widgetAt(cursor_pos); + + if (widget && !isFullScreen()) + { + if (mouse_event->button() == Qt::LeftButton) + m_cursor_move_pos = QPoint(); + } + } + + m_mouse_button_pressed = false; + + switch (margin) + { + case HTCAPTION: + case HTMINBUTTON: + case HTMAXBUTTON: + case HTCLOSE: + return true; + default: + break; + } + + break; + } + case QEvent::Wheel: + { + switch (margin) + { + case HTCAPTION: + case HTMINBUTTON: + case HTMAXBUTTON: + case HTCLOSE: + return true; + default: + break; + } + + break; + } + case QEvent::ContextMenu: + { + switch (margin) + { + case HTCAPTION: + case HTMINBUTTON: + case HTMAXBUTTON: + case HTCLOSE: + return true; + default: + break; + } + + break; + } + default: + break; + } +#endif + return QMainWindow::eventFilter(watched, event); +} + +bool QGoodWindow::nativeEvent(const QByteArray &eventType, void *message, qgoodintptr *result) +{ +#ifdef Q_OS_LINUX + if (eventType == "xcb_generic_event_t") + { + xcb_generic_event_t *event = static_cast(message); + + if (event->response_type == XCB_GE_GENERIC) + { + if (m_resize_move_started) + { + m_resize_move_started = false; + + //Fix mouse problems after resize or move. + QTest::mouseClick(windowHandle(), Qt::NoButton, Qt::NoModifier); + } +#ifdef QT_VERSION_QT5 + else + { + //Fix no mouse event after moving mouse from resize borders. + QEvent event(QEvent::MouseMove); + QApplication::sendEvent(this, &event); + } +#endif + } + } +#endif + return QMainWindow::nativeEvent(eventType, message, result); +} + +#ifdef Q_OS_WIN +void QGoodWindow::initGW() +{ + setProperty("_q_embedded_native_parent_handle", WId(m_hwnd)); + QMainWindow::setWindowFlags(Qt::FramelessWindowHint); + + QEvent event(QEvent::EmbeddingControl); + QApplication::sendEvent(this, &event); +} + +void QGoodWindow::destroyGW() +{ + QMainWindow::close(); +#ifdef QT_VERSION_QT5 + QMainWindow::destroy(true, true); +#endif +#ifdef QT_VERSION_QT6 + QMainWindow::destroy(false, false); +#endif +} + +void QGoodWindow::closeGW() +{ + if (m_shadow) + { + if (!m_shadow->parentWidget()) + delete m_shadow; + else + QTimer::singleShot(0, m_shadow, &QWidget::close); + } + + if (m_helper_widget) + delete m_helper_widget; + +#ifdef QT_VERSION_QT6 + if (m_helper_window) + delete m_helper_window; +#endif + + qDeleteAll(findChildren()); + + destroyGW(); + + SetWindowLongPtrW(m_hwnd, GWLP_USERDATA, reinterpret_cast(nullptr)); +} + +void QGoodWindow::setWindowStateWin() +{ + if (m_window_state.testFlag(Qt::WindowFullScreen)) + showFullScreen(); + else if (m_window_state.testFlag(Qt::WindowMaximized)) + showMaximized(); + else if (m_window_state.testFlag(Qt::WindowMinimized)) + showMinimized(); + else if (m_window_state.testFlag(Qt::WindowNoState)) + showNormal(); +} + +LRESULT QGoodWindow::WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + QGoodWindow *gw = reinterpret_cast(GetWindowLongPtrW(hwnd, GWLP_USERDATA)); + + if (!gw) + return DefWindowProcW(hwnd, message, wParam, lParam); + + switch (message) + { + case WM_ACTIVATE: + { + switch (wParam) + { + case WA_ACTIVE: + case WA_CLICKACTIVE: + { + QTimer::singleShot(0, gw, [=]{ + gw->setWidgetFocus(); + gw->handleActivation(); + }); + + break; + } + case WA_INACTIVE: + { + if (gw->focusWidget()) + { + //If app going to be inactive, + //save current focused widget + //to restore focus later. + + gw->m_focus_widget = gw->focusWidget(); + } + + QTimer::singleShot(0, gw, [=]{ + gw->handleDeactivation(); + }); + + if (!IsWindowEnabled(hwnd)) + { + if (gw->m_shadow) + gw->m_shadow->hide(); + } + + break; + } + default: + break; + } + + break; + } + case WM_SETFOCUS: + { + //Pass focus to last focused widget. + gw->setWidgetFocus(); + gw->m_main_window->activateWindow(); + + break; + } + case WM_SIZE: + { + gw->sizeMoveWindow(); + + break; + } + case WM_MOVE: + { + gw->sizeMoveWindow(); + + QMoveEvent event(QPoint(gw->x(), gw->y()), QPoint()); + QApplication::sendEvent(gw, &event); + + break; + } + case WM_GETMINMAXINFO: + { + MINMAXINFO *mmi = reinterpret_cast(lParam); + + int border_width = 0; + + if (gw->m_is_using_system_borders && gw->windowState().testFlag(Qt::WindowNoState)) + border_width = BORDERWIDTH(); + + QSize minimum = gw->minimumSize(); + + QSize sizeHint = gw->minimumSizeHint(); + + mmi->ptMinTrackSize.x = qFloor(qMax(minimum.width(), sizeHint.width()) * + gw->m_pixel_ratio) + border_width * 2; + mmi->ptMinTrackSize.y = qFloor(qMax(minimum.height(), sizeHint.height()) * + gw->m_pixel_ratio) + border_width; + + QSize maximum = gw->maximumSize(); + + mmi->ptMaxTrackSize.x = qFloor(maximum.width() * gw->m_pixel_ratio) + border_width * 2; + mmi->ptMaxTrackSize.y = qFloor(maximum.height() * gw->m_pixel_ratio) + border_width; + + break; + } + case WM_CLOSE: + { + //Send Qt QCloseEvent to the window, + //which allows to accept or reject the window close. + gw->m_self_generated_close_event = true; + QCloseEvent event; + QApplication::sendEvent(gw, &event); + gw->m_self_generated_close_event = false; + + if (!event.isAccepted()) + return 0; + + gw->hide(); + + gw->m_closed = true; + + //Begin the cleanup. + + gw->closeGW(); + + if (gw->testAttribute(Qt::WA_DeleteOnClose)) + delete gw; + + //End the cleanup. + + return DefWindowProcW(hwnd, message, wParam, lParam); + } + case WM_DESTROY: + { + return DefWindowProcW(hwnd, message, wParam, lParam); + } + case WM_NCHITTEST: + { + QPoint pos = QPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); + + LRESULT lresult = LRESULT(gw->ncHitTest(pos.x(), pos.y())); + + if (FIXED_SIZE(gw)) + { + //If have fixed size, then only HTCAPTION hit test is valid, + //which means that only the title bar click and move is valid. + + if (lresult != HTNOWHERE + && lresult != HTSYSMENU + && lresult != HTMINBUTTON + && lresult != HTMAXBUTTON + && lresult != HTCLOSE + && lresult != HTCAPTION) + lresult = HTNOWHERE; + + return lresult; + } + else if (FIXED_WIDTH(gw)) + { + if (lresult != HTNOWHERE + && lresult != HTSYSMENU + && lresult != HTMINBUTTON + && lresult != HTMAXBUTTON + && lresult != HTCLOSE + && lresult != HTCAPTION + && lresult != HTTOP + && lresult != HTBOTTOM) + lresult = HTNOWHERE; + + return lresult; + } + else if (FIXED_HEIGHT(gw)) + { + if (lresult != HTNOWHERE + && lresult != HTSYSMENU + && lresult != HTMINBUTTON + && lresult != HTMAXBUTTON + && lresult != HTCLOSE + && lresult != HTCAPTION + && lresult != HTLEFT + && lresult != HTRIGHT) + lresult = HTNOWHERE; + + return lresult; + } + + return lresult; + } + case WM_NCMOUSELEAVE: + { + if (gw->m_is_caption_button_pressed) + break; + + gw->buttonLeave(gw->m_last_caption_button_hovered); + + break; + } + case WM_NCMOUSEMOVE: + { + QPoint pos = QPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); + + qintptr button = gw->ncHitTest(pos.x(), pos.y()); + + bool valid_caption_button = gw->winButtonHover(button); + + if (valid_caption_button) + { + if (gw->m_is_caption_button_pressed) + { + if (GetCapture() != hwnd) + { + SetCapture(hwnd); + } + } + } + else + { + gw->buttonLeave(gw->m_last_caption_button_hovered); + } + + break; + } + case WM_MOUSEMOVE: + { + QPoint pos = QPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); + + const int border_width = (gw->m_is_win_11_or_greater && + gw->windowState().testFlag(Qt::WindowNoState)) ? BORDERWIDTH() : 0; + + pos.setX(qFloor(gw->x() * gw->m_pixel_ratio) + border_width + pos.x()); + pos.setY(qFloor(gw->y() * gw->m_pixel_ratio) + pos.y()); + + qintptr button = gw->ncHitTest(pos.x(), pos.y()); + + bool valid_caption_button = gw->winButtonHover(button); + + if (!valid_caption_button) + { + if (!gw->m_is_caption_button_pressed && GetCapture() == hwnd) + { + ReleaseCapture(); + } + + gw->buttonLeave(gw->m_last_caption_button_hovered); + } + + break; + } + case WM_NCLBUTTONDBLCLK: + { + QPoint pos = QPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); + + qintptr button = gw->ncHitTest(pos.x(), pos.y()); + + if (button == HTSYSMENU) + { + QTimer::singleShot(0, gw, &QGoodWindow::close); + return 0; + } + + break; + } + case WM_NCLBUTTONDOWN: + { + QPoint pos = QPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); + + qintptr button = gw->ncHitTest(pos.x(), pos.y()); + + if (button == HTSYSMENU) + { + gw->iconClicked(); + return 0; + } + + bool valid_caption_button = gw->buttonPress(button); + + if (valid_caption_button) + return 0; + + break; + } + case WM_NCLBUTTONUP: + { + QPoint pos = QPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); + + qintptr button = gw->ncHitTest(pos.x(), pos.y()); + + if (button == HTSYSMENU) + { + return 0; + } + + bool valid_caption_button = gw->buttonRelease(gw->m_caption_button_pressed, + (button == gw->m_caption_button_pressed)); + + if (valid_caption_button) + return 0; + + break; + } + case WM_LBUTTONDOWN: + { + QPoint pos = QPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); + + const int border_width = (gw->m_is_win_11_or_greater && + gw->windowState().testFlag(Qt::WindowNoState)) ? BORDERWIDTH() : 0; + + pos.setX(qFloor(gw->x() * gw->m_pixel_ratio) + border_width + pos.x()); + pos.setY(qFloor(gw->y() * gw->m_pixel_ratio) + pos.y()); + + qintptr button = gw->ncHitTest(pos.x(), pos.y()); + + gw->buttonPress(button); + + break; + } + case WM_LBUTTONUP: + { + QPoint pos = QPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); + + const int border_width = (gw->m_is_win_11_or_greater && + gw->windowState().testFlag(Qt::WindowNoState)) ? BORDERWIDTH() : 0; + + pos.setX(qFloor(gw->x() * gw->m_pixel_ratio) + border_width + pos.x()); + pos.setY(qFloor(gw->y() * gw->m_pixel_ratio) + pos.y()); + + qintptr button = gw->ncHitTest(pos.x(), pos.y()); + + if (button != gw->m_caption_button_pressed) + gw->buttonEnter(button); + + gw->buttonRelease(gw->m_caption_button_pressed, (button == gw->m_caption_button_pressed)); + + break; + } + case WM_SYSKEYDOWN: + case WM_SYSKEYUP: + { + if ((GetKeyState(VK_SHIFT) & 0x8000) && wParam == VK_F10) + { + //When SHIFT+F10 is pressed. + gw->showContextMenu(); + return 0; + } + + if ((GetKeyState(VK_MENU) & 0x8000) && wParam == VK_SPACE) + { + //When ALT+SPACE is pressed. + gw->showContextMenu(); + return 0; + } + + break; + } + case WM_NCRBUTTONUP: + { + //Show context menu on right click on title bar. + + int x = GET_X_LPARAM(lParam); + int y = GET_Y_LPARAM(lParam); + + LRESULT lRet = LRESULT(gw->ncHitTest(x, y)); + + if (lRet == HTCAPTION || lRet == HTSYSMENU) + gw->showContextMenu(x, y); + + break; + } + case WM_NCCALCSIZE: + { + if (gw->isFullScreen()) + break; + + if (gw->m_is_using_system_borders && !gw->isMaximized()) + { + const int border_width = BORDERWIDTH(); + + RECT *rect = reinterpret_cast(lParam); + rect->left += border_width; + rect->bottom -= border_width; + rect->right -= border_width; + } + + if (gw->isMaximized()) + { + //Compensate window client area when maximized, + //by removing BORDERWIDTH value for all edges. + + const int border_width = BORDERWIDTH(); + + InflateRect(reinterpret_cast(lParam), -border_width, -border_width); + } + + //Make the whole window as client area. + return 0; + } +#ifdef QT_VERSION_QT5 + case WM_NCPAINT: + case WM_NCACTIVATE: + { + //Prevent undesired painting on window when DWM is not enabled. + + if (!QtWin::isCompositionEnabled()) + return TRUE; + + break; + } +#endif + case WM_WINDOWPOSCHANGING: + { + Qt::WindowState state; + + if (gw->isMinimized()) + state = Qt::WindowMinimized; + else if (gw->isMaximized()) + state = Qt::WindowMaximized; + else if (gw->isFullScreen()) + state = Qt::WindowFullScreen; + else + state = Qt::WindowNoState; + + if (state != gw->m_last_state) + { + //If window state changed. + + gw->m_window_state = state; + + if (gw->m_last_state == Qt::WindowMinimized) + { + QTimer::singleShot(0, gw, [=]{ + gw->m_self_generated_show_event = true; + QShowEvent event; + QApplication::sendEvent(gw, &event); + gw->m_self_generated_show_event = false; + }); + } + + QWindowStateChangeEvent event(gw->m_last_state); + QApplication::sendEvent(gw, &event); + + gw->m_last_state = state; + + if (gw->m_shadow) + { + if (state != Qt::WindowNoState) + { + //Hide shadow if not on "normal" state. + gw->m_shadow->hide(); + } + else if (gw->isVisible()) + { + //Show shadow if switching to "normal" state, with delay. + gw->m_shadow->showLater(); + } + } + + if (state == Qt::WindowMinimized) + { + if (gw->focusWidget()) + { + //If app going to be minimized, + //save current focused widget + //to restore focus later. + + gw->m_focus_widget = gw->focusWidget(); + } + } + } + + WINDOWPOS *pwp = reinterpret_cast(lParam); + + if (pwp->flags & SWP_SHOWWINDOW) + { + if (!gw->m_main_window->isVisible()) + { + auto func_init = [=] + { + gw->updateScreen(gw->screenForWindow(hwnd)); + }; + + auto func_flush = [=] + { + //Fix native window frozen state + gw->m_main_window->resize(gw->size() * 2); + gw->m_main_window->resize(gw->size()); + }; + + auto func_finish = [=] + { + gw->internalWidgetResize(); + gw->m_window_ready_for_resize = true; + gw->sizeMoveMainWindow(); + }; + + auto func_show = [=] + { + gw->m_main_window->show(); + }; + + if (!gw->m_main_window->testAttribute(Qt::WA_NativeWindow)) + { + func_init(); + func_flush(); + func_finish(); + func_show(); + } + else + { + QTimer::singleShot(0, gw, [=]{ + func_init(); + func_show(); + func_flush(); + func_finish(); + }); + } + + gw->m_self_generated_show_event = true; + QShowEvent event; + QApplication::sendEvent(gw, &event); + gw->m_self_generated_show_event = false; + + if (gw->m_shadow && gw->windowState().testFlag(Qt::WindowNoState)) + gw->m_shadow->showLater(); + } + +#ifdef QT_VERSION_QT6 + if (gw->m_helper_window) + gw->m_helper_window->show(); +#endif + } + else if (pwp->flags & SWP_HIDEWINDOW) + { + if (gw->focusWidget()) + { + //If app have a valid focused widget, + //save them to restore focus later. + + gw->m_focus_widget = gw->focusWidget(); + } + + if (gw->m_shadow) + gw->m_shadow->hide(); + +#ifdef QT_VERSION_QT6 + if (gw->m_helper_window) + gw->m_helper_window->hide(); +#endif + + QHideEvent event; + QApplication::sendEvent(gw, &event); + } + + if (pwp->flags == (SWP_NOSIZE + SWP_NOMOVE)) + { + //Activate window to fix no activation + //problem when QGoodWindow isn't shown initially in + //active state. + + if (gw->isMinimized() || !gw->isVisible()) + break; + + gw->m_main_window->activateWindow(); + + if (gw->windowState().testFlag(Qt::WindowNoState)) + { + if (gw->m_shadow) + gw->m_shadow->showLater(); + } + } + + break; + } +#ifdef QT_VERSION_QT5 + case WM_WINDOWPOSCHANGED: + { + if (!QtWin::isCompositionEnabled() && gw->isMaximized()) + { + //Hack for prevent window goes to full screen when it's being maximized, + //enable WS_CAPTION and schedule for disable it, + //not mantaining WS_CAPTION all the time to prevent undesired painting on window + //when title or icon of the window is changed when DWM is not enabled. + + gw->enableCaption(); + QTimer::singleShot(0, gw, &QGoodWindow::disableCaption); + } + + break; + } +#endif + case WM_SETTEXT: + { + Q_EMIT gw->windowTitleChanged(gw->windowTitle()); + break; + } + case WM_SETICON: + { + Q_EMIT gw->windowIconChanged(gw->windowIcon()); + break; + } + case WM_ENTERSIZEMOVE: + { + gw->m_timer_move->stop(); + + break; + } + case WM_EXITSIZEMOVE: + { + if (gw->windowHandle()->screen() != gw->screenForWindow(hwnd)) + gw->m_timer_move->start(); + + break; + } + case WM_DPICHANGED: + { + gw->m_timer_move->stop(); + + //Wait to refresh screen pixel ratio information. + qApp->processEvents(); + + QScreen *screen = gw->screenForWindow(hwnd); + + if (!screen) + break; + + gw->screenChangeMoveWindow(screen); + gw->updateScreen(screen); + gw->windowScaleChanged(); + + break; + } + case WM_DISPLAYCHANGE: + { + if (gw->isFullScreen()) + gw->showNormal(); + + break; + } + case WM_SETTINGCHANGE: + { + if (QGoodWindowUtils::isWinXOrGreater(10, 0, 0)) //Windows 10 or later. + { + if (QString::fromWCharArray(LPCWSTR(lParam)) == "ImmersiveColorSet") + { + QTimer::singleShot(0, gw, &QGoodWindow::themeChanged); + } + } + + break; + } +#ifdef QT_VERSION_QT5 + case WM_THEMECHANGED: + case WM_DWMCOMPOSITIONCHANGED: + { + //Send the window change event to m_helper_widget, + //this hack corrects the background color when switching between + //Windows composition modes or system themes. + SendMessageW(HWND(gw->m_helper_widget->winId()), message, 0, 0); + + if (QtWin::isCompositionEnabled()) + { + QTimer::singleShot(100, gw, [=]{ + gw->enableCaption(); + gw->frameChanged(); + SetWindowRgn(hwnd, nullptr, TRUE); + }); + } + else + { + QTimer::singleShot(100, gw, [=]{ + gw->disableCaption(); + gw->frameChanged(); + }); + } + + QTimer::singleShot(100, gw, [=]{ + gw->sizeMoveWindow(); + gw->repaint(); + }); + + break; + } +#endif + default: + break; + } + + MSG msg; + msg.hwnd = hwnd; + msg.message = message; + msg.lParam = lParam; + msg.wParam = wParam; + + qgoodintptr result = 0; + + bool return_value = gw->nativeEvent(QByteArray(), &msg, &result); + + if (return_value) + return result; + + return DefWindowProcW(hwnd, message, wParam, lParam); +} + +void QGoodWindow::handleActivation() +{ + if (m_shadow) + { + if (windowState().testFlag(Qt::WindowNoState)) + { + m_shadow->setActive(true); + + //If in "normal" state, make shadow visible. + if (isVisible()) + m_shadow->show(); + } + } +} + +void QGoodWindow::handleDeactivation() +{ + if (m_shadow) + m_shadow->setActive(false); + + if (!isEnabled()) + buttonLeave(m_last_caption_button_hovered); +} + +void QGoodWindow::setWidgetFocus() +{ + if (!m_focus_widget) + { + bool have_focusable_widget = false; + + for (QWidget *next_focus : findChildren()) + { + if (next_focus->focusPolicy() != Qt::NoFocus) + { + //Set focus to first focusable widget. + have_focusable_widget = true; + m_focus_widget = next_focus; + break; + } + } + + if (!have_focusable_widget) + { + //If not have focusable widget + //set focus to m_main_window. + m_focus_widget = m_main_window; + } + } + + if (m_focus_widget) + { + //If have a valid m_focus_widget, + //set the focus to this widget. + m_focus_widget->setFocus(); + } +} + +void QGoodWindow::enableCaption() +{ + SetWindowLongW(m_hwnd, GWL_STYLE, GetWindowLongW(m_hwnd, GWL_STYLE) | WS_CAPTION); +} + +void QGoodWindow::disableCaption() +{ + SetWindowLongW(m_hwnd, GWL_STYLE, GetWindowLongW(m_hwnd, GWL_STYLE) & ~WS_CAPTION); +} + +void QGoodWindow::frameChanged() +{ + SetWindowPos(m_hwnd, nullptr, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_FRAMECHANGED); +} + +void QGoodWindow::sizeMoveWindow() +{ + if (!isVisible() || isMinimized()) + return; + + if (windowState().testFlag(Qt::WindowNoState)) + m_rect_normal = geometry(); + +#ifdef QT_VERSION_QT5 + setWindowMask(); +#endif + + sizeMoveMainWindow(); + + moveShadow(); +} + +void QGoodWindow::sizeMoveMainWindow() +{ + bool opengl_adjust = false; + +#if defined QT_VERSION_QT5 && QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + QWidgetList list; + if (m_central_widget) + { + list.append(m_central_widget); + list.append(m_central_widget->findChildren()); + } + + for (QWidget *widget : list) + { + QMetaObject *obj = const_cast(widget->metaObject()); + + while (obj) + { + if (obj->className() == QStringLiteral("QOpenGLWidget")) + { + opengl_adjust = true; + break; + } + else if (obj->className() == QStringLiteral("QQuickWidget")) + { + opengl_adjust = true; + break; + } + + obj = const_cast(obj->superClass()); + } + + if (opengl_adjust) + break; + } +#endif + //Move proxy widget. + int y = (opengl_adjust && (!qFuzzyCompare(m_pixel_ratio, qreal(1))) ? height() : 0); + m_proxy_widget->setGeometry(0, y, width(), height()); + + HWND hwnd = HWND(m_main_window->winId()); + + RECT win_rect; + GetClientRect(m_hwnd, &win_rect); + int w = win_rect.right - win_rect.left; + int h = win_rect.bottom - win_rect.top; + + //Move main window widget inside native main window. + SetWindowPos(hwnd, nullptr, 0, 0, w, h, SWP_NOACTIVATE); +} + +void QGoodWindow::setWindowMask() +{ +#ifdef QT_VERSION_QT5 + if (!QtWin::isCompositionEnabled()) + { + QRect window_rect = rect(); + + window_rect.setWidth(qFloor(window_rect.width() * m_pixel_ratio)); + window_rect.setHeight(qFloor(window_rect.height() * m_pixel_ratio)); + + const int border_width = BORDERWIDTH(); + + if (isMaximized()) + window_rect.moveTopLeft(QPoint(border_width, border_width)); + + SetWindowRgn(m_hwnd, QtWin::toHRGN(window_rect), TRUE); + } + else + { + SetWindowRgn(m_hwnd, nullptr, TRUE); + } +#endif +#ifdef QT_VERSION_QT6 + SetWindowRgn(m_hwnd, nullptr, TRUE); +#endif +} + +void QGoodWindow::moveShadow() +{ + if (!m_shadow) + return; + + if (!windowState().testFlag(Qt::WindowNoState)) + return; + + const int shadow_width = m_shadow->shadowWidth(); + const QPoint adjusted_pos = screenAdjustedPos(); + + int pos_x = x() - shadow_width + adjusted_pos.x(); + int pos_y = y() - shadow_width + adjusted_pos.y(); + int w = width() + shadow_width * 2; + int h = height() + shadow_width * 2; + + m_shadow->setGeometry(pos_x, pos_y, w, h); + + m_shadow->setActive(isActiveWindow()); +} + +void QGoodWindow::screenChangeMoveWindow(QScreen *screen) +{ + if (!screen) + return; + + QRect screen_geom = screen->geometry(); + qreal screen_pixel_ratio = screen->devicePixelRatio(); + screen_geom.setWidth(qFloor(screen_geom.width() * screen_pixel_ratio)); + screen_geom.setHeight(qFloor(screen_geom.height() * screen_pixel_ratio)); + + RECT rect; + GetWindowRect(m_hwnd, &rect); + + QRect window_geom; + window_geom.moveTop(rect.top); + window_geom.moveLeft(rect.left); + window_geom.setWidth(rect.right - rect.left); + window_geom.setHeight(rect.bottom - rect.top); + + QPoint center_pos = window_geom.center(); + int border_width = m_is_using_system_borders ? BORDERWIDTH() : 0; + + QSize window_size = size(); + window_size.setWidth(qFloor(window_size.width() * screen_pixel_ratio) + border_width * 2); + window_size.setHeight(qFloor(window_size.height() * screen_pixel_ratio) + border_width); + window_geom.setSize(window_size); + + //Keep X position. + center_pos.setY(window_geom.center().y()); + + const int top_dist = qAbs(screen_geom.top() - center_pos.y()); + const int bottom_dist = qAbs(screen_geom.bottom() - center_pos.y()); + const int left_dist = qAbs(screen_geom.left() - center_pos.x()); + const int right_dist = qAbs(screen_geom.right() - center_pos.x()); + + const int margin = 10; + + QRect center_rect; + center_rect.moveCenter(center_pos); + center_rect.adjust(-margin, -margin, margin, margin); + + if (!screen_geom.contains(center_rect)) + { + if (left_dist < right_dist) + { + if (left_dist < top_dist && left_dist < bottom_dist) + { + center_pos.setX(screen_geom.left() + margin); + } + else if (top_dist < bottom_dist) + { + center_pos.setY(screen_geom.top() + margin); + } + else + { + center_pos.setY(screen_geom.bottom() - margin); + } + } + else if (left_dist > right_dist) + { + if (right_dist < top_dist && right_dist < bottom_dist) + { + center_pos.setX(screen_geom.right() - margin); + } + else if (top_dist < bottom_dist) + { + center_pos.setY(screen_geom.top() + margin); + } + else + { + center_pos.setY(screen_geom.bottom() - margin); + } + } + } + + window_geom.moveCenter(center_pos); + + m_pixel_ratio = screen_pixel_ratio; + + SetWindowPos(m_hwnd, nullptr, window_geom.x(), window_geom.y(), + window_geom.width(), window_geom.height(), SWP_NOACTIVATE); + + moveShadow(); +} + +void QGoodWindow::updateScreen(QScreen *screen) +{ + if (!screen) + return; + + m_pixel_ratio = screen->devicePixelRatio(); + setCurrentScreen(screen); +} + +void QGoodWindow::setCurrentScreen(QScreen *screen) +{ + windowHandle()->setScreen(screen); + + m_main_window->windowHandle()->setScreen(screen); + + if (m_shadow) + m_shadow->windowHandle()->setScreen(screen); +} + +void QGoodWindow::windowScaleChanged() +{ +#ifdef QT_VERSION_QT6 + internalWidgetResize(); +#endif + + //Fix widget scale bug. + resize(width() + 1, height() + 1); + qApp->processEvents(); + resize(width() - 1, height() - 1); + qApp->processEvents(); + + sizeMoveWindow(); + moveShadow(); +} + +void QGoodWindow::internalWidgetResize() +{ + if (!windowState().testFlag(Qt::WindowNoState)) + return; + + if (!m_window_ready_for_resize) + return; + + const QSize current_size = size(); + QSize mw_size = m_main_window->size(); + +#ifdef QT_VERSION_QT5 + mw_size.setWidth(qFloor(mw_size.width() / m_pixel_ratio)); + mw_size.setHeight(qFloor(mw_size.height() / m_pixel_ratio)); +#endif + + if (current_size.width() < qFloor(mw_size.width() * m_pixel_ratio) || + current_size.width() > qCeil(mw_size.width() * m_pixel_ratio)) + return; + + if (current_size.height() < qFloor(mw_size.height() * m_pixel_ratio) || + current_size.height() > qCeil(mw_size.height() * m_pixel_ratio)) + return; + + resize(mw_size); +} + +QScreen *QGoodWindow::screenForWindow(HWND hwnd) +{ + HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); + + MONITORINFO monitor_info; + memset(&monitor_info, 0, sizeof(MONITORINFO)); + monitor_info.cbSize = sizeof(MONITORINFO); + + GetMonitorInfoW(monitor, &monitor_info); + + QPoint top_left; + top_left.setX(monitor_info.rcMonitor.left); + top_left.setY(monitor_info.rcMonitor.top); + + for (QScreen *screen : qApp->screens()) + { + if (screen->geometry().topLeft() == top_left) + { + return screen; + } + } + + return nullptr; +} + +QPoint QGoodWindow::screenAdjustedPos() +{ + const QRect screen_geom = windowHandle()->screen()->geometry(); + + int adjust_x = qFloor(screen_geom.x() - (screen_geom.x() / m_pixel_ratio)); + int adjust_y = qFloor(screen_geom.y() - (screen_geom.y() / m_pixel_ratio)); + + return QPoint(adjust_x, adjust_y); +} + +QScreen *QGoodWindow::screenForPoint(const QPoint &pos) +{ + POINT point; + point.x = pos.x(); + point.y = pos.y(); + + HMONITOR monitor = MonitorFromPoint(point, MONITOR_DEFAULTTONEAREST); + + MONITORINFO monitor_info; + memset(&monitor_info, 0, sizeof(MONITORINFO)); + monitor_info.cbSize = sizeof(MONITORINFO); + + GetMonitorInfoW(monitor, &monitor_info); + + QPoint top_left; + top_left.setX(monitor_info.rcMonitor.left); + top_left.setY(monitor_info.rcMonitor.top); + + for (QScreen *screen : qApp->screens()) + { + if (screen->geometry().topLeft() == top_left) + { + return screen; + } + } + + return nullptr; +} + +void QGoodWindow::showContextMenu(int x, int y) +{ + HMENU menu = GetSystemMenu(m_hwnd, FALSE); + + if (!menu) + return; + + MENUITEMINFOW mi; + memset(&mi, 0, sizeof(MENUITEMINFOW)); + mi.cbSize = sizeof(MENUITEMINFOW); + mi.fMask = MIIM_STATE; + + mi.fState = MF_ENABLED; + + SetMenuItemInfoW(menu, SC_RESTORE, FALSE, &mi); + SetMenuItemInfoW(menu, SC_SIZE, FALSE, &mi); + SetMenuItemInfoW(menu, SC_MOVE, FALSE, &mi); + SetMenuItemInfoW(menu, SC_MAXIMIZE, FALSE, &mi); + SetMenuItemInfoW(menu, SC_MINIMIZE, FALSE, &mi); + + mi.fState = MF_GRAYED; + + WINDOWPLACEMENT wp; + GetWindowPlacement(m_hwnd, &wp); + + switch (wp.showCmd) + { + case SW_SHOWMAXIMIZED: + { + SetMenuItemInfoW(menu, SC_SIZE, FALSE, &mi); + SetMenuItemInfoW(menu, SC_MOVE, FALSE, &mi); + SetMenuItemInfoW(menu, SC_MAXIMIZE, FALSE, &mi); + SetMenuDefaultItem(menu, SC_CLOSE, FALSE); + break; + } + case SW_SHOWMINIMIZED: + { + SetMenuItemInfoW(menu, SC_MINIMIZE, FALSE, &mi); + SetMenuDefaultItem(menu, SC_RESTORE, FALSE); + break; + } + case SW_SHOWNORMAL: + { + SetMenuItemInfoW(menu, SC_RESTORE, FALSE, &mi); + SetMenuDefaultItem(menu, SC_CLOSE, FALSE); + break; + } + default: + break; + } + + if (!(GetWindowLongW(m_hwnd, GWL_STYLE) & WS_MAXIMIZEBOX)) + { + SetMenuItemInfoW(menu, SC_MAXIMIZE, FALSE, &mi); + SetMenuItemInfoW(menu, SC_RESTORE, FALSE, &mi); + } + + if (!(GetWindowLongW(m_hwnd, GWL_STYLE) & WS_MINIMIZEBOX)) + { + SetMenuItemInfoW(menu, SC_MINIMIZE, FALSE, &mi); + } + + if (FIXED_SIZE(this)) + { + SetMenuItemInfoW(menu, SC_SIZE, FALSE, &mi); + } + + int cmd = int(TrackPopupMenu(menu, TPM_RIGHTBUTTON | TPM_NONOTIFY | TPM_RETURNCMD, + x, y, 0, m_hwnd, nullptr)); + + if (cmd) + { + SendMessageW(m_hwnd, WM_SYSCOMMAND, WPARAM(cmd), 0); + } +} + +void QGoodWindow::showContextMenu() +{ + int pos_x = x(); + int pos_y = y() + titleBarHeight(); + + if (m_is_using_system_borders && windowState().testFlag(Qt::WindowNoState)) + pos_x += BORDERWIDTH(); + + pos_x = qFloor(pos_x * m_pixel_ratio); + pos_y = qFloor(pos_y * m_pixel_ratio); + + showContextMenu(pos_x, pos_y); +} + +QWidget *QGoodWindow::bestParentForModalWindow() +{ + return new QGoodWindowUtils::ParentWindow(this); +} + +void QGoodWindow::moveCenterWindow(QWidget *widget) +{ + QGoodWindow *child_gw = qobject_cast(widget); + + const int title_bar_height = titleBarHeight(); + + int border_width = 0; + + if (m_is_using_system_borders && windowState().testFlag(Qt::WindowNoState)) + border_width = BORDERWIDTH(); + + QRect rect; + + if (!isMinimized() && isVisible()) + rect = frameGeometry(); + else + rect = m_window_handle->screen()->availableGeometry(); + + QRect screen_rect = m_window_handle->screen()->availableGeometry(); + + QRect window_rect; + + if (child_gw) + window_rect = child_gw->frameGeometry(); + else + window_rect = widget->frameGeometry(); + + window_rect.moveCenter(rect.center()); + + if (child_gw) + { + window_rect.moveLeft(qMax(window_rect.left(), screen_rect.left())); + window_rect.moveTop(qMax(window_rect.top(), screen_rect.top())); + window_rect.moveRight(qMin(window_rect.right(), screen_rect.right())); + window_rect.moveBottom(qMin(window_rect.bottom(), screen_rect.bottom())); + } + else + { + window_rect.moveLeft(qMax(window_rect.left(), screen_rect.left() + border_width)); + window_rect.moveTop(qMax(window_rect.top() + titleBarHeight(), screen_rect.top() + title_bar_height)); + window_rect.moveRight(qMin(window_rect.right(), screen_rect.right() - border_width)); + window_rect.moveBottom(qMin(window_rect.bottom(), screen_rect.bottom() - border_width)); + } + + if (child_gw) + child_gw->setGeometry(window_rect); + else + widget->setGeometry(window_rect); +} + +bool QGoodWindow::winButtonHover(qintptr button) +{ + if (button == -1) + return false; + + switch (button) + { + case HTMINBUTTON: + case HTMAXBUTTON: + case HTCLOSE: + { + if (!m_is_caption_button_pressed) + { + if (button != m_last_caption_button_hovered) + buttonLeave(m_last_caption_button_hovered); + + if (isEnabled()) + buttonEnter(button); + } + else + { + if (button != m_last_caption_button_hovered) + buttonLeave(m_last_caption_button_hovered); + + if (!m_is_caption_button_pressed || (button == m_caption_button_pressed)) + buttonEnter(button); + } + + return true; + } + default: + { + buttonLeave(m_last_caption_button_hovered); + return false; + } + } +} + +void QGoodWindow::iconClicked() +{ + if (!m_is_menu_visible) + { + m_is_menu_visible = true; + + m_timer_menu->start(); + + showContextMenu(); + + QTimer::singleShot(qApp->doubleClickInterval(), this, [=]{ + m_is_menu_visible = false; + }); + } + + if (m_timer_menu->isActive()) + QTimer::singleShot(0, this, &QGoodWindow::close); +} +#endif +#ifdef Q_OS_LINUX +void QGoodWindow::setCursorForCurrentPos() +{ + const QPoint cursor_pos = QCursor::pos(); + const int margin = int(ncHitTest(qFloor(cursor_pos.x() * m_pixel_ratio), qFloor(cursor_pos.y() * m_pixel_ratio))); + + m_cursor_pos = cursor_pos; + m_margin = margin; + + QWidget *widget = QApplication::widgetAt(cursor_pos); + + if (!widget) + return; + + Display *dpy = QX11Info::display(); + + switch (margin) + { + case HTTOPLEFT: + { + Cursor cursor; + + if (!FIXED_SIZE(this)) + cursor = XCreateFontCursor(dpy, XC_top_left_corner); + else + cursor = XCreateFontCursor(dpy, XC_arrow); + + XDefineCursor(dpy, Window(widget->winId()), cursor); + + XFlush(dpy); + + XFreeCursor(dpy, cursor); + + break; + } + case HTTOP: + { + Cursor cursor; + + if (!FIXED_HEIGHT(this)) + cursor = XCreateFontCursor(dpy, XC_top_side); + else + cursor = XCreateFontCursor(dpy, XC_arrow); + + XDefineCursor(dpy, Window(widget->winId()), cursor); + + XFlush(dpy); + + XFreeCursor(dpy, cursor); + + break; + } + case HTTOPRIGHT: + { + Cursor cursor; + + if (!FIXED_SIZE(this)) + cursor = XCreateFontCursor(dpy, XC_top_right_corner); + else + cursor = XCreateFontCursor(dpy, XC_arrow); + + XDefineCursor(dpy, Window(widget->winId()), cursor); + + XFlush(dpy); + + XFreeCursor(dpy, cursor); + + break; + } + case HTLEFT: + { + Cursor cursor; + + if (!FIXED_WIDTH(this)) + cursor = XCreateFontCursor(dpy, XC_left_side); + else + cursor = XCreateFontCursor(dpy, XC_arrow); + + XDefineCursor(dpy, Window(widget->winId()), cursor); + + XFlush(dpy); + + XFreeCursor(dpy, cursor); + + break; + } + case HTRIGHT: + { + Cursor cursor; + + if (!FIXED_WIDTH(this)) + cursor = XCreateFontCursor(dpy, XC_right_side); + else + cursor = XCreateFontCursor(dpy, XC_arrow); + + XDefineCursor(dpy, Window(widget->winId()), cursor); + + XFlush(dpy); + + XFreeCursor(dpy, cursor); + + break; + } + case HTBOTTOMLEFT: + { + Cursor cursor; + + if (!FIXED_SIZE(this)) + cursor = XCreateFontCursor(dpy, XC_bottom_left_corner); + else + cursor = XCreateFontCursor(dpy, XC_arrow); + + XDefineCursor(dpy, Window(widget->winId()), cursor); + + XFlush(dpy); + + XFreeCursor(dpy, cursor); + + break; + } + case HTBOTTOM: + { + Cursor cursor; + + if (!FIXED_HEIGHT(this)) + cursor = XCreateFontCursor(dpy, XC_bottom_side); + else + cursor = XCreateFontCursor(dpy, XC_arrow); + + XDefineCursor(dpy, Window(widget->winId()), cursor); + + XFlush(dpy); + + XFreeCursor(dpy, cursor); + + break; + } + case HTBOTTOMRIGHT: + { + Cursor cursor; + + if (!FIXED_SIZE(this)) + cursor = XCreateFontCursor(dpy, XC_bottom_right_corner); + else + cursor = XCreateFontCursor(dpy, XC_arrow); + + XDefineCursor(dpy, Window(widget->winId()), cursor); + + XFlush(dpy); + + XFreeCursor(dpy, cursor); + + break; + } + case HTCAPTION: + case HTMINBUTTON: + case HTMAXBUTTON: + case HTCLOSE: + { + if (QApplication::overrideCursor() && + QApplication::overrideCursor()->shape() == Qt::ArrowCursor) + break; + + QApplication::setOverrideCursor(Qt::ArrowCursor); + + break; + } + case HTNOWHERE: + { + if (!QApplication::overrideCursor()) + break; + + if (QApplication::overrideCursor()->shape() != Qt::ArrowCursor) + break; + + QApplication::restoreOverrideCursor(); + + break; + } + default: + break; + } +} + +void QGoodWindow::startSystemMoveResize() +{ + const int margin = m_margin; + + if (margin == HTNOWHERE) + return; + + if (FIXED_SIZE(this) && margin != HTCAPTION) + return; + + QPoint cursor_pos = QPoint(qFloor(m_cursor_pos.x() * m_pixel_ratio), qFloor(m_cursor_pos.y() * m_pixel_ratio)); + + XClientMessageEvent xmsg; + memset(&xmsg, 0, sizeof(XClientMessageEvent)); + + xmsg.type = ClientMessage; + xmsg.window = Window(winId()); + xmsg.message_type = XInternAtom(QX11Info::display(), "_NET_WM_MOVERESIZE", False); + xmsg.format = 32; + xmsg.data.l[0] = long(cursor_pos.x()); + xmsg.data.l[1] = long(cursor_pos.y()); + + if (margin == HTCAPTION) + xmsg.data.l[2] = MOVERESIZE_MOVE; + else + xmsg.data.l[2] = long(margin); + + xmsg.data.l[3] = 0; + xmsg.data.l[4] = 0; + + XSendEvent(QX11Info::display(), QX11Info::appRootWindow(), False, + SubstructureRedirectMask | SubstructureNotifyMask, + reinterpret_cast(&xmsg)); + + XUngrabPointer(QX11Info::display(), QX11Info::appTime()); + XFlush(QX11Info::display()); + + QTimer::singleShot(qApp->doubleClickInterval(), this, [=]{ + m_resize_move_started = true; + }); +} + +void QGoodWindow::sizeMove() +{ + if (!m_resize_move) + return; + + m_resize_move = false; + + const int margin = m_margin; + + if (margin == HTCAPTION) + { + QTimer::singleShot(0, this, [=]{ + startSystemMoveResize(); + }); + } + else + { + startSystemMoveResize(); + } +} + +void QGoodWindow::sizeMoveBorders() +{ + if (!windowState().testFlag(Qt::WindowNoState)) + return; + + const int border_width = BORDERWIDTHDPI; + + QRegion visible_rgn; + + for (const QScreen *screen : qApp->screens()) + { + visible_rgn += screen->availableGeometry(); + } + + QRect frame_geom = frameGeometry(); + frame_geom.adjust(-border_width, -border_width, border_width, border_width); + + QRegion rgn = m_shadow->rect(); + rgn = rgn.subtracted(mask().translated(border_width, border_width)); + + rgn.translate(frame_geom.topLeft()); + + rgn = rgn.intersected(visible_rgn); + + rgn.translate(-rgn.boundingRect().topLeft()); + + m_shadow->setMask(rgn); + + m_shadow->setGeometry(frame_geom); +} +#endif +#ifdef Q_OS_MAC +void QGoodWindow::setMacOSStyle(int style_type) +{ + macOSNative::Style *style = static_cast(style_ptr); + style->m_style = macOSNative::StyleType(style_type); + macOSNative::setStyle(long(winId()), style); +} + +void QGoodWindow::notificationReceiver(const QByteArray ¬ification) +{ + if (notification == "NSWindowWillEnterFullScreenNotification") + { + m_on_animate_event = true; + + setMacOSStyle(int(macOSNative::StyleType::Fullscreen)); + } + else if (notification == "NSWindowWillExitFullScreenNotification") + { + setMacOSStyle(int(macOSNative::StyleType::NoState)); + } + else if (notification == "NSWindowDidExitFullScreenNotification") + { + setMacOSStyle(int(macOSNative::StyleType::NoState)); + + m_on_animate_event = false; + } + else if (notification == "AppleInterfaceThemeChangedNotification") + { + QTimer::singleShot(0, this, &QGoodWindow::themeChanged); + } +} +#endif + +#ifdef QGOODWINDOW +qintptr QGoodWindow::ncHitTest(int pos_x, int pos_y) +{ + if (isFullScreen()) + { + //If on full screen, the whole window can be clicked. + return HTNOWHERE; + } + + int border_width = 0; + +#ifdef Q_OS_WIN + if (windowState().testFlag(Qt::WindowNoState)) + { + if (m_is_using_system_borders) + border_width = qFloor(BORDERWIDTH() / m_pixel_ratio); //in pixels. + else + border_width = qFloor(1 * m_pixel_ratio); //in pixels. + } +#endif + +//Get the window rectangle. +#ifndef Q_OS_MAC + QRect window_rect = frameGeometry(); +#else + //Get the correct window rectangle for macOS. + QRect window_rect; + + if (isVisible()) + { + int x, y, w, h; + macOSNative::frameGeometry(long(winId()), &x, &y, &w, &h); + window_rect = QRect(x, y, w, h); + } +#endif + + //Get the point coordinates for the hit test. + const QPoint cursor_pos = QPoint(qFloor(pos_x / m_pixel_ratio), qFloor(pos_y / m_pixel_ratio)); + + //Get the mapped point coordinates for the hit test without border width. + const QPoint cursor_pos_map = QPoint(cursor_pos.x() - window_rect.x() - border_width, cursor_pos.y() - window_rect.y()); + +#ifdef Q_OS_WIN + for (QSizeGrip *size_grip : findChildren()) + { + if (size_grip->isEnabled() && + !size_grip->window()->windowFlags().testFlag(Qt::SubWindow)) + { + QPoint cursor_pos_map_widget = size_grip->parentWidget()->mapFromGlobal(cursor_pos); + + QPoint adjusted_pos = screenAdjustedPos(); + + cursor_pos_map_widget.setX(cursor_pos_map_widget.x() + adjusted_pos.x()); + cursor_pos_map_widget.setY(cursor_pos_map_widget.y() + adjusted_pos.y()); + + if (size_grip->geometry().contains(cursor_pos_map_widget)) + return HTBOTTOMRIGHT; + } + } +#endif + + const int title_bar_height = titleBarHeight(); + +#ifdef Q_OS_WIN + const int icon_width = iconWidth(); +#endif + + //Determine if the hit test is for resizing. Default middle (1,1). + int row = 1; + int col = 1; + bool on_resize_border = false; + + //Determine if the point is at the top or bottom of the window. + if (cursor_pos.y() < window_rect.top() + title_bar_height) + { + on_resize_border = (cursor_pos.y() < (window_rect.top() + border_width)); + row = 0; //top border. + } + else if (cursor_pos.y() > window_rect.bottom() - border_width) + { + row = 2; //bottom border. + } + + //Determine if the point is at the left or right of the window. + if (cursor_pos.x() < window_rect.left() + border_width) + { + col = 0; //left border. + } + else if (cursor_pos.x() > window_rect.right() - border_width) + { + col = 2; //right border. + } + else if (row == 0 && !on_resize_border) + { + if (m_cls_mask.contains(cursor_pos_map)) + return HTCLOSE; //title bar close button. + else if (m_max_mask.contains(cursor_pos_map)) + return HTMAXBUTTON; //title bar maximize button. + else if (m_min_mask.contains(cursor_pos_map)) + return HTMINBUTTON; //title bar minimize button. +#ifdef Q_OS_WIN + else if (cursor_pos_map.x() <= icon_width) + return HTSYSMENU; //title bar icon. +#endif + else if (m_title_bar_mask.contains(cursor_pos_map)) + return HTNOWHERE; //user title bar mask. + } + + //Hit test (HTTOPLEFT, ... HTBOTTOMRIGHT) + qintptr hitTests[3][3] = + { + {HTTOPLEFT, on_resize_border ? HTTOP : HTCAPTION, HTTOPRIGHT}, + {HTLEFT, HTNOWHERE, HTRIGHT}, + {HTBOTTOMLEFT, HTBOTTOM, HTBOTTOMRIGHT}, + }; + + return hitTests[row][col]; +} + +void QGoodWindow::buttonEnter(qintptr button) +{ + if (button == -1) + return; + + if (button == m_last_caption_button_hovered) + return; + + m_last_caption_button_hovered = button; + + switch (button) + { + case HTMINBUTTON: + { + Q_EMIT captionButtonStateChanged(CaptionButtonState::MinimizeHoverEnter); + + break; + } + case HTMAXBUTTON: + { + Q_EMIT captionButtonStateChanged(CaptionButtonState::MaximizeHoverEnter); + + break; + } + case HTCLOSE: + { + Q_EMIT captionButtonStateChanged(CaptionButtonState::CloseHoverEnter); + + break; + } + default: + break; + } +} + +void QGoodWindow::buttonLeave(qintptr button) +{ + if (button == -1) + return; + + if (button != m_last_caption_button_hovered) + return; + + m_last_caption_button_hovered = -1; + + switch (button) + { + case HTMINBUTTON: + { + Q_EMIT captionButtonStateChanged(CaptionButtonState::MinimizeHoverLeave); + + break; + } + case HTMAXBUTTON: + { + Q_EMIT captionButtonStateChanged(CaptionButtonState::MaximizeHoverLeave); + + break; + } + case HTCLOSE: + { + Q_EMIT captionButtonStateChanged(CaptionButtonState::CloseHoverLeave); + + break; + } + default: + break; + } +} + +bool QGoodWindow::buttonPress(qintptr button) +{ + if (button == -1) + return false; + + switch (button) + { + case HTMINBUTTON: + { + m_is_caption_button_pressed = true; + m_caption_button_pressed = HTMINBUTTON; + + Q_EMIT captionButtonStateChanged(CaptionButtonState::MinimizePress); + + activateWindow(); + + return true; + } + case HTMAXBUTTON: + { + m_is_caption_button_pressed = true; + m_caption_button_pressed = HTMAXBUTTON; + + Q_EMIT captionButtonStateChanged(CaptionButtonState::MaximizePress); + + activateWindow(); + + return true; + } + case HTCLOSE: + { + m_is_caption_button_pressed = true; + m_caption_button_pressed = HTCLOSE; + + Q_EMIT captionButtonStateChanged(CaptionButtonState::ClosePress); + + activateWindow(); + + return true; + } + default: + break; + } + + return false; +} + +bool QGoodWindow::buttonRelease(qintptr button, bool valid_click) +{ + if (button == -1) + return false; + + if (!m_is_caption_button_pressed) + return false; + + if (m_hover_timer->isActive()) + return false; + + m_is_caption_button_pressed = false; + m_caption_button_pressed = -1; + + switch (button) + { + case HTMINBUTTON: + { + Q_EMIT captionButtonStateChanged(CaptionButtonState::MinimizeRelease); + + if (valid_click) + { + buttonLeave(button); + Q_EMIT captionButtonStateChanged(CaptionButtonState::MinimizeClicked); + m_hover_timer->start(); + } + + return true; + } + case HTMAXBUTTON: + { + Q_EMIT captionButtonStateChanged(CaptionButtonState::MaximizeRelease); + + if (valid_click) + { + buttonLeave(button); + Q_EMIT captionButtonStateChanged(CaptionButtonState::MaximizeClicked); + m_hover_timer->start(); + } + + return true; + } + case HTCLOSE: + { + Q_EMIT captionButtonStateChanged(CaptionButtonState::CloseRelease); + + if (valid_click) + { + buttonLeave(button); + Q_EMIT captionButtonStateChanged(CaptionButtonState::CloseClicked); + m_hover_timer->start(); + } + + return true; + } + default: + break; + } + + return false; +} +#endif diff --git a/lib/QGoodWindow/QGoodWindow/src/qgoodwindow.h b/lib/QGoodWindow/QGoodWindow/src/qgoodwindow.h new file mode 100644 index 00000000..119242ab --- /dev/null +++ b/lib/QGoodWindow/QGoodWindow/src/qgoodwindow.h @@ -0,0 +1,630 @@ +/* +The MIT License (MIT) + +Copyright © 2018-2023 Antonio Dias (https://github.com/antonypro) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef QGOODWINDOW_H +#define QGOODWINDOW_H + +#include +#include +#include +#include "intcommon.h" +#include "qgoodstateholder.h" +#include "lightstyle.h" +#include "darkstyle.h" + +#include "qgoodwindow_global.h" + +#ifdef QGOODWINDOW + +#ifdef Q_OS_WIN +namespace QGoodWindowUtils +{ +class NativeEventFilter; +} +#endif + +#if defined Q_OS_WIN || defined Q_OS_LINUX +class Shadow; +#endif + +#endif + +/** **QGoodWindow** class contains the public API's to control the behavior of the customized window. */ +class QGOODWINDOW_SHARED_EXPORT QGoodWindow : public QMainWindow +{ + Q_OBJECT +public: + /** Constructor of *QGoodWindow*. + * + * On Windows creates the native window, turns the `QMainWindow` as a native widget, + * creates the shadow, initialize default values and calls the `QMainWindow` + * parent constructor. + * + * On Linux creates the frame less `QMainWindow`, use the shadow only to create resize borders, + * and the real shadow is draw by current Linux window manager. + * + * On macOS creates a `QMainWindow` with full access to the title bar, + * and hide native minimize, zoom and close buttons. + */ + explicit QGoodWindow(QWidget *parent = nullptr, + const QColor &clear_color = + QColor(!isSystemThemeDark() ? Qt::white : Qt::black)); + + /** Destructor of *QGoodWindow*. */ + ~QGoodWindow(); + + /** Enum that contains caption buttons states when it's states are handled by *QGoodWindow*. */ + enum class CaptionButtonState + { + /** Minimize button hover enter. */ + MinimizeHoverEnter, + + /** Minimize button hover leave. */ + MinimizeHoverLeave, + + /** Minimize button press. */ + MinimizePress, + + /** Minimize button release. */ + MinimizeRelease, + + /** Minimize button clicked. */ + MinimizeClicked, + + /** Maximize or restore button hover enter. */ + MaximizeHoverEnter, + + /** Maximize or restore button hover leave. */ + MaximizeHoverLeave, + + /** Maximize or restore button press. */ + MaximizePress, + + /** Maximize or restore button release. */ + MaximizeRelease, + + /** Maximize or restore button clicked. */ + MaximizeClicked, + + /** Close button hover enter. */ + CloseHoverEnter, + + /** Close button hover leave. */ + CloseHoverLeave, + + /** Close button press. */ + ClosePress, + + /** Close button release. */ + CloseRelease, + + /** Close button clicked. */ + CloseClicked + }; + + //Functions + //\cond HIDDEN_SYMBOLS + void themeChanged(); + //\endcond + + /** Returns the window id of the *QGoodWindow*. */ + WId winId() const; + + /** *QGoodWindow* handles flags internally, use this function only when *QGoodWindow* is not enabled. */ + void setWindowFlags(Qt::WindowFlags type); + + /** Returns the window flags of the *QGoodWindow*. */ + Qt::WindowFlags windowFlags() const; + + /*** QGOODWINDOW FUNCTIONS BEGIN ***/ + + /** Returns the *QGoodWindow* version. */ + static QString version(); + + /** Call this function to setup *QApplication* for *QGoodWindow* usage. */ + static void setup(); + + /** Returns if the current system theme is dark or not. */ + static bool isSystemThemeDark(); + + /** Returns true if system draw borders and false if your app should do it. */ + static bool shouldBordersBeDrawnBySystem(); + + /** Show modal frame less *QDialog*, inside window \e child_gw, with parent \e parent_gw. */ + static int execDialog(QDialog *dialog, QGoodWindow *child_gw, QGoodWindow *parent_gw); + + /** Set the app theme to the dark theme. */ + static void setAppDarkTheme(); + + /** Set the app theme to the light theme. */ + static void setAppLightTheme(); + + /** Get the global state holder. */ + static QGoodStateHolder *qGoodStateHolderInstance(); + + /*** QGOODWINDOW FUNCTIONS END ***/ +Q_SIGNALS: + /** On handled caption buttons, this SIGNAL report the state of these buttons. */ + void captionButtonStateChanged(const QGoodWindow::CaptionButtonState &state); + + /** Notify that the system has changed between light and dark mode. */ + void systemThemeChanged(); + + /** Notify that the visibility of caption buttons have changed on macOS. */ + void captionButtonsVisibilityChangedOnMacOS(); + + /*** QGOODWINDOW FUNCTIONS BEGIN ***/ + +public: + /** Deprecated function scheduled for removal in some future version, don’t use it. */ + Q_DECL_DEPRECATED void setLeftMargin(int width) + {Q_UNUSED(width); Q_ASSERT(false);} + + /** Deprecated function scheduled for removal in some future version, don’t use it. */ + Q_DECL_DEPRECATED void setRightMargin(int width) + {Q_UNUSED(width); Q_ASSERT(false);} + + /** Deprecated function scheduled for removal in some future version, don’t use it. */ + Q_DECL_DEPRECATED int leftMargin() const + {Q_ASSERT(false); return 0;} + + /** Deprecated function scheduled for removal in some future version, don’t use it. */ + Q_DECL_DEPRECATED int rightMargin() const + {Q_ASSERT(false); return 0;} + + /** Deprecated function scheduled for removal in some future version, don’t use it. */ + Q_DECL_DEPRECATED void setMargins(int title_bar_height, int icon_width, int left, int right) + {Q_UNUSED(title_bar_height); Q_UNUSED(icon_width); Q_UNUSED(left); Q_UNUSED(right); Q_ASSERT(false);} + + /** Deprecated function scheduled for removal in some future version, don’t use it. */ + Q_DECL_DEPRECATED void setLeftMask(const QRegion &mask) + {Q_UNUSED(mask); Q_ASSERT(false);} + + /** Deprecated function scheduled for removal in some future version, don’t use it. */ + Q_DECL_DEPRECATED void setRightMask(const QRegion &mask) + {Q_UNUSED(mask); Q_ASSERT(false);} + + /** Deprecated function scheduled for removal in some future version, don’t use it. */ + Q_DECL_DEPRECATED void setCenterMask(const QRegion &mask) + {Q_UNUSED(mask); Q_ASSERT(false);} + + /** Deprecated function scheduled for removal in some future version, don’t use it. */ + Q_DECL_DEPRECATED void setCaptionButtonsHandled(bool handled, const Qt::Corner &corner = Qt::TopRightCorner) + {Q_UNUSED(handled); Q_UNUSED(corner); Q_ASSERT(false);} + + /** Deprecated function scheduled for removal in some future version, don’t use it. */ + Q_DECL_DEPRECATED QRegion leftMask() const + {Q_ASSERT(false); return QRegion();} + + /** Deprecated function scheduled for removal in some future version, don’t use it. */ + Q_DECL_DEPRECATED QRegion rightMask() const + {Q_ASSERT(false); return QRegion();} + + /** Deprecated function scheduled for removal in some future version, don’t use it. */ + Q_DECL_DEPRECATED QRegion centerMask() const + {Q_ASSERT(false); return QRegion();} + + /** Deprecated function scheduled for removal in some future version, don’t use it. */ + Q_DECL_DEPRECATED QSize leftMaskSize() const + {Q_ASSERT(false); return QSize();} + + /** Deprecated function scheduled for removal in some future version, don’t use it. */ + Q_DECL_DEPRECATED QSize rightMaskSize() const + {Q_ASSERT(false); return QSize();} + + /** Deprecated function scheduled for removal in some future version, don’t use it. */ + Q_DECL_DEPRECATED QRect leftCaptionButtonsRect() const + {Q_ASSERT(false); return QRect();} + + /** Deprecated function scheduled for removal in some future version, don’t use it. */ + Q_DECL_DEPRECATED QRect rightCaptionButtonsRect() const + {Q_ASSERT(false); return QRect();} + +public Q_SLOTS: + /** Set native caption buttons on macOS visibility to \e visible. **/ + void setNativeCaptionButtonsVisibleOnMac(bool visible); + + /** Returns if native caption buttons are visible on macOS. **/ + bool isNativeCaptionButtonsVisibleOnMac() const; + + /** Returns native caption buttons rect on macOS. **/ + QRect titleBarButtonsRectOnMacOS() const; + + /** Set title bar height for *QGoodWindow*. */ + void setTitleBarHeight(int height); + + /** Set current icon width on the left side of the title bar of *QGoodWindow*. */ + void setIconWidth(int width); + + /** On Windows, Linux and macOS, returns the actual title bar height, on other OSes returns 0. */ + int titleBarHeight() const; + + /** On Windows, Linux and macOS, return the actual icon width, on other OSes returns 0. */ + int iconWidth() const; + + /** Rect that contains the whole title bar. */ + QRect titleBarRect() const; + + /** Set the mask for the customized title bar. */ + void setTitleBarMask(const QRegion &mask); + + /** Set the location and shape of handled minimize button, relative to title bar rect. */ + void setMinimizeMask(const QRegion &mask); + + /** Set the location and shape of handled maximize button, relative to title bar rect. */ + void setMaximizeMask(const QRegion &mask); + + /** Set the location and shape of handled close button, relative to title bar rect. */ + void setCloseMask(const QRegion &mask); + + /** Get the mask for the customized title bar. */ + QRegion titleBarMask() const; + + /** Get the location and shape of handled minimize button, relative to title bar rect. */ + QRegion minimizeMask() const; + + /** Get the location and shape of handled maximize button, relative to title bar rect. */ + QRegion maximizeMask() const; + + /** Get the location and shape of handled close button, relative to title bar rect. */ + QRegion closeMask() const; + + /*** QGOODWINDOW FUNCTIONS END ***/ + + /** Set central widget of *QGoodWindow* to \e widget. */ + void setCentralWidget(QWidget *widget); + + /** Returns the central widget of *QGoodWindow*. */ + QWidget *centralWidget() const; + + /** Get the size hint of *QGoodWindow*. */ + QSize sizeHint() const override; + + /** Get the minimum size hint of *QGoodWindow*. */ + QSize minimumSizeHint() const override; + + /** Set fixed size for *QGoodWindow* to width \e w and height \e h. */ + void setFixedSize(int w, int h); + + /** Set fixed size for *QGoodWindow* to \e size. */ + void setFixedSize(const QSize &size); + + /** Set minimum size for *QGoodWindow* to \e size. */ + void setMinimumSize(const QSize &size); + + /** Set maximum size for *QGoodWindow* to \e size. */ + void setMaximumSize(const QSize &size); + + /** Set minimum width for *QGoodWindow* to \e w. */ + void setMinimumWidth(int w); + + /** Set minimum height for *QGoodWindow* to \e h. */ + void setMinimumHeight(int h); + + /** Set maximum width for *QGoodWindow* to \e w. */ + void setMaximumWidth(int w); + + /** Set maximum height for *QGoodWindow* to \e h. */ + void setMaximumHeight(int h); + + /** Returns the *QGoodWindow* minimum size. */ + QSize minimumSize() const; + + /** Returns the *QGoodWindow* maximum size. */ + QSize maximumSize() const; + + /** Returns minimum width for *QGoodWindow* */ + int minimumWidth() const; + + /** Returns minimum height for *QGoodWindow* */ + int minimumHeight() const; + + /** Returns maximum width for *QGoodWindow* */ + int maximumWidth() const; + + /** Returns maximum height for *QGoodWindow* */ + int maximumHeight() const; + + /** Returns the geometry for *QGoodWindow* when it's state is no state. */ + QRect normalGeometry() const; + + /** Returns the geometry for *QGoodWindow* including extended frame and excluding shadow. */ + QRect frameGeometry() const; + + /** Returns the client area geometry. */ + QRect geometry() const; + + /** Returns the client area size, position is always `QPoint(0, 0)`. */ + QRect rect() const; + + /** Position of the window on screen. */ + QPoint pos() const; + + /** Size of the window on screen. */ + QSize size() const; + + /** X position of the window on screen. */ + int x() const; + + /** Y position of the window on screen. */ + int y() const; + + /** Width of the window. */ + int width() const; + + /** Height of the window. */ + int height() const; + + /** Move the window to \e x - \e y coordinates. */ + void move(int x, int y); + + /** Move the window to \e pos. */ + void move(const QPoint &pos); + + /** Resize the window to \e width - \e height size. */ + void resize(int width, int height); + + /** Resize the window to \e size. */ + void resize(const QSize &size); + + /** Set geometry to pos \e x - \e y, width \e w and height \e h. */ + void setGeometry(int x, int y, int w, int h); + + /** Set geometry to \e rect. */ + void setGeometry(const QRect &rect); + + /** Activates the *QGoodWindow*. */ + void activateWindow(); + + /** Shows the *QGoodWindow*. */ + void show(); + + /** Shows or restore the *QGoodWindow*. */ + void showNormal(); + + /** Shows or maximize the *QGoodWindow*. */ + void showMaximized(); + + /** Minimize the *QGoodWindow*. */ + void showMinimized(); + + /** Turns the *QGoodWindow* into full screen mode. Including the title bar. */ + void showFullScreen(); + + /** Hide the *QGoodWindow*. */ + void hide(); + + /** Try to close the *QGoodWindow*, returns true if event is accepted or false otherwise. */ + bool close(); + + /** Returns if the *QGoodWindow* is visible or not. */ + bool isVisible() const; + + /** Returns if the *QGoodWindow* is enabled or not. */ + bool isEnabled() const; + + /** Returns if the *QGoodWindow* is the foreground window or not. */ + bool isActiveWindow() const; + + /** Returns if the *QGoodWindow* is maximized or not. */ + bool isMaximized() const; + + /** Returns if the *QGoodWindow* is minimized or not. */ + bool isMinimized() const; + + /** Returns if the *QGoodWindow* is in full screen mode or not. */ + bool isFullScreen() const; + + /** Returns the *QGoodWindow* state. */ + Qt::WindowStates windowState() const; + + /** Sets the state of the *QGoodWindow* to \e state. */ + void setWindowState(Qt::WindowStates state); + + /** Returns the window handle of the *QGoodWindow*. */ + QWindow *windowHandle() const; + + /** Returns the opacity of the *QGoodWindow*. */ + qreal windowOpacity() const; + + /** Sets the opacity of the *QGoodWindow* to \e level. Where 0.0 is fully transparent and 1.0 fully opaque. */ + void setWindowOpacity(qreal level); + + /** Returns the title of the *QGoodWindow*. */ + QString windowTitle() const; + + /** Sets the title of the *QGoodWindow* to \e title. */ + void setWindowTitle(const QString &title); + + /** Returns the icon of the *QGoodWindow*. */ + QIcon windowIcon() const; + + /** Sets the icon of the *QGoodWindow* to \e icon. */ + void setWindowIcon(const QIcon &icon); + + /** Returns a copy of the *QGoodWindow* geometry to restore it later. */ + QByteArray saveGeometry() const; + + /** Restore *QGoodWindow* to previous geometry \e geometry. */ + bool restoreGeometry(const QByteArray &geometry); + +protected: + //\cond HIDDEN_SYMBOLS + //Functions + bool event(QEvent *event) override; + + bool eventFilter(QObject *watched, QEvent *event) override; + + bool nativeEvent(const QByteArray &eventType, void *message, qgoodintptr *result) override; + +private: +#ifdef QGOODWINDOW +#ifdef Q_OS_WIN + //Functions + void initGW(); + void destroyGW(); + void closeGW(); + void setWindowStateWin(); + static LRESULT WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); + void handleActivation(); + void handleDeactivation(); + void setWidgetFocus(); + void enableCaption(); + void disableCaption(); + void frameChanged(); + void sizeMoveWindow(); + void sizeMoveMainWindow(); + void setWindowMask(); + void moveShadow(); + void screenChangeMoveWindow(QScreen *screen); + void updateScreen(QScreen *screen); + void setCurrentScreen(QScreen *screen); + void windowScaleChanged(); + void internalWidgetResize(); + QPoint screenAdjustedPos(); + QScreen *screenForWindow(HWND hwnd); + QScreen *screenForPoint(const QPoint &pos); + void showContextMenu(int x, int y); + void showContextMenu(); + QWidget *bestParentForModalWindow(); + void moveCenterWindow(QWidget *widget); + bool winButtonHover(qintptr button); + void iconClicked(); + + //Variables + HWND m_hwnd; + bool m_is_win_11_or_greater; + QPointer m_main_window; + QPointer m_proxy_widget; + QPointer m_central_widget; + QPointer m_shadow; + QPointer m_helper_widget; +#ifdef QT_VERSION_QT6 + QPointer m_helper_window; +#endif + QGoodWindowUtils::NativeEventFilter *m_native_event; + QWindow *m_window_handle; + + QPointer m_focus_widget; + + bool m_window_ready_for_resize; + bool m_closed; + bool m_visible; + bool m_self_generated_close_event; + QPointer m_timer_move; + + Qt::WindowStates m_window_state; + + QPointer m_timer_menu; + bool m_is_menu_visible; + + QRect m_rect_normal; + + bool m_active_state; + + Qt::WindowState m_last_state; + bool m_self_generated_show_event; + + QColor m_clear_color; + + friend class QGoodWindowUtils::NativeEventFilter; + friend class QGoodDialog; +#endif +#ifdef Q_OS_LINUX + //Functions + void setCursorForCurrentPos(); + void startSystemMoveResize(); + void sizeMove(); + void sizeMoveBorders(); + + //Variables + QPointer m_shadow; + + int m_margin; + QPoint m_cursor_pos; + bool m_resize_move; + bool m_resize_move_started; + Qt::WindowFlags m_window_flags; +#endif +#ifdef Q_OS_MAC + //Functions + void notificationReceiver(const QByteArray ¬ification); + void setMacOSStyle(int style_type); + + //Variables + bool m_is_native_caption_buttons_visible_on_mac; + QPoint m_cursor_move_pos; + bool m_mouse_button_pressed; + bool m_on_animate_event; + void *style_ptr; + + friend class QGoodCentralWidget; + friend class QGoodDialog; + friend class Notification; +#endif +#if defined Q_OS_LINUX || defined Q_OS_MAC + int m_last_move_button; +#endif +#ifdef Q_OS_WIN + int m_minimum_width; + int m_minimum_height; + int m_maximum_width; + int m_maximum_height; +#endif + //Functions + qintptr ncHitTest(int pos_x, int pos_y); + + void buttonEnter(qintptr button); + void buttonLeave(qintptr button); + bool buttonPress(qintptr button); + bool buttonRelease(qintptr button, bool valid_click); + + //Variables + QPointer m_parent; + + QPointer m_hover_timer; + + QRegion m_title_bar_mask; + + QRegion m_min_mask; + QRegion m_max_mask; + QRegion m_cls_mask; + + qreal m_pixel_ratio; + + bool m_is_using_system_borders; + + bool m_dark; + + int m_title_bar_height; + int m_icon_width; + + bool m_is_caption_button_pressed; + qintptr m_last_caption_button_hovered; + qintptr m_caption_button_pressed; +#endif + //\endcond +}; + +#endif // QGOODWINDOW_H diff --git a/lib/QGoodWindow/QGoodWindow/src/qgoodwindow_global.h b/lib/QGoodWindow/QGoodWindow/src/qgoodwindow_global.h new file mode 100644 index 00000000..877e95db --- /dev/null +++ b/lib/QGoodWindow/QGoodWindow/src/qgoodwindow_global.h @@ -0,0 +1,44 @@ +/* +The MIT License (MIT) + +Copyright © 2023 Antonio Dias (https://github.com/antonypro) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef QGOODWINDOW_GLOBAL_H +#define QGOODWINDOW_GLOBAL_H + +#include + +#include "qgoodwindow_helper.h" + +#ifndef QGOODWINDOW_SHARED_EXPORT +#ifdef QGOODWINDOW_LIBRARY +#ifdef QGOODWINDOW_SHARED_LIBRARY +#define QGOODWINDOW_SHARED_EXPORT Q_DECL_EXPORT +#else +#define QGOODWINDOW_SHARED_EXPORT Q_DECL_IMPORT +#endif +#else +#define QGOODWINDOW_SHARED_EXPORT +#endif +#endif + +#endif // QGOODWINDOW_GLOBAL_H diff --git a/lib/QGoodWindow/QGoodWindow/src/qgoodwindow_helper.h b/lib/QGoodWindow/QGoodWindow/src/qgoodwindow_helper.h new file mode 100644 index 00000000..1687aeb9 --- /dev/null +++ b/lib/QGoodWindow/QGoodWindow/src/qgoodwindow_helper.h @@ -0,0 +1,28 @@ +/* +The MIT License (MIT) + +Copyright © 2023 Antonio Dias (https://github.com/antonypro) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef QGOODWINDOW_HELPER_H +#define QGOODWINDOW_HELPER_H + +#endif // QGOODWINDOW_HELPER_H diff --git a/lib/QGoodWindow/QGoodWindow/src/qgoodwindow_style.qrc b/lib/QGoodWindow/QGoodWindow/src/qgoodwindow_style.qrc new file mode 100644 index 00000000..9c3e8c2d --- /dev/null +++ b/lib/QGoodWindow/QGoodWindow/src/qgoodwindow_style.qrc @@ -0,0 +1,6 @@ + + + darkstyle.qss + lightstyle.qss + + diff --git a/lib/QGoodWindow/QGoodWindow/src/shadow.cpp b/lib/QGoodWindow/QGoodWindow/src/shadow.cpp new file mode 100644 index 00000000..ca432517 --- /dev/null +++ b/lib/QGoodWindow/QGoodWindow/src/shadow.cpp @@ -0,0 +1,300 @@ +/* +The MIT License (MIT) + +Copyright © 2018-2023 Antonio Dias (https://github.com/antonypro) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "common.h" +#include "shadow.h" +#include "qgoodwindow.h" + +#ifdef Q_OS_WIN +#define SHADOWWIDTH 10 +#define COLOR1 QColor(0, 0, 0, 75) +#define COLOR2 QColor(0, 0, 0, 30) +#define COLOR3 QColor(0, 0, 0, 1) +#endif + +Shadow::Shadow(qintptr hwnd, QGoodWindow *gw, QWidget *parent) : QWidget(parent) +{ +#ifdef Q_OS_WIN + m_hwnd = HWND(hwnd); + m_active = true; + m_parent = gw; + + setWindowFlags(Qt::Window | Qt::FramelessWindowHint | + (!m_parent ? Qt::Tool : Qt::WindowStaysOnTopHint)); + + setAttribute(Qt::WA_NoSystemBackground); + setAttribute(Qt::WA_TranslucentBackground); +#endif +#ifdef Q_OS_LINUX + Q_UNUSED(hwnd) + + m_parent = gw; + + setWindowFlags(Qt::Window | Qt::FramelessWindowHint | Qt::Tool | + Qt::WindowDoesNotAcceptFocus | Qt::NoDropShadowWindowHint); + + setAttribute(Qt::WA_TranslucentBackground); +#endif +#if defined Q_OS_WIN || defined Q_OS_LINUX + m_timer = new QTimer(this); + connect(m_timer, &QTimer::timeout, this, &Shadow::show); + //Time to wait before showing shadow when showLater() is callled. + m_timer->setInterval(500); + m_timer->setSingleShot(true); + + setWindowTitle("Shadow"); +#endif +} + +int Shadow::shadowWidth() +{ +#ifdef Q_OS_WIN + return SHADOWWIDTH; +#else + return 0; +#endif +} + +void Shadow::showLater() +{ +#if defined Q_OS_WIN || defined Q_OS_LINUX + m_timer->stop(); + m_timer->start(); +#endif +} + +void Shadow::show() +{ +#ifdef Q_OS_WIN + if (m_timer->isActive()) + return; + + if (!IsWindowEnabled(m_hwnd)) + return; + + if (GetForegroundWindow() != m_hwnd) + return; + + Q_EMIT showSignal(); + + QWidget::show(); + QWidget::raise(); + + SetWindowPos(m_hwnd, !parentWidget() ? HWND_TOP : HWND_TOPMOST, + 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE); +#endif +#ifdef Q_OS_LINUX + if (m_timer->isActive()) + return; + + QWidget *modal_widget = qApp->activeModalWidget(); + + if (!modal_widget || (modal_widget && modal_widget->window() == m_parent)) + { + if (!m_parent->isMinimized() && m_parent->isVisible()) + { + Q_EMIT showSignal(); + + QWidget::show(); + QWidget::raise(); + } + } +#endif +} + +void Shadow::hide() +{ +#ifdef Q_OS_WIN + m_timer->stop(); + + if (m_parent && m_parent->windowState().testFlag(Qt::WindowNoState)) + return; + + if (!isVisible()) + return; + + QWidget::hide(); +#endif +#ifdef Q_OS_LINUX + m_timer->stop(); + + if (m_parent->isMinimized() || !m_parent->isVisible()) + return; + + QWidget::hide(); +#endif +} + +void Shadow::setActive(bool active) +{ +#ifdef Q_OS_WIN + if (isVisible() && m_parent && !active) + hide(); + + m_active = active; + repaint(); +#else + Q_UNUSED(active) +#endif +} + +bool Shadow::nativeEvent(const QByteArray &eventType, void *message, qgoodintptr *result) +{ +#ifdef Q_OS_WIN + MSG *msg = static_cast(message); + + switch (msg->message) + { + case WM_ACTIVATE: + { + switch (msg->wParam) + { + case WA_ACTIVE: + case WA_CLICKACTIVE: + { + //When shadow got focus, transfer it to main window. + SetForegroundWindow(m_hwnd); + + break; + } + default: + break; + } + + break; + } + case WM_MOUSEACTIVATE: + { + //When shadow got focus, transfer it to main window. + SetForegroundWindow(m_hwnd); + + //Prevent main window from "focus flickering". + *result = MA_NOACTIVATE; + + return true; + } + case WM_NCMOUSEMOVE: + case WM_NCLBUTTONDOWN: + case WM_NCLBUTTONUP: + case WM_NCLBUTTONDBLCLK: + case WM_NCHITTEST: + { + //Transfer the above messages to main window, + //this way the resize and snap effects happens also + //when interacting with the shadow, and acts like a secondary border. + *result = qgoodintptr(SendMessageW(m_hwnd, msg->message, msg->wParam, msg->lParam)); + return true; + } + default: + break; + } +#endif + return QWidget::nativeEvent(eventType, message, result); +} + +bool Shadow::event(QEvent *event) +{ + switch (event->type()) + { + case QEvent::MouseButtonPress: + { +#ifdef Q_OS_LINUX + m_parent->activateWindow(); +#endif + break; + } + default: + break; + } + + return QWidget::event(event); +} + +void Shadow::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + +#ifdef Q_OS_WIN + //Draw shadow + + const int shadow_width = SHADOWWIDTH; + + QPainter painter(this); + + painter.setCompositionMode(QPainter::CompositionMode_Source); + + if (!m_active) + { + painter.fillRect(rect(), QColor(0, 0, 0, 1)); + + QRect rect1 = rect().adjusted(shadow_width, shadow_width, -shadow_width, -shadow_width); + + painter.fillRect(rect1, Qt::transparent); + + return; + } + + QPixmap radial_gradient = QPixmap(shadow_width * 2, shadow_width * 2); + + { + //Draw a radial gradient then split it in 4 parts and draw it to corners and edges + + radial_gradient.fill(QColor(0, 0, 0, 1)); + + QPainter painter(&radial_gradient); + painter.setRenderHint(QPainter::Antialiasing); + painter.setCompositionMode(QPainter::CompositionMode_Source); + + QRadialGradient gradient(shadow_width, shadow_width, shadow_width); + gradient.setColorAt(0.0, COLOR1); + gradient.setColorAt(0.2, COLOR2); + gradient.setColorAt(0.5, COLOR3); + + QPen pen(Qt::transparent, 0); + painter.setPen(pen); + painter.setBrush(gradient); + painter.drawEllipse(0, 0, shadow_width * 2, shadow_width * 2); + } + + QRect rect1 = rect().adjusted(shadow_width, shadow_width, -shadow_width, -shadow_width); + + painter.drawPixmap(0, 0, shadow_width, shadow_width, radial_gradient, 0, 0, shadow_width, shadow_width); //Top-left corner + painter.drawPixmap(rect().width() - shadow_width, 0, radial_gradient, shadow_width, 0, shadow_width, shadow_width); //Top-right corner + painter.drawPixmap(0, rect().height() - shadow_width, radial_gradient, 0, shadow_width, shadow_width, shadow_width); //Bottom-left corner + painter.drawPixmap(rect().width() - shadow_width, rect().height() - shadow_width, radial_gradient, shadow_width, shadow_width, shadow_width, shadow_width); //Bottom-right corner + + painter.drawPixmap(shadow_width, 0, rect1.width(), shadow_width, radial_gradient, shadow_width, 0, 1, shadow_width); //Top + painter.drawPixmap(0, shadow_width, shadow_width, rect1.height(), radial_gradient, 0, shadow_width, shadow_width, 1); //Left + painter.drawPixmap(rect1.width() + shadow_width, shadow_width, shadow_width, rect1.height(), radial_gradient, shadow_width, shadow_width, shadow_width, 1); //Right + painter.drawPixmap(shadow_width, rect1.height() + shadow_width, rect1.width(), shadow_width, radial_gradient, shadow_width, shadow_width, 1, SHADOWWIDTH); //Bottom +#endif +#ifdef Q_OS_LINUX + if (!m_parent) + return; + + QPainter painter(this); + painter.setCompositionMode(QPainter::CompositionMode_Source); + painter.fillRect(rect(), QColor(0, 0, 0, 0)); +#endif +} diff --git a/lib/QGoodWindow/QGoodWindow/src/shadow.h b/lib/QGoodWindow/QGoodWindow/src/shadow.h new file mode 100644 index 00000000..6f4305b8 --- /dev/null +++ b/lib/QGoodWindow/QGoodWindow/src/shadow.h @@ -0,0 +1,72 @@ +/* +The MIT License (MIT) + +Copyright © 2018-2023 Antonio Dias (https://github.com/antonypro) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef SHADOW_H +#define SHADOW_H + +#include +#include +#include +#include "intcommon.h" + +#if defined Q_OS_WIN || defined Q_OS_LINUX +class QGoodWindow; +#endif + +//\cond HIDDEN_SYMBOLS +class Shadow : public QWidget +{ + Q_OBJECT +public: + explicit Shadow(qintptr hwnd, QGoodWindow *gw, QWidget *parent); + +Q_SIGNALS: + void showSignal(); + +public Q_SLOTS: + void showLater(); + void show(); + void hide(); + void setActive(bool active); + int shadowWidth(); + +private: + //Functions + bool nativeEvent(const QByteArray &eventType, void *message, qgoodintptr *result); + bool event(QEvent *event); + void paintEvent(QPaintEvent *event); + + //Variables +#if defined Q_OS_WIN || defined Q_OS_LINUX + QPointer m_parent; + QPointer m_timer; +#endif +#ifdef Q_OS_WIN + HWND m_hwnd; + bool m_active; +#endif +}; +//\endcond + +#endif // SHADOW_H diff --git a/lib/QGoodWindow/QGoodWindow/src/stylecommon.cpp b/lib/QGoodWindow/QGoodWindow/src/stylecommon.cpp new file mode 100644 index 00000000..11fb1f8e --- /dev/null +++ b/lib/QGoodWindow/QGoodWindow/src/stylecommon.cpp @@ -0,0 +1,121 @@ +/* +The MIT License (MIT) + +Copyright © 2022-2023 Antonio Dias (https://github.com/antonypro) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifdef _WIN32 + +#ifdef NTDDI_VERSION +#undef NTDDI_VERSION +#endif + +#ifdef _WIN32_WINNT +#undef _WIN32_WINNT +#endif + +#define NTDDI_VERSION NTDDI_VISTA +#define _WIN32_WINNT _WIN32_WINNT_VISTA + +#include +#include + +#include "stylecommon.h" + +QPixmap StyleCommon::winStandardPixmap(QStyle::StandardPixmap standardPixmap) +{ + QPixmap pixmap; + + HICON hicon = nullptr; + + LPCWSTR icon_handle_xp = nullptr; + SHSTOCKICONID icon_handle_vista = SIID_INVALID; + + switch (standardPixmap) + { + case QStyle::SP_MessageBoxInformation: + { + icon_handle_xp = IDI_INFORMATION; + icon_handle_vista = SIID_INFO; + break; + } + case QStyle::SP_MessageBoxWarning: + { + icon_handle_xp = IDI_WARNING; + icon_handle_vista = SIID_WARNING; + break; + } + case QStyle::SP_MessageBoxCritical: + { + icon_handle_xp = IDI_ERROR; + icon_handle_vista = SIID_ERROR; + break; + } + case QStyle::SP_MessageBoxQuestion: + { + icon_handle_xp = IDI_QUESTION; + icon_handle_vista = SIID_HELP; + break; + } + default: + break; + } + + typedef HRESULT(STDAPICALLTYPE *tSHGetStockIconInfo)(SHSTOCKICONID siid, UINT uFlags, SHSTOCKICONINFO *psii); + tSHGetStockIconInfo pSHGetStockIconInfo = tSHGetStockIconInfo(QLibrary::resolve("shell32", "SHGetStockIconInfo")); + + if (pSHGetStockIconInfo) + { + if (icon_handle_vista != SIID_INVALID) + { + SHSTOCKICONINFO sii; + memset(&sii, 0, sizeof(SHSTOCKICONINFO)); + sii.cbSize = sizeof(sii); + + if (SUCCEEDED(pSHGetStockIconInfo(icon_handle_vista, SHGSI_ICON | SHGSI_LARGEICON, &sii))) + { + hicon = sii.hIcon; + } + } + } + else + { + if (icon_handle_xp) + { + hicon = LoadIconW(nullptr, icon_handle_xp); + } + } + + if (hicon) + { +#ifdef QT_VERSION_QT5 + pixmap = QtWin::fromHICON(hicon); +#endif +#ifdef QT_VERSION_QT6 + pixmap = QPixmap::fromImage(QImage::fromHICON(hicon)); +#endif + DestroyIcon(hicon); + } + + return pixmap; +} + +#endif diff --git a/lib/QGoodWindow/QGoodWindow/src/stylecommon.h b/lib/QGoodWindow/QGoodWindow/src/stylecommon.h new file mode 100644 index 00000000..73968489 --- /dev/null +++ b/lib/QGoodWindow/QGoodWindow/src/stylecommon.h @@ -0,0 +1,46 @@ +/* +The MIT License (MIT) + +Copyright © 2022-2023 Antonio Dias (https://github.com/antonypro) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef STYLECOMMON +#define STYLECOMMON + +#ifdef _WIN32 + +#include +#include +#include + +#ifdef QT_VERSION_QT5 +#include +#endif + +namespace StyleCommon +{ +QPixmap winStandardPixmap(QStyle::StandardPixmap standardPixmap); +} + +#endif + +#endif // STYLECOMMON + diff --git a/lib/QGoodWindow/QGoodWindow/version/version.h b/lib/QGoodWindow/QGoodWindow/version/version.h new file mode 100644 index 00000000..cdab074b --- /dev/null +++ b/lib/QGoodWindow/QGoodWindow/version/version.h @@ -0,0 +1,6 @@ +#ifndef VERSION_H +#define VERSION_H + +#define QGOODWINDOW_VERSION "2.4.2-beta4" + +#endif // VERSION_H diff --git a/quardCRT.pro b/quardCRT.pro index 44cfdd0a..5a36cd3d 100644 --- a/quardCRT.pro +++ b/quardCRT.pro @@ -23,6 +23,8 @@ include(./lib/qcustomfilesystemmodel/qcustomfilesystemmodel.pri) include(./lib/qtkeychain/qtkeychain.pri) include(./lib/QVNCClient/QVNCClient.pri) include(./lib/qhexedit/qhexedit.pri) +include(./lib/QGoodWindow/QGoodWindow/QGoodWindow.pri) +include(./lib/QGoodWindow/QGoodCentralWidget/QGoodCentralWidget.pri) INCLUDEPATH += \ src/util \ diff --git a/src/main.cpp b/src/main.cpp index 82ab545b..dc982a95 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -154,6 +154,7 @@ static QTranslator appTranslator; int main(int argc, char *argv[]) { + QGoodWindow::setup(); QApplication::setAttribute(Qt::AA_DontUseNativeDialogs); QApplication::setAttribute(Qt::AA_DontUseNativeMenuBar); QApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); @@ -261,7 +262,7 @@ int main(int argc, char *argv[]) if(app_lang == "fr_FR") locale = QLocale::French; if(app_lang == "es_ES") locale = QLocale::Spanish; if(app_lang == "en_US") locale = QLocale::English; - MainWindow::setAppLangeuage(locale); + CentralWidget::setAppLangeuage(locale); #if 0 // Now we always use the dark theme, Because the dark theme is more beautiful int text_hsv_value = QPalette().color(QPalette::WindowText).value(); @@ -272,26 +273,12 @@ int main(int argc, char *argv[]) #endif if(dark_theme == "true") isDarkTheme = true; if(dark_theme == "false") isDarkTheme = false; - QString themeName; - if(isDarkTheme) { - themeName = ":/qdarkstyle/dark/darkstyle.qss"; - } else { - themeName = ":/qdarkstyle/light/lightstyle.qss"; - } - QFile ftheme(themeName); - if (!ftheme.exists()) { - qDebug() << "Unable to set stylesheet, file not found!"; - } else { - ftheme.open(QFile::ReadOnly | QFile::Text); - QTextStream ts(&ftheme); - qApp->setStyleSheet(ts.readAll()); - } QFontIcon::addFont(":/icons/icons/fontawesome-webfont.ttf"); QFontIcon::instance()->setColor(isDarkTheme?Qt::white:Qt::black); //QApplication::setStyle(QStyleFactory::create("Fusion")); - MainWindow window(dir,isMiniUI?MainWindow::MINIUI_MODE:MainWindow::STDUI_MODE,locale,isDarkTheme); + MainWindow window(dir,isMiniUI?CentralWidget::MINIUI_MODE:CentralWidget::STDUI_MODE,locale,isDarkTheme); window.show(); return application.exec(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 4e220d2f..e199ada2 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -63,16 +63,17 @@ #include "ui_mainwindow.h" -MainWindow::MainWindow(QString dir, StartupUIMode mode, QLocale lang, bool isDark, QWidget *parent) +CentralWidget::CentralWidget(QString dir, StartupUIMode mode, QLocale lang, bool isDark, QWidget *parent) : QMainWindow(parent) - , ui(new Ui::MainWindow) + , ui(new Ui::CentralWidget) , windowTransparency(1.0) , windowTransparencyEnabled(false) , language(lang) - , isDarkTheme(isDark) { + , isDarkTheme(isDark) + , mainWindow(static_cast(parent)) { ui->setupUi(this); - restoreSettings(); + ui->toolBar->setVisible(true); setWindowTitle(QApplication::applicationName()+" - "+VERSION); @@ -375,16 +376,20 @@ MainWindow::MainWindow(QString dir, StartupUIMode mode, QLocale lang, bool isDar } if(sessionsWindow->isTerminal()) terminalWidgetContextMenuBase(menu,sessionsWindow,position); - if(!ui->menuBar->isVisible()) { + if(fullScreenAction->isChecked()) { + menu->addSeparator(); + menu->addAction(fullScreenAction); + } else if(!ui->menuBar->isVisible()) { menu->addSeparator(); menu->addAction(menuBarAction); } } else { - if(!ui->menuBar->isVisible()) { + if(fullScreenAction->isChecked()) { + menu->addSeparator(); + menu->addAction(fullScreenAction); + } else if(!ui->menuBar->isVisible()) { + menu->addSeparator(); menu->addAction(menuBarAction); - } else { - delete menu; - return; } } } else if(index == -2) { @@ -392,9 +397,6 @@ MainWindow::MainWindow(QString dir, StartupUIMode mode, QLocale lang, bool isDar menu->addAction(toolBarAction); menu->addAction(cmdWindowAction); menu->addAction(fullScreenAction); - } else { - delete menu; - return; } if(menu->isEmpty()) { delete menu; @@ -609,7 +611,7 @@ MainWindow::MainWindow(QString dir, StartupUIMode mode, QLocale lang, bool isDar cascadeAction->setEnabled(false); } -MainWindow::~MainWindow() { +CentralWidget::~CentralWidget() { stopAllSession(true); saveSettings(); if(tftpServer->isRunning()) { @@ -621,7 +623,7 @@ MainWindow::~MainWindow() { delete ui; } -void MainWindow::moveToAnotherTab(int src,int dst, int index) { +void CentralWidget::moveToAnotherTab(int src,int dst, int index) { QIcon icon = mainWidgetGroupList.at(src)->sessionTab->tabIcon(index); QString text = mainWidgetGroupList.at(src)->sessionTab->tabTitle(index); QWidget *widget = mainWidgetGroupList.at(src)->sessionTab->widget(index); @@ -634,7 +636,7 @@ void MainWindow::moveToAnotherTab(int src,int dst, int index) { mainWidgetGroupList.at(src)->sessionTab->count()-1); }; -void MainWindow::terminalWidgetContextMenuBase(QMenu *menu,SessionsWindow *term,const QPoint& position) +void CentralWidget::terminalWidgetContextMenuBase(QMenu *menu,SessionsWindow *term,const QPoint& position) { QList ftActions = term->filterActions(position); if(!ftActions.isEmpty()) { @@ -742,7 +744,7 @@ void MainWindow::terminalWidgetContextMenuBase(QMenu *menu,SessionsWindow *term, } } -void MainWindow::floatingWindow(MainWidgetGroup *g, int index) { +void CentralWidget::floatingWindow(MainWidgetGroup *g, int index) { QDialog *dialog = new QDialog(this); dialog->setWindowFlags(Qt::Window); dialog->resize(800,480); @@ -798,19 +800,29 @@ void MainWindow::floatingWindow(MainWidgetGroup *g, int index) { dialog->show(); } -void MainWindow::saveSettings(void) { +void CentralWidget::saveSettings(void) { GlobalSetting settings; - settings.setValue("MainWindow/Geometry", saveGeometry()); - settings.setValue("MainWindow/State", saveState()); + if(mainWindow) { + settings.setValue("MainWindow/Geometry", mainWindow->saveGeometry()); + settings.setValue("MainWindow/State", mainWindow->saveState()); + } else { + settings.setValue("MainWindow/Geometry", saveGeometry()); + settings.setValue("MainWindow/State", saveState()); + } } -void MainWindow::restoreSettings(void) { +void CentralWidget::restoreSettings(void) { GlobalSetting settings; - restoreGeometry(settings.value("MainWindow/Geometry").toByteArray()); - restoreState(settings.value("MainWindow/State").toByteArray()); + if(mainWindow) { + mainWindow->restoreGeometry(settings.value("MainWindow/Geometry").toByteArray()); + mainWindow->restoreState(settings.value("MainWindow/State").toByteArray()); + } else { + restoreGeometry(settings.value("MainWindow/Geometry").toByteArray()); + restoreState(settings.value("MainWindow/State").toByteArray()); + } } -MainWidgetGroup* MainWindow::findCurrentFocusGroup(void) { +MainWidgetGroup* CentralWidget::findCurrentFocusGroup(void) { foreach(MainWidgetGroup *mainWidgetGroup, mainWidgetGroupList) { if(mainWidgetGroup->sessionTab->currentWidget()->hasFocus()) { return mainWidgetGroup; @@ -824,13 +836,13 @@ MainWidgetGroup* MainWindow::findCurrentFocusGroup(void) { return mainWidgetGroupList[0]; } -QWidget *MainWindow::findCurrentFocusWidget(void) { +QWidget *CentralWidget::findCurrentFocusWidget(void) { SessionTab *sessionTab = findCurrentFocusGroup()->sessionTab; if(sessionTab->count() == 0) return nullptr; return sessionTab->currentWidget(); } -void MainWindow::menuAndToolBarRetranslateUi(void) { +void CentralWidget::menuAndToolBarRetranslateUi(void) { sessionManagerPushButton->setText(tr("Session Manager")); fileMenu->setTitle(tr("File")); @@ -1080,7 +1092,7 @@ void MainWindow::menuAndToolBarRetranslateUi(void) { aboutQtAction->setStatusTip(tr("Display about Qt dialog")); } -void MainWindow::menuAndToolBarInit(void) { +void CentralWidget::menuAndToolBarInit(void) { GlobalSetting settings; ui->toolBar->setIconSize(QSize(16,16)); @@ -1430,7 +1442,7 @@ void MainWindow::menuAndToolBarInit(void) { setSessionClassActionEnable(false); } -void MainWindow::setSessionClassActionEnable(bool enable) +void CentralWidget::setSessionClassActionEnable(bool enable) { reconnectAction->setEnabled(enable); reconnectAllAction->setEnabled(enable); @@ -1472,7 +1484,7 @@ void MainWindow::setSessionClassActionEnable(bool enable) //startZmodemUploadAction->setEnabled(enable); } -void MainWindow::menuAndToolBarConnectSignals(void) { +void CentralWidget::menuAndToolBarConnectSignals(void) { connect(newWindowAction,&QAction::triggered,this,[=](){ QProcess::startDetached(QApplication::applicationFilePath(),QApplication::arguments().mid(1)); }); @@ -1694,7 +1706,11 @@ void MainWindow::menuAndToolBarConnectSignals(void) { }); connect(globalOptionsWindow,&GlobalOptionsWindow::transparencyChanged,this,[=](int transparency){ windowTransparency = (100-transparency)/100.0; - setWindowOpacity(windowTransparencyEnabled?windowTransparency:1.0); + if(mainWindow) { + mainWindow->setWindowOpacity(windowTransparencyEnabled?windowTransparency:1.0); + } else { + setWindowOpacity(windowTransparencyEnabled?windowTransparency:1.0); + } }); connect(sessionOptionsWindow,&SessionOptionsWindow::sessionPropertiesChanged,this, [=](QString name, QuickConnectWindow::QuickConnectData data, QString newName) { @@ -1904,7 +1920,11 @@ void MainWindow::menuAndToolBarConnectSignals(void) { }); connect(windwosTransparencyAction,&QAction::triggered,this,[=](bool checked){ windowTransparencyEnabled = checked; - setWindowOpacity(windowTransparencyEnabled?windowTransparency:1.0); + if(mainWindow) { + mainWindow->setWindowOpacity(windowTransparencyEnabled?windowTransparency:1.0); + } else { + setWindowOpacity(windowTransparencyEnabled?windowTransparency:1.0); + } }); connect(verticalScrollBarAction,&QAction::triggered,this,[=](bool checked){ foreach(SessionsWindow *sessionsWindow, sessionList) { @@ -1912,18 +1932,35 @@ void MainWindow::menuAndToolBarConnectSignals(void) { } }); connect(allwaysOnTopAction,&QAction::triggered,this,[=](bool checked){ - if(checked) { - setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint); + if(mainWindow) { + if(checked) { + mainWindow->setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint); + } else { + mainWindow->setWindowFlags(windowFlags() & ~Qt::WindowStaysOnTopHint); + } + mainWindow->show(); } else { - setWindowFlags(windowFlags() & ~Qt::WindowStaysOnTopHint); + if(checked) { + setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint); + } else { + setWindowFlags(windowFlags() & ~Qt::WindowStaysOnTopHint); + } + show(); } - show(); }); connect(fullScreenAction,&QAction::triggered,this,[=](bool checked){ - if(checked) { - this->showFullScreen(); + if(mainWindow) { + if(checked) { + mainWindow->showFullScreen(); + } else { + mainWindow->showNormal(); + } } else { - this->showNormal(); + if(checked) { + showFullScreen(); + } else { + showNormal(); + } } }); connect(startTFTPServerAction,&QAction::triggered,this,[=](bool checked){ @@ -2062,14 +2099,7 @@ void MainWindow::menuAndToolBarConnectSignals(void) { }); connect(lightThemeAction,&QAction::triggered,this,[=](){ isDarkTheme = false; - QFile ftheme(":/qdarkstyle/light/lightstyle.qss"); - if (!ftheme.exists()) { - qDebug() << "Unable to set stylesheet, file not found!"; - } else { - ftheme.open(QFile::ReadOnly | QFile::Text); - QTextStream ts(&ftheme); - qApp->setStyleSheet(ts.readAll()); - } + qGoodStateHolder->setCurrentThemeDark(isDarkTheme); QFontIcon::instance()->setColor(Qt::black); menuAndToolBarRetranslateUi(); GlobalSetting settings; @@ -2077,14 +2107,7 @@ void MainWindow::menuAndToolBarConnectSignals(void) { }); connect(darkThemeAction,&QAction::triggered,this,[=](){ isDarkTheme = true; - QFile ftheme(":/qdarkstyle/dark/darkstyle.qss"); - if (!ftheme.exists()) { - qDebug() << "Unable to set stylesheet, file not found!"; - } else { - ftheme.open(QFile::ReadOnly | QFile::Text); - QTextStream ts(&ftheme); - qApp->setStyleSheet(ts.readAll()); - } + qGoodStateHolder->setCurrentThemeDark(isDarkTheme); QFontIcon::instance()->setColor(Qt::white); menuAndToolBarRetranslateUi(); GlobalSetting settings; @@ -2094,7 +2117,7 @@ void MainWindow::menuAndToolBarConnectSignals(void) { qApp->quit(); }); connect(helpAction, &QAction::triggered, this, [&]() { - MainWindow::appHelp(this); + CentralWidget::appHelp(this); }); connect(checkUpdateAction, &QAction::triggered, this, [&]() { QLocale locale; @@ -2106,14 +2129,14 @@ void MainWindow::menuAndToolBarConnectSignals(void) { } }); connect(aboutAction, &QAction::triggered, this, [&]() { - MainWindow::appAbout(this); + CentralWidget::appAbout(this); }); connect(aboutQtAction, &QAction::triggered, this, [&]() { QMessageBox::aboutQt(this); }); } -void MainWindow::setGlobalOptions(SessionsWindow *window) { +void CentralWidget::setGlobalOptions(SessionsWindow *window) { window->setKeyBindings(keyMapManagerWindow->getCurrentKeyBinding()); window->setColorScheme(globalOptionsWindow->getCurrentColorScheme()); window->setTerminalFont(globalOptionsWindow->getCurrentFont()); @@ -2148,7 +2171,7 @@ void MainWindow::setGlobalOptions(SessionsWindow *window) { } } -void MainWindow::restoreSessionToSessionManager(void) +void CentralWidget::restoreSessionToSessionManager(void) { GlobalSetting settings; int size = settings.beginReadArray("Global/Session"); @@ -2161,7 +2184,7 @@ void MainWindow::restoreSessionToSessionManager(void) settings.endArray(); } -bool MainWindow::checkSessionName(QString &name) +bool CentralWidget::checkSessionName(QString &name) { QString oldNmae = name; for(uint32_t i=0;iaddSession(name,sessionsWindow->getSessionType()); @@ -2233,7 +2256,7 @@ int MainWindow::addSessionToSessionManager(SessionsWindow *sessionsWindow, QStri return 0; } -int MainWindow::addSessionToSessionManager(const QuickConnectWindow::QuickConnectData &data, QString &name, bool checkname, int64_t id) +int CentralWidget::addSessionToSessionManager(const QuickConnectWindow::QuickConnectData &data, QString &name, bool checkname, int64_t id) { if(checkname) checkSessionName(name); @@ -2282,7 +2305,7 @@ int MainWindow::addSessionToSessionManager(const QuickConnectWindow::QuickConnec return 0; } -int64_t MainWindow::removeSessionFromSessionManager(QString name) +int64_t CentralWidget::removeSessionFromSessionManager(QString name) { int64_t matched = -1; sessionManagerWidget->removeSession(name); @@ -2321,7 +2344,7 @@ int64_t MainWindow::removeSessionFromSessionManager(QString name) return matched; } -void MainWindow::connectSessionFromSessionManager(QString name) +void CentralWidget::connectSessionFromSessionManager(QString name) { foreach(SessionsWindow *sessionsWindow, sessionList) { if(sessionsWindow->getName() == name) { @@ -2378,7 +2401,7 @@ void MainWindow::connectSessionFromSessionManager(QString name) } } -void MainWindow::connectSessionStateChange(SessionTab *tab, int index, SessionsWindow *sessionsWindow) +void CentralWidget::connectSessionStateChange(SessionTab *tab, int index, SessionsWindow *sessionsWindow) { tab->setTabIcon(index,QFontIcon::icon(QChar(0xf09e), Qt::gray)); connect(sessionsWindow, &SessionsWindow::stateChanged, this, [=](SessionsWindow::SessionsState state){ @@ -2417,7 +2440,7 @@ void MainWindow::connectSessionStateChange(SessionTab *tab, int index, SessionsW }); } -QString MainWindow::startTelnetSession(MainWidgetGroup *group, QString hostname, quint16 port, QTelnet::SocketType type, QString name) +QString CentralWidget::startTelnetSession(MainWidgetGroup *group, QString hostname, quint16 port, QTelnet::SocketType type, QString name) { SessionsWindow *sessionsWindow = new SessionsWindow(SessionsWindow::Telnet,this); setGlobalOptions(sessionsWindow); @@ -2449,7 +2472,7 @@ QString MainWindow::startTelnetSession(MainWidgetGroup *group, QString hostname, return name; } -QString MainWindow::startSerialSession(MainWidgetGroup *group, QString portName, uint32_t baudRate, +QString CentralWidget::startSerialSession(MainWidgetGroup *group, QString portName, uint32_t baudRate, int dataBits, int parity, int stopBits, bool flowControl, bool xEnable, QString name) { SessionsWindow *sessionsWindow = new SessionsWindow(SessionsWindow::Serial,this); @@ -2482,7 +2505,7 @@ QString MainWindow::startSerialSession(MainWidgetGroup *group, QString portName, return name; } -QString MainWindow::startRawSocketSession(MainWidgetGroup *group, QString hostname, quint16 port, QString name) +QString CentralWidget::startRawSocketSession(MainWidgetGroup *group, QString hostname, quint16 port, QString name) { SessionsWindow *sessionsWindow = new SessionsWindow(SessionsWindow::RawSocket,this); setGlobalOptions(sessionsWindow); @@ -2514,7 +2537,7 @@ QString MainWindow::startRawSocketSession(MainWidgetGroup *group, QString hostna return name; } -QString MainWindow::startNamePipeSession(MainWidgetGroup *group, QString pipeName, QString name) +QString CentralWidget::startNamePipeSession(MainWidgetGroup *group, QString pipeName, QString name) { SessionsWindow *sessionsWindow = new SessionsWindow(SessionsWindow::NamePipe,this); setGlobalOptions(sessionsWindow); @@ -2546,7 +2569,7 @@ QString MainWindow::startNamePipeSession(MainWidgetGroup *group, QString pipeNam return name; } -QString MainWindow::getDirAndcheckeSysName(const QString &title) +QString CentralWidget::getDirAndcheckeSysName(const QString &title) { // newTitle lile username@hostname:dir #if defined(Q_OS_WIN) @@ -2582,7 +2605,7 @@ QString MainWindow::getDirAndcheckeSysName(const QString &title) return QString(); } -QString MainWindow::startLocalShellSession(MainWidgetGroup *group, const QString &command, const QString &workingDirectory, QString name) +QString CentralWidget::startLocalShellSession(MainWidgetGroup *group, const QString &command, const QString &workingDirectory, QString name) { SessionsWindow *sessionsWindow = new SessionsWindow(SessionsWindow::LocalShell,this); setGlobalOptions(sessionsWindow); @@ -2632,7 +2655,7 @@ QString MainWindow::startLocalShellSession(MainWidgetGroup *group, const QString return name; } -QString MainWindow::startSSH2Session(MainWidgetGroup *group, +QString CentralWidget::startSSH2Session(MainWidgetGroup *group, QString hostname, quint16 port, QString username, QString password, QString name) { SessionsWindow *sessionsWindow = new SessionsWindow(SessionsWindow::SSH2,this); @@ -2665,7 +2688,7 @@ QString MainWindow::startSSH2Session(MainWidgetGroup *group, return name; } -QString MainWindow::startVNCSession(MainWidgetGroup *group, QString hostname, quint16 port, QString password, QString name) +QString CentralWidget::startVNCSession(MainWidgetGroup *group, QString hostname, quint16 port, QString password, QString name) { SessionsWindow *sessionsWindow = new SessionsWindow(SessionsWindow::VNC,this); setGlobalOptions(sessionsWindow); @@ -2684,7 +2707,7 @@ QString MainWindow::startVNCSession(MainWidgetGroup *group, QString hostname, qu return name; } -int MainWindow::stopSession(MainWidgetGroup *group, int index, bool force) +int CentralWidget::stopSession(MainWidgetGroup *group, int index, bool force) { if(index <= 0) return -1; if(group->sessionTab->count() == 0) return -1; @@ -2723,7 +2746,7 @@ int MainWindow::stopSession(MainWidgetGroup *group, int index, bool force) return 0; } -int MainWindow::stopAllSession(bool force) +int CentralWidget::stopAllSession(bool force) { foreach(MainWidgetGroup *mainWidgetGroup, mainWidgetGroupList) { while(mainWidgetGroup->sessionTab->count() > 0) { @@ -2733,7 +2756,7 @@ int MainWindow::stopAllSession(bool force) return 0; } -int MainWindow::cloneCurrentSession(MainWidgetGroup *group, QString name) +int CentralWidget::cloneCurrentSession(MainWidgetGroup *group, QString name) { if(group->sessionTab->count() == 0) return -1; QWidget *widget = group->sessionTab->currentWidget(); @@ -2781,7 +2804,7 @@ int MainWindow::cloneCurrentSession(MainWidgetGroup *group, QString name) return 0; } -void MainWindow::sessionWindow2InfoData(SessionsWindow *sessionsWindow, QuickConnectWindow::QuickConnectData &data, QString &name) +void CentralWidget::sessionWindow2InfoData(SessionsWindow *sessionsWindow, QuickConnectWindow::QuickConnectData &data, QString &name) { name = sessionsWindow->getName(); data.type = (QuickConnectWindow::QuickConnectType)sessionsWindow->getSessionType(); @@ -2832,7 +2855,7 @@ void MainWindow::sessionWindow2InfoData(SessionsWindow *sessionsWindow, QuickCon } } -int MainWindow::setting2InfoData(GlobalSetting *settings, QuickConnectWindow::QuickConnectData &data, QString &name,bool skipPassword) +int CentralWidget::setting2InfoData(GlobalSetting *settings, QuickConnectWindow::QuickConnectData &data, QString &name,bool skipPassword) { name = settings->value("name").toString(); data.type = (QuickConnectWindow::QuickConnectType)(settings->value("type").toInt()); @@ -2890,7 +2913,7 @@ int MainWindow::setting2InfoData(GlobalSetting *settings, QuickConnectWindow::Qu return 0; } -void MainWindow::infoData2Setting(GlobalSetting *settings,const QuickConnectWindow::QuickConnectData &data,const QString &name,bool skipPassword) { +void CentralWidget::infoData2Setting(GlobalSetting *settings,const QuickConnectWindow::QuickConnectData &data,const QString &name,bool skipPassword) { settings->setValue("name",name); settings->setValue("type",data.type); switch(data.type) { @@ -2936,7 +2959,7 @@ void MainWindow::infoData2Setting(GlobalSetting *settings,const QuickConnectWind } } -void MainWindow::addBookmark(const QString &path) +void CentralWidget::addBookmark(const QString &path) { QAction *action = new QAction(path,bookmarkMenu); action->setStatusTip(path); @@ -2953,7 +2976,7 @@ void MainWindow::addBookmark(const QString &path) settings.endArray(); } -QMenu *MainWindow::createPopupMenu() +QMenu *CentralWidget::createPopupMenu() { QMenu *menu = new QMenu(this); menu->addAction(menuBarAction); @@ -2963,7 +2986,7 @@ QMenu *MainWindow::createPopupMenu() return menu; } -void MainWindow::appAbout(QWidget *parent) +void CentralWidget::appAbout(QWidget *parent) { QMessageBox::about(parent, tr("About"), tr( @@ -2980,7 +3003,7 @@ void MainWindow::appAbout(QWidget *parent) ); } -void MainWindow::appHelp(QWidget *parent) +void CentralWidget::appHelp(QWidget *parent) { QMessageBox::about(parent, tr("Help"), QString() + "" + @@ -2999,7 +3022,7 @@ void MainWindow::appHelp(QWidget *parent) ); } -void MainWindow::closeEvent(QCloseEvent *event) +void CentralWidget::closeEvent(QCloseEvent *event) { int activeSessionCount = 0; int lockedSessionCount = 0; @@ -3036,7 +3059,7 @@ void MainWindow::closeEvent(QCloseEvent *event) } } -void MainWindow::setAppLangeuage(QLocale lang) { +void CentralWidget::setAppLangeuage(QLocale lang) { GlobalSetting settings; static QTranslator *qtTranslator = nullptr; static QTranslator *qtbaseTranslator = nullptr; @@ -3140,3 +3163,61 @@ void MainWindow::setAppLangeuage(QLocale lang) { break; } } + +MainWindow::MainWindow(QString dir, CentralWidget::StartupUIMode mode, QLocale lang, bool isDark, QWidget *parent) + : QGoodWindow(parent) +{ + m_central_widget = new CentralWidget(dir,mode,lang,isDark,this); + m_central_widget->setWindowFlags(Qt::Widget); + + m_good_central_widget = new QGoodCentralWidget(this); + +#ifdef Q_OS_MAC + //macOS uses global menu bar + if(QApplication::testAttribute(Qt::AA_DontUseNativeMenuBar)) { +#else + if(true) { +#endif + QMenuBar *menu_bar = m_central_widget->menuBar(); + + //Set font of menu bar + QFont font = menu_bar->font(); + #ifdef Q_OS_WIN + font.setFamily("Segoe UI"); + #else + font.setFamily(qApp->font().family()); + #endif + menu_bar->setFont(font); + + QTimer::singleShot(0, this, [=]{ + const int title_bar_height = m_good_central_widget->titleBarHeight(); + menu_bar->setStyleSheet(QString("QMenuBar {height: %0px;}").arg(title_bar_height)); + }); + + m_good_central_widget->setLeftTitleBarWidget(menu_bar); + setNativeCaptionButtonsVisibleOnMac(false); + } + + connect(qGoodStateHolder, &QGoodStateHolder::currentThemeChanged, this, [](){ + if (qGoodStateHolder->isCurrentThemeDark()) + QGoodWindow::setAppDarkTheme(); + else + QGoodWindow::setAppLightTheme(); + }); + connect(this, &QGoodWindow::systemThemeChanged, this, [=]{ + qGoodStateHolder->setCurrentThemeDark(QGoodWindow::isSystemThemeDark()); + }); + qGoodStateHolder->setCurrentThemeDark(isDark); + + m_good_central_widget->setCentralWidget(m_central_widget); + setCentralWidget(m_good_central_widget); + + setWindowIcon(QIcon(":/icons/icons/about.png")); + setWindowTitle(QApplication::applicationName()+" - "+VERSION); + + m_good_central_widget->setTitleAlignment(Qt::AlignCenter); + +} + +MainWindow::~MainWindow() { +} diff --git a/src/mainwindow.h b/src/mainwindow.h index 50305e02..591854c3 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -49,15 +49,18 @@ #include "sftpwindow.h" #include "netscanwindow.h" #include "keychainclass.h" +#include "QGoodWindow" +#include "QGoodCentralWidget" extern QString VERSION; extern QString GIT_TAG; QT_BEGIN_NAMESPACE -namespace Ui { class MainWindow; } +namespace Ui { class CentralWidget; } QT_END_NAMESPACE -class MainWindow : public QMainWindow +class MainWindow; +class CentralWidget : public QMainWindow { Q_OBJECT @@ -66,8 +69,8 @@ class MainWindow : public QMainWindow STDUI_MODE = 0, MINIUI_MODE, }; - MainWindow(QString dir = QString(), StartupUIMode mode = STDUI_MODE, QLocale lang = QLocale(QLocale::English), bool isDark = true, QWidget *parent = nullptr); - ~MainWindow(); + CentralWidget(QString dir = QString(), StartupUIMode mode = STDUI_MODE, QLocale lang = QLocale(QLocale::English), bool isDark = true, QWidget *parent = nullptr); + ~CentralWidget(); static void appAbout(QWidget *parent = nullptr); static void appHelp(QWidget *parent = nullptr); static void setAppLangeuage(QLocale lang); @@ -102,7 +105,7 @@ class MainWindow : public QMainWindow void connectSessionFromSessionManager(QString name); void restoreSessionToSessionManager(void); void saveSettings(void); - void restoreSettings(void); + void restoreSettings(void); void connectSessionStateChange(SessionTab *tab, int index, SessionsWindow *sessionsWindow); void sessionWindow2InfoData(SessionsWindow *sessionsWindow, QuickConnectWindow::QuickConnectData &data, QString &name); int setting2InfoData(GlobalSetting *settings, QuickConnectWindow::QuickConnectData &data, QString &name, bool skipPassword = false); @@ -115,7 +118,7 @@ class MainWindow : public QMainWindow void closeEvent(QCloseEvent *event) override; private: - Ui::MainWindow *ui; + Ui::CentralWidget *ui; SessionManagerWidget *sessionManagerWidget; QList mainWidgetGroupList; @@ -251,5 +254,19 @@ class MainWindow : public QMainWindow QLocale language; bool isDarkTheme; + class MainWindow *mainWindow = nullptr; }; + +class MainWindow : public QGoodWindow +{ + Q_OBJECT +public: + explicit MainWindow(QString dir = QString(), CentralWidget::StartupUIMode mode = CentralWidget::STDUI_MODE, QLocale lang = QLocale(QLocale::English), bool isDark = true, QWidget *parent = nullptr); + ~MainWindow(); + +private: + QGoodCentralWidget *m_good_central_widget; + CentralWidget *m_central_widget; +}; + #endif // MAINWINDOW_H diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 759d5383..e1d5502a 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1,7 +1,7 @@ - MainWindow - + CentralWidget + 0