├── .gitignore ├── data ├── preview.png ├── window.png └── full-window.png ├── .vscode └── settings.json ├── src ├── libdbusmenuqt │ ├── README │ ├── test │ │ ├── CMakeLists.txt │ │ ├── README │ │ └── main.cpp │ ├── CMakeLists.txt │ ├── utils_p.h │ ├── dbusmenushortcut_p.h │ ├── utils.cpp │ ├── com.canonical.dbusmenu.xml │ ├── dbusmenutypes_p.h │ ├── dbusmenushortcut_p.cpp │ ├── dbusmenuimporter.h │ ├── dbusmenutypes_p.cpp │ └── dbusmenuimporter.cpp ├── BuildConfig.h.cmake ├── InternalSettings.kcfgc ├── material.json ├── BoxShadowHelper.h ├── plugin.cc ├── MenuOverflowButton.h ├── AppMenuButton.h ├── ConfigurationModule.h ├── Material.h ├── MinimizeButton.h ├── MenuOverflowButton.cc ├── ApplicationMenuButton.h ├── CloseButton.h ├── TextButton.h ├── OnAllDesktopsButton.h ├── KeepAboveButton.h ├── KeepBelowButton.h ├── MaximizeButton.h ├── ContextHelpButton.h ├── AppIconButton.h ├── ShadeButton.h ├── InternalSettingsSchema.kcfg ├── AppMenuButton.cc ├── AppMenuModel.h ├── TextButton.cc ├── CMakeLists.txt ├── Button.h ├── AppMenuButtonGroup.h ├── Decoration.h ├── BoxShadowHelper.cc ├── ConfigurationModule.cc ├── AppMenuModel.cc ├── Button.cc └── AppMenuButtonGroup.cc ├── CMakeLists.txt ├── README.md ├── .clang-format └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /data/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zren/material-decoration/HEAD/data/preview.png -------------------------------------------------------------------------------- /data/window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zren/material-decoration/HEAD/data/window.png -------------------------------------------------------------------------------- /data/full-window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zren/material-decoration/HEAD/data/full-window.png -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "array": "cpp", 4 | "string_view": "cpp" 5 | } 6 | } -------------------------------------------------------------------------------- /src/libdbusmenuqt/README: -------------------------------------------------------------------------------- 1 | Contains a patched version of the import path of libdbusmenu-qt 2 | Remove when next version of libdbusmenu-qt is released. -------------------------------------------------------------------------------- /src/BuildConfig.h.cmake: -------------------------------------------------------------------------------- 1 | #cmakedefine01 HAVE_Wayland 2 | #cmakedefine01 HAVE_X11 3 | #cmakedefine01 HAVE_KDecoration2_5_25 4 | #cmakedefine01 HAVE_KF5_101 5 | -------------------------------------------------------------------------------- /src/libdbusmenuqt/test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(appmenutest main.cpp) 2 | target_link_libraries(appmenutest 3 | Qt${QT_VERSION_MAJOR}::Widgets 4 | ) 5 | -------------------------------------------------------------------------------- /src/InternalSettings.kcfgc: -------------------------------------------------------------------------------- 1 | File=InternalSettingsSchema.kcfg 2 | ClassName=InternalSettings 3 | NameSpace=Material 4 | Singleton=false 5 | Mutators=true 6 | GlobalEnums=true 7 | -------------------------------------------------------------------------------- /src/libdbusmenuqt/test/README: -------------------------------------------------------------------------------- 1 | App with a menu, designed for use testing appmenu QPTs/applets/kded modules 2 | small enough that we can attach debuggers and breakpoints without drowning in data 3 | -------------------------------------------------------------------------------- /src/material.json: -------------------------------------------------------------------------------- 1 | { 2 | "KPlugin": { 3 | "Description": "Window decoration", 4 | "EnabledByDefault": false, 5 | "Id": "com.github.zren.material", 6 | "Name": "Material", 7 | "ServiceTypes": [ 8 | "org.kde.kdecoration2" 9 | ] 10 | }, 11 | "org.kde.kdecoration2": { 12 | "blur": true, 13 | "kcmodule": true, 14 | "recommendedBorderSize": "None" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/libdbusmenuqt/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(libdbusmenu_SRCS 2 | dbusmenuimporter.cpp 3 | dbusmenushortcut_p.cpp 4 | dbusmenutypes_p.cpp 5 | utils.cpp 6 | ) 7 | 8 | ecm_qt_declare_logging_category(libdbusmenu_SRCS 9 | HEADER debug.h 10 | IDENTIFIER DBUSMENUQT 11 | CATEGORY_NAME org.kde.libdbusmenuqt 12 | DEFAULT_SEVERITY Info 13 | ) 14 | 15 | set_source_files_properties(com.canonical.dbusmenu.xml PROPERTIES 16 | NO_NAMESPACE true 17 | INCLUDE "dbusmenutypes_p.h" 18 | CLASSNAME DBusMenuInterface 19 | ) 20 | 21 | if (Qt6_FOUND) 22 | qt_add_dbus_interface(libdbusmenu_SRCS com.canonical.dbusmenu.xml dbusmenu_interface) 23 | elseif(Qt5_FOUND) 24 | qt5_add_dbus_interface(libdbusmenu_SRCS com.canonical.dbusmenu.xml dbusmenu_interface) 25 | endif() 26 | 27 | 28 | 29 | add_library(dbusmenuqt STATIC ${libdbusmenu_SRCS}) 30 | target_link_libraries(dbusmenuqt 31 | Qt${QT_VERSION_MAJOR}::DBus 32 | Qt${QT_VERSION_MAJOR}::Widgets 33 | ) 34 | 35 | add_subdirectory(test) 36 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.16.0) 2 | project (material-decoration) 3 | 4 | add_definitions (-Wall -Werror) 5 | 6 | include (FeatureSummary) 7 | find_package (ECM 0.0.9 REQUIRED NO_MODULE) 8 | 9 | set (CMAKE_MODULE_PATH 10 | ${CMAKE_MODULE_PATH} 11 | ${ECM_MODULE_PATH} 12 | ) 13 | 14 | include (ECMInstallIcons) 15 | include (KDEInstallDirs) 16 | include (KDECMakeSettings) 17 | include (KDECompilerSettings NO_POLICY_SCOPE) 18 | 19 | # set(QT_MIN_VERSION "5.9.0") 20 | # https://doc.qt.io/qt-6/cmake-qt5-and-qt6-compatibility.html#supporting-older-qt-5-versions 21 | # find_package(QT NAMES Qt6 Qt5) 22 | find_package(QT NAMES Qt5) 23 | # find_package(QT NAMES Qt6) 24 | find_package(Qt${QT_VERSION_MAJOR} CONFIG REQUIRED COMPONENTS 25 | Widgets 26 | DBus 27 | ) 28 | include(ECMQtDeclareLoggingCategory) 29 | 30 | # Remove Qt 5.15 Deprecations 31 | # https://doc.qt.io/qt-6/portingguide.html 32 | add_compile_definitions(QT_DISABLE_DEPRECATED_BEFORE=0x050F00) 33 | 34 | add_subdirectory (src/libdbusmenuqt) 35 | add_subdirectory (src) 36 | 37 | feature_summary(WHAT ALL) 38 | -------------------------------------------------------------------------------- /src/BoxShadowHelper.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Vlad Zagorodniy 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #pragma once 19 | 20 | // Qt 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | namespace Material 27 | { 28 | namespace BoxShadowHelper 29 | { 30 | 31 | void boxShadow(QPainter *p, const QRect &box, const QPoint &offset, 32 | int radius, const QColor &color); 33 | 34 | } // namespace BoxShadowHelper 35 | } // namespace Material 36 | -------------------------------------------------------------------------------- /src/plugin.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Vlad Zagorodniy 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | // own 19 | #include "Decoration.h" 20 | #include "Button.h" 21 | #include "ConfigurationModule.h" 22 | 23 | // KF 24 | #include 25 | 26 | K_PLUGIN_FACTORY_WITH_JSON( 27 | MaterialDecorationFactory, 28 | "material.json", 29 | registerPlugin(); 30 | registerPlugin(); 31 | registerPlugin(); 32 | ); 33 | 34 | #include "plugin.moc" 35 | -------------------------------------------------------------------------------- /src/libdbusmenuqt/utils_p.h: -------------------------------------------------------------------------------- 1 | /* This file is part of the dbusmenu-qt library 2 | Copyright 2010 Canonical 3 | Author: Aurelien Gateau 4 | 5 | This library is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU Library General Public 7 | License (LGPL) as published by the Free Software Foundation; 8 | either version 2 of the License, or (at your option) any later 9 | version. 10 | 11 | This library is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | Library General Public License for more details. 15 | 16 | You should have received a copy of the GNU Library General Public License 17 | along with this library; see the file COPYING.LIB. If not, write to 18 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 | Boston, MA 02110-1301, USA. 20 | */ 21 | #ifndef UTILS_P_H 22 | #define UTILS_P_H 23 | 24 | class QString; 25 | 26 | /** 27 | * Swap mnemonic char: Qt uses '&', while dbusmenu uses '_' 28 | */ 29 | QString swapMnemonicChar(const QString &in, const char src, const char dst); 30 | 31 | #endif /* UTILS_P_H */ 32 | -------------------------------------------------------------------------------- /src/MenuOverflowButton.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Chris Holland 3 | * Copyright (C) 2019 Zain Ahmad 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | // own 22 | #include "AppMenuButton.h" 23 | 24 | namespace Material 25 | { 26 | 27 | class Decoration; 28 | 29 | class MenuOverflowButton : public AppMenuButton 30 | { 31 | Q_OBJECT 32 | 33 | public: 34 | MenuOverflowButton(Decoration *decoration, const int buttonIndex, QObject *parent = nullptr); 35 | ~MenuOverflowButton() override; 36 | 37 | void paintIcon(QPainter *painter, const QRectF &iconRect, const qreal gridUnit) override; 38 | }; 39 | 40 | } // namespace Material 41 | -------------------------------------------------------------------------------- /src/libdbusmenuqt/dbusmenushortcut_p.h: -------------------------------------------------------------------------------- 1 | /* This file is part of the dbusmenu-qt library 2 | Copyright 2009 Canonical 3 | Author: Aurelien Gateau 4 | 5 | This library is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU Library General Public 7 | License (LGPL) as published by the Free Software Foundation; 8 | either version 2 of the License, or (at your option) any later 9 | version. 10 | 11 | This library is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | Library General Public License for more details. 15 | 16 | You should have received a copy of the GNU Library General Public License 17 | along with this library; see the file COPYING.LIB. If not, write to 18 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 | Boston, MA 02110-1301, USA. 20 | */ 21 | #ifndef DBUSMENUSHORTCUT_H 22 | #define DBUSMENUSHORTCUT_H 23 | 24 | // Qt 25 | #include 26 | #include 27 | 28 | class QKeySequence; 29 | 30 | class DBusMenuShortcut : public QList 31 | { 32 | public: 33 | QKeySequence toKeySequence() const; 34 | static DBusMenuShortcut fromKeySequence(const QKeySequence &); 35 | }; 36 | 37 | Q_DECLARE_METATYPE(DBusMenuShortcut) 38 | 39 | #endif /* DBUSMENUSHORTCUT_H */ 40 | -------------------------------------------------------------------------------- /src/AppMenuButton.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Chris Holland 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #pragma once 19 | 20 | // own 21 | #include "Button.h" 22 | 23 | namespace Material 24 | { 25 | 26 | class Decoration; 27 | 28 | class AppMenuButton : public Button 29 | { 30 | Q_OBJECT 31 | 32 | public: 33 | AppMenuButton(Decoration *decoration, const int buttonIndex, QObject *parent = nullptr); 34 | ~AppMenuButton() override; 35 | 36 | Q_PROPERTY(int buttonIndex READ buttonIndex NOTIFY buttonIndexChanged) 37 | 38 | int buttonIndex() const; 39 | 40 | QColor backgroundColor() const override; 41 | QColor foregroundColor() const override; 42 | 43 | signals: 44 | void buttonIndexChanged(); 45 | 46 | public slots: 47 | virtual void trigger(); 48 | 49 | private: 50 | int m_buttonIndex; 51 | }; 52 | 53 | } // namespace Material 54 | -------------------------------------------------------------------------------- /src/ConfigurationModule.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Chris Holland 3 | * Copyright (C) 2012 Martin Gräßlin 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | // KF 20 | #include 21 | 22 | // Qt 23 | #include 24 | #include 25 | 26 | namespace Material 27 | { 28 | 29 | class ConfigurationModule : public KCModule 30 | { 31 | Q_OBJECT 32 | public: 33 | ConfigurationModule(QWidget *parent, const QVariantList &args); 34 | 35 | private: 36 | void init(); 37 | 38 | int m_titleAlignment; 39 | int m_buttonSize; 40 | double m_activeOpacity; 41 | double m_inactiveOpacity; 42 | bool m_menuAlwaysShow; 43 | int m_menuButtonHorzPadding; 44 | bool m_animationsEnabled; 45 | int m_animationsDuration; 46 | int m_shadowSize; 47 | int m_shadowStrength; 48 | QColor m_shadowColor; 49 | }; 50 | 51 | } // namespace Material 52 | -------------------------------------------------------------------------------- /src/Material.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Chris Holland 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #pragma once 19 | 20 | // Qt 21 | #include 22 | 23 | 24 | namespace Material 25 | { 26 | static const QLoggingCategory category("kdecoration.material"); 27 | static const QString s_configFilename = QStringLiteral("kdecoration_materialrc"); 28 | 29 | //--- Standard pen widths 30 | namespace PenWidth 31 | { 32 | /* https://github.com/KDE/breeze/blob/master/kstyle/breeze.h#L164 33 | * Using 1 instead of slightly more than 1 causes symbols drawn with 34 | * pen strokes to look skewed. The exact amount added does not matter 35 | * as long as it isn't too visible. 36 | */ 37 | // The standard pen stroke width for symbols. 38 | static constexpr qreal Symbol = 1.01; 39 | } 40 | 41 | } // namespace Material 42 | -------------------------------------------------------------------------------- /src/MinimizeButton.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Vlad Zagorodniy 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #pragma once 19 | 20 | // own 21 | #include "Button.h" 22 | 23 | // KDecoration 24 | #include 25 | 26 | // Qt 27 | #include 28 | 29 | namespace Material 30 | { 31 | 32 | class MinimizeButton 33 | { 34 | 35 | public: 36 | static void init(Button *button, KDecoration2::DecoratedClient *decoratedClient) { 37 | QObject::connect(decoratedClient, &KDecoration2::DecoratedClient::minimizeableChanged, 38 | button, &Button::setVisible); 39 | 40 | button->setVisible(decoratedClient->isMinimizeable()); 41 | } 42 | static void paintIcon(Button *button, QPainter *painter, const QRectF &iconRect, const qreal gridUnit) { 43 | Q_UNUSED(button) 44 | Q_UNUSED(gridUnit) 45 | 46 | painter->drawLine( 47 | iconRect.left(), iconRect.center().y(), 48 | iconRect.right(), iconRect.center().y()); 49 | } 50 | }; 51 | 52 | } // namespace Material 53 | -------------------------------------------------------------------------------- /src/MenuOverflowButton.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Chris Holland 3 | * Copyright (C) 2019 Zain Ahmad 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | // own 20 | #include "MenuOverflowButton.h" 21 | #include "Material.h" 22 | #include "AppMenuButton.h" 23 | #include "Decoration.h" 24 | #include "ApplicationMenuButton.h" 25 | 26 | // KDecoration 27 | #include 28 | 29 | // Qt 30 | #include 31 | #include 32 | 33 | 34 | namespace Material 35 | { 36 | 37 | MenuOverflowButton::MenuOverflowButton(Decoration *decoration, const int buttonIndex, QObject *parent) 38 | : AppMenuButton(decoration, buttonIndex, parent) 39 | { 40 | auto *decoratedClient = decoration->client().toStrongRef().data(); 41 | 42 | setVisible(decoratedClient->hasApplicationMenu()); 43 | } 44 | 45 | MenuOverflowButton::~MenuOverflowButton() 46 | { 47 | } 48 | 49 | void MenuOverflowButton::paintIcon(QPainter *painter, const QRectF &iconRect, const qreal gridUnit) 50 | { 51 | ApplicationMenuButton::paintIcon(this, painter, iconRect, gridUnit); 52 | } 53 | 54 | } // namespace Material 55 | -------------------------------------------------------------------------------- /src/ApplicationMenuButton.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Zain Ahmad 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #pragma once 19 | 20 | // own 21 | #include "Button.h" 22 | #include "Material.h" 23 | 24 | // KDecoration 25 | #include 26 | 27 | // Qt 28 | #include 29 | 30 | namespace Material 31 | { 32 | 33 | class ApplicationMenuButton 34 | { 35 | 36 | public: 37 | static void init(Button *button, KDecoration2::DecoratedClient *decoratedClient) { 38 | button->setVisible(decoratedClient->hasApplicationMenu()); 39 | } 40 | static void paintIcon(Button *button, QPainter *painter, const QRectF &iconRect, const qreal gridUnit) { 41 | button->setPenWidth(painter, gridUnit, 1.75); 42 | 43 | int spacing = qRound(gridUnit * 4); 44 | for (int i = -1; i <= 1; ++i) { 45 | const QPointF left { iconRect.left(), iconRect.center().y() + i * spacing }; 46 | const QPointF right { iconRect.right(), iconRect.center().y() + i * spacing }; 47 | 48 | painter->drawLine(left, right); 49 | } 50 | } 51 | }; 52 | 53 | } // namespace Material 54 | -------------------------------------------------------------------------------- /src/CloseButton.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Vlad Zagorodniy 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #pragma once 19 | 20 | // own 21 | #include "Button.h" 22 | 23 | // KDecoration 24 | #include 25 | 26 | // Qt 27 | #include 28 | 29 | namespace Material 30 | { 31 | 32 | class CloseButton 33 | { 34 | 35 | public: 36 | static void init(Button *button, KDecoration2::DecoratedClient *decoratedClient) { 37 | QObject::connect(decoratedClient, &KDecoration2::DecoratedClient::closeableChanged, 38 | button, &Button::setVisible); 39 | 40 | button->setVisible(decoratedClient->isCloseable()); 41 | } 42 | static void paintIcon(Button *button, QPainter *painter, const QRectF &iconRect, const qreal gridUnit) { 43 | Q_UNUSED(button) 44 | Q_UNUSED(gridUnit) 45 | 46 | painter->setRenderHints(QPainter::Antialiasing, true); 47 | 48 | button->setPenWidth(painter, gridUnit, 1.10); 49 | 50 | painter->drawLine(iconRect.topLeft(), iconRect.bottomRight()); 51 | painter->drawLine(iconRect.topRight(), iconRect.bottomLeft()); 52 | } 53 | }; 54 | 55 | } // namespace Material 56 | -------------------------------------------------------------------------------- /src/TextButton.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Chris Holland 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #pragma once 19 | 20 | // own 21 | #include "AppMenuButton.h" 22 | 23 | // Qt 24 | #include 25 | 26 | namespace Material 27 | { 28 | 29 | class Decoration; 30 | 31 | class TextButton : public AppMenuButton 32 | { 33 | Q_OBJECT 34 | 35 | public: 36 | TextButton(Decoration *decoration, const int buttonIndex, QObject *parent = nullptr); 37 | ~TextButton() override; 38 | 39 | Q_PROPERTY(QAction* action READ action WRITE setAction NOTIFY actionChanged) 40 | Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged) 41 | 42 | void paintIcon(QPainter *painter, const QRectF &iconRect, const qreal gridUnit) override; 43 | QSize getTextSize(); 44 | 45 | 46 | QAction* action() const; 47 | void setAction(QAction *set); 48 | 49 | QString text() const; 50 | void setText(const QString set); 51 | 52 | void setHeight(int buttonHeight) override; 53 | void updateGeometry(); 54 | 55 | signals: 56 | void actionChanged(); 57 | void textChanged(); 58 | 59 | private: 60 | QAction *m_action; 61 | QString m_text; 62 | }; 63 | 64 | } // namespace Material 65 | -------------------------------------------------------------------------------- /src/OnAllDesktopsButton.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Chris Holland 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #pragma once 19 | 20 | // own 21 | #include "Button.h" 22 | #include "Material.h" 23 | 24 | // KDecoration 25 | #include 26 | 27 | // Qt 28 | #include 29 | 30 | namespace Material 31 | { 32 | 33 | class OnAllDesktopsButton 34 | { 35 | 36 | public: 37 | static void init(Button *button, KDecoration2::DecoratedClient *decoratedClient) { 38 | Q_UNUSED(decoratedClient) 39 | 40 | button->setVisible(true); 41 | } 42 | static void paintIcon(Button *button, QPainter *painter, const QRectF &iconRect, const qreal gridUnit) { 43 | Q_UNUSED(gridUnit) 44 | painter->setRenderHints(QPainter::Antialiasing, true); 45 | button->setPenWidth(painter, gridUnit, 1.25); 46 | 47 | int radius = qMin(iconRect.width(), iconRect.height()) / 2; 48 | QPoint center(iconRect.center().toPoint()); 49 | painter->drawPolygon( QVector { 50 | center + QPoint(-radius, 0), 51 | center + QPoint(0, -radius), 52 | center + QPoint(radius, 0), 53 | center + QPoint(0, radius) 54 | }); 55 | } 56 | }; 57 | 58 | } // namespace Material 59 | -------------------------------------------------------------------------------- /src/KeepAboveButton.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Zain Ahmad 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #pragma once 19 | 20 | // own 21 | #include "Button.h" 22 | #include "Material.h" 23 | 24 | // KDecoration 25 | #include 26 | 27 | // Qt 28 | #include 29 | 30 | namespace Material 31 | { 32 | 33 | class KeepAboveButton 34 | { 35 | 36 | public: 37 | static void init(Button *button, KDecoration2::DecoratedClient *decoratedClient) { 38 | Q_UNUSED(decoratedClient) 39 | 40 | button->setVisible(true); 41 | } 42 | static void paintIcon(Button *button, QPainter *painter, const QRectF &iconRect, const qreal gridUnit) { 43 | button->setPenWidth(painter, gridUnit, 1.25); 44 | 45 | painter->translate( iconRect.topLeft() ); 46 | painter->drawPolyline( QVector { 47 | QPointF( 0.5, 4.75 ) * gridUnit, 48 | QPointF( 5.0, 0.25 ) * gridUnit, 49 | QPointF( 9.5, 4.75 ) * gridUnit 50 | }); 51 | 52 | painter->drawPolyline( QVector { 53 | QPointF( 0.5, 9.75 ) * gridUnit, 54 | QPointF( 5.0, 5.25 ) * gridUnit, 55 | QPointF( 9.5, 9.75 ) * gridUnit 56 | }); 57 | } 58 | }; 59 | 60 | } // namespace Material 61 | -------------------------------------------------------------------------------- /src/KeepBelowButton.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Chris Holland 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #pragma once 19 | 20 | // own 21 | #include "Button.h" 22 | #include "Material.h" 23 | 24 | // KDecoration 25 | #include 26 | 27 | // Qt 28 | #include 29 | 30 | namespace Material 31 | { 32 | 33 | class KeepBelowButton 34 | { 35 | 36 | public: 37 | static void init(Button *button, KDecoration2::DecoratedClient *decoratedClient) { 38 | Q_UNUSED(decoratedClient) 39 | 40 | button->setVisible(true); 41 | } 42 | static void paintIcon(Button *button, QPainter *painter, const QRectF &iconRect, const qreal gridUnit) { 43 | button->setPenWidth(painter, gridUnit, 1.25); 44 | 45 | painter->translate( iconRect.topLeft() ); 46 | painter->drawPolyline( QVector { 47 | QPointF( 0.5, 0.25 ) * gridUnit, 48 | QPointF( 5.0, 4.75 ) * gridUnit, 49 | QPointF( 9.5, 0.25 ) * gridUnit 50 | }); 51 | 52 | painter->drawPolyline( QVector { 53 | QPointF( 0.5, 5.25 ) * gridUnit, 54 | QPointF( 5.0, 9.75 ) * gridUnit, 55 | QPointF( 9.5, 5.25 ) * gridUnit 56 | }); 57 | } 58 | }; 59 | 60 | } // namespace Material 61 | -------------------------------------------------------------------------------- /src/libdbusmenuqt/utils.cpp: -------------------------------------------------------------------------------- 1 | /* This file is part of the dbusmenu-qt library 2 | Copyright 2010 Canonical 3 | Author: Aurelien Gateau 4 | 5 | This library is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU Library General Public 7 | License (LGPL) as published by the Free Software Foundation; 8 | either version 2 of the License, or (at your option) any later 9 | version. 10 | 11 | This library is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | Library General Public License for more details. 15 | 16 | You should have received a copy of the GNU Library General Public License 17 | along with this library; see the file COPYING.LIB. If not, write to 18 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 | Boston, MA 02110-1301, USA. 20 | */ 21 | #include "utils_p.h" 22 | 23 | // Qt 24 | #include 25 | 26 | QString swapMnemonicChar(const QString &in, const char src, const char dst) 27 | { 28 | QString out; 29 | bool mnemonicFound = false; 30 | 31 | for (int pos = 0; pos < in.length();) { 32 | QChar ch = in[pos]; 33 | if (ch == src) { 34 | if (pos == in.length() - 1) { 35 | // 'src' at the end of string, skip it 36 | ++pos; 37 | } else { 38 | if (in[pos + 1] == src) { 39 | // A real 'src' 40 | out += src; 41 | pos += 2; 42 | } else if (!mnemonicFound) { 43 | // We found the mnemonic 44 | mnemonicFound = true; 45 | out += dst; 46 | ++pos; 47 | } else { 48 | // We already have a mnemonic, just skip the char 49 | ++pos; 50 | } 51 | } 52 | } else if (ch == dst) { 53 | // Escape 'dst' 54 | out += dst; 55 | out += dst; 56 | ++pos; 57 | } else { 58 | out += ch; 59 | ++pos; 60 | } 61 | } 62 | 63 | return out; 64 | } 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Demo](data/preview.png) 2 | 3 | ## material-decoration 4 | 5 | Material-ish window decoration theme for KWin. 6 | 7 | ### Locally Integrated Menus 8 | 9 | This hides the AppMenu icon button and draws the menu in the titlebar. 10 | 11 | ![](https://i.imgur.com/oFOVWjV.png) 12 | 13 | Make sure you add the AppMenu button in System Settings > Application Style > Window Decorations > Buttons Tab. 14 | 15 | TODO/Bugs ([Issue #1](https://github.com/Zren/material-decoration/issues/1)): 16 | 17 | * Open Submenu on Shortcut (eg: `Alt+F`) 18 | * Display mnemonics when holding `Alt` 19 | 20 | Upstream LIM discussion in the KDE Bug report: https://bugs.kde.org/show_bug.cgi?id=375951#c27 21 | 22 | ### Installation 23 | 24 | #### Binary package 25 | 26 | - Arch/Manjaro (AUR): 27 | Install the `material-kwin-decoration-git` AUR package. 28 | https://aur.archlinux.org/packages/material-kwin-decoration-git/ 29 | 30 | - openSUSE: 31 | https://build.opensuse.org/package/show/home:trmdi/material-decoration 32 | ``` 33 | sudo zypper ar obs://home:trmdi trmdi 34 | sudo zypper in -r trmdi material-decoration 35 | ``` 36 | 37 | #### Building from source 38 | Build dependencies: 39 | 40 | - Ubuntu: 41 | ``` 42 | sudo apt build-dep breeze 43 | sudo apt build-dep kwin 44 | ``` 45 | 46 | 47 | Download the source: 48 | 49 | ``` 50 | cd ~/Downloads 51 | git clone https://github.com/Zren/material-decoration.git 52 | cd material-decoration 53 | ``` 54 | 55 | Then compile the decoration, and install it: 56 | 57 | ``` 58 | mkdir build 59 | cd build 60 | cmake -DCMAKE_INSTALL_PREFIX=/usr .. 61 | make 62 | sudo make install 63 | ``` 64 | 65 | Select Material in System Settings > Application Style > Window Decorations. 66 | 67 | To test changes, restart `kwin_x11` with: 68 | 69 | ``` 70 | QT_LOGGING_RULES="*=false;kdecoration.material=true" kstart5 -- kwin_x11 --replace 71 | ``` 72 | 73 | ### Update 74 | 75 | On 2020 June 18, the kdecoration id was changed from `zzag` to `zren`. You will need to re-select `Material` in System Settings > Application Style > Window Decoration. KWin will fallback to `Breeze` if you forget to do this. 76 | 77 | #### Building from source 78 | 79 | First navigate to the source directory, and `git pull` recent changes. 80 | 81 | ``` 82 | cd ~/Downloads/material-decoration 83 | git pull origin master --ff-only 84 | ``` 85 | 86 | Then re-run the install instructions. 87 | -------------------------------------------------------------------------------- /src/libdbusmenuqt/com.canonical.dbusmenu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/libdbusmenuqt/test/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 David Edmundson 3 | * This program is free software; you can redistribute it and/or modify 4 | * it under the terms of the GNU Library General Public License as 5 | * published by the Free Software Foundation; either version 2, or 6 | * (at your option) any later version. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details 12 | * 13 | * You should have received a copy of the GNU Library General Public 14 | * License along with this program; if not, write to the 15 | * Free Software Foundation, Inc., 16 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | */ 18 | 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | class MainWindow : public QMainWindow 28 | { 29 | public: 30 | MainWindow(); 31 | }; 32 | 33 | MainWindow::MainWindow() 34 | : QMainWindow() 35 | { 36 | /*set an initial menu with the following 37 | Menu A 38 | - Item 39 | - Checkable Item 40 | - Item With Icon 41 | - A separator 42 | - Menu B 43 | - Item B1 44 | Menu C 45 | - DynamicItem ${timestamp} 46 | 47 | TopLevelItem 48 | */ 49 | 50 | QAction *t; 51 | auto menuA = new QMenu("Menu A", this); 52 | menuA->addAction("Item"); 53 | 54 | t = menuA->addAction("Checkable Item"); 55 | t->setCheckable(true); 56 | 57 | t = menuA->addAction(QIcon::fromTheme("document-edit"), "Item with icon"); 58 | 59 | menuA->addSeparator(); 60 | 61 | auto menuB = new QMenu("Menu B", this); 62 | menuB->addAction("Item B1"); 63 | menuA->addMenu(menuB); 64 | 65 | menuBar()->addMenu(menuA); 66 | 67 | auto menuC = new QMenu("Menu C", this); 68 | connect(menuC, &QMenu::aboutToShow, this, [menuC]() { 69 | menuC->clear(); 70 | menuC->addAction("Dynamic Item " + QDateTime::currentDateTime().toString()); 71 | }); 72 | 73 | menuBar()->addMenu(menuC); 74 | 75 | menuBar()->addAction("Top Level Item"); 76 | } 77 | 78 | int main(int argc, char **argv) 79 | { 80 | QApplication app(argc, argv); 81 | MainWindow mw; 82 | mw.show(); 83 | return app.exec(); 84 | } 85 | -------------------------------------------------------------------------------- /src/MaximizeButton.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Vlad Zagorodniy 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #pragma once 19 | 20 | // own 21 | #include "Button.h" 22 | 23 | // KDecoration 24 | #include 25 | 26 | // Qt 27 | #include 28 | 29 | namespace Material 30 | { 31 | 32 | class MaximizeButton 33 | { 34 | 35 | public: 36 | static void init(Button *button, KDecoration2::DecoratedClient *decoratedClient) { 37 | QObject::connect(decoratedClient, &KDecoration2::DecoratedClient::maximizeableChanged, 38 | button, &Button::setVisible); 39 | 40 | button->setVisible(decoratedClient->isMaximizeable()); 41 | } 42 | static void paintIcon(Button *button, QPainter *painter, const QRectF &iconRect, qreal gridUnit) { 43 | if (button->isChecked()) { 44 | const int offset = qRound(gridUnit * 2); 45 | // Outline of first square, "on top", aligned bottom left. 46 | painter->drawPolygon(QVector { 47 | iconRect.bottomLeft(), 48 | iconRect.topLeft() + QPointF(0, offset), 49 | iconRect.topRight() + QPointF(-offset, offset), 50 | iconRect.bottomRight() + QPointF(-offset, 0) 51 | }); 52 | 53 | // Partially occluded square, "below" first square, aligned top right. 54 | painter->drawPolyline(QVector { 55 | iconRect.topLeft() + QPointF(offset, offset), 56 | iconRect.topLeft() + QPointF(offset, 0), 57 | iconRect.topRight(), 58 | iconRect.bottomRight() + QPointF(0, -offset), 59 | iconRect.bottomRight() + QPointF(-offset, -offset) 60 | }); 61 | } else { 62 | painter->drawRect(iconRect); 63 | } 64 | } 65 | }; 66 | 67 | } // namespace Material 68 | -------------------------------------------------------------------------------- /src/ContextHelpButton.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 Chris Holland 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #pragma once 19 | 20 | // own 21 | #include "Button.h" 22 | #include "Material.h" 23 | 24 | // KDecoration 25 | #include 26 | 27 | // Qt 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | namespace Material 35 | { 36 | 37 | class ContextHelpButton 38 | { 39 | 40 | public: 41 | static void init(Button *button, KDecoration2::DecoratedClient *decoratedClient) { 42 | QObject::connect(decoratedClient, &KDecoration2::DecoratedClient::providesContextHelpChanged, 43 | button, &Button::setVisible); 44 | 45 | button->setVisible(decoratedClient->providesContextHelp()); 46 | } 47 | static void paintIcon(Button *button, QPainter *painter, const QRectF &iconRect, const qreal gridUnit) { 48 | button->setPenWidth(painter, gridUnit, 1.25); 49 | 50 | painter->setRenderHints(QPainter::Antialiasing, true); 51 | painter->translate( iconRect.topLeft() ); 52 | 53 | const QRectF topCurveRect = QRectF( 54 | QPointF( 1.5, 0.5 ) * gridUnit, 55 | QSizeF( 8, 6 ) * gridUnit 56 | ); 57 | QPainterPath path; 58 | path.moveTo( topCurveRect.center() - QPointF(topCurveRect.width()/2, 0) ); 59 | path.arcTo( 60 | topCurveRect, 61 | 180, 62 | -180 63 | ); 64 | path.cubicTo( 65 | QPointF( 7.8125, 5.9375 ) * gridUnit, 66 | QPointF( 5.625, 4.6875 ) * gridUnit, 67 | QPointF( 5, 8 ) * gridUnit 68 | ); 69 | painter->drawPath(path); 70 | 71 | // Dot 72 | painter->drawRect( QRectF( 73 | QPointF( 5, 10 ) * gridUnit, 74 | QSizeF( 0.5, 0.5 ) * gridUnit 75 | )); 76 | } 77 | }; 78 | 79 | } // namespace Material 80 | -------------------------------------------------------------------------------- /src/AppIconButton.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Chris Holland 3 | * Copyright (C) 2018 Vlad Zagorodniy 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | // own 22 | #include "Button.h" 23 | #include "Decoration.h" 24 | 25 | // KDecoration 26 | #include 27 | 28 | // KF 29 | #include 30 | 31 | // Qt 32 | #include 33 | #include 34 | 35 | namespace Material 36 | { 37 | 38 | class AppIconButton 39 | { 40 | 41 | public: 42 | static void init(Button *button, KDecoration2::DecoratedClient *decoratedClient) { 43 | QObject::connect(decoratedClient, &KDecoration2::DecoratedClient::iconChanged, 44 | button, [button] { 45 | button->update(); 46 | } 47 | ); 48 | } 49 | static void paintIcon(Button *button, QPainter *painter, const QRectF &iconRect, const qreal gridUnit) { 50 | Q_UNUSED(iconRect) 51 | 52 | const QRectF contentRect = button->contentArea(); 53 | int appIconSize = qMax(16, qRound(gridUnit * 16)); 54 | QRectF appIconRect = QRectF(0, 0, appIconSize, appIconSize); 55 | appIconRect.moveCenter(contentRect.center().toPoint()); 56 | 57 | const auto *deco = qobject_cast(button->decoration()); 58 | auto *decoratedClient = deco->client().toStrongRef().data(); 59 | 60 | const QPalette activePalette = KIconLoader::global()->customPalette(); 61 | QPalette palette = decoratedClient->palette(); 62 | palette.setColor(QPalette::WindowText, deco->titleBarForegroundColor()); 63 | KIconLoader::global()->setCustomPalette(palette); 64 | decoratedClient->icon().paint(painter, appIconRect.toRect()); 65 | if (activePalette == QPalette()) { 66 | KIconLoader::global()->resetPalette(); 67 | } else { 68 | KIconLoader::global()->setCustomPalette(palette); 69 | } 70 | } 71 | }; 72 | 73 | } // namespace Material 74 | -------------------------------------------------------------------------------- /src/ShadeButton.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 Chris Holland 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #pragma once 19 | 20 | // own 21 | #include "Button.h" 22 | #include "Material.h" 23 | 24 | // KDecoration 25 | #include 26 | 27 | // Qt 28 | #include 29 | 30 | namespace Material 31 | { 32 | 33 | class ShadeButton 34 | { 35 | 36 | public: 37 | static void init(Button *button, KDecoration2::DecoratedClient *decoratedClient) { 38 | QObject::connect(decoratedClient, &KDecoration2::DecoratedClient::shadeableChanged, 39 | button, &Button::setVisible); 40 | 41 | button->setVisible(decoratedClient->isShadeable()); 42 | } 43 | static void paintIcon(Button *button, QPainter *painter, const QRectF &iconRect, const qreal gridUnit) { 44 | painter->translate( iconRect.topLeft() ); 45 | 46 | if (button->isChecked()) { 47 | button->setPenWidth(painter, gridUnit, 1.0); 48 | painter->drawLine( 49 | QPointF( 0, 2 ) * gridUnit, 50 | QPointF( 10, 2 ) * gridUnit 51 | ); 52 | button->setPenWidth(painter, gridUnit, 1.25); 53 | painter->drawPolyline( QVector { 54 | QPointF( 0.5, 5.25 ) * gridUnit, 55 | QPointF( 5.0, 9.75 ) * gridUnit, 56 | QPointF( 9.5, 5.25 ) * gridUnit 57 | }); 58 | } else { 59 | button->setPenWidth(painter, gridUnit, 1.0); 60 | painter->drawLine( 61 | QPointF( 0, 2 ) * gridUnit, 62 | QPointF( 10, 2 ) * gridUnit 63 | ); 64 | button->setPenWidth(painter, gridUnit, 1.25); 65 | painter->drawPolyline( QVector { 66 | QPointF( 0.5, 9.75 ) * gridUnit, 67 | QPointF( 5.0, 5.25 ) * gridUnit, 68 | QPointF( 9.5, 9.75 ) * gridUnit 69 | }); 70 | } 71 | } 72 | }; 73 | 74 | } // namespace Material 75 | -------------------------------------------------------------------------------- /src/InternalSettingsSchema.kcfg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | ButtonDefault 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | AlignCenterFullWidth 29 | 30 | 31 | 32 | 33 | 0.75 34 | 35 | 36 | 0.85 37 | 38 | 39 | 40 | 41 | true 42 | 43 | 44 | 4 45 | 46 | 47 | 48 | 49 | true 50 | 51 | 52 | 250 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | ShadowVeryLarge 65 | 66 | 67 | 33, 33, 33 68 | 69 | 70 | 255 71 | 25 72 | 255 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/libdbusmenuqt/dbusmenutypes_p.h: -------------------------------------------------------------------------------- 1 | /* This file is part of the dbusmenu-qt library 2 | Copyright 2009 Canonical 3 | Author: Aurelien Gateau 4 | 5 | This library is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU Library General Public 7 | License (LGPL) as published by the Free Software Foundation; 8 | either version 2 of the License, or (at your option) any later 9 | version. 10 | 11 | This library is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | Library General Public License for more details. 15 | 16 | You should have received a copy of the GNU Library General Public License 17 | along with this library; see the file COPYING.LIB. If not, write to 18 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 | Boston, MA 02110-1301, USA. 20 | */ 21 | #ifndef DBUSMENUTYPES_P_H 22 | #define DBUSMENUTYPES_P_H 23 | 24 | // Qt 25 | #include 26 | #include 27 | #include 28 | 29 | class QDBusArgument; 30 | 31 | //// DBusMenuItem 32 | /** 33 | * Internal struct used to communicate on DBus 34 | */ 35 | struct DBusMenuItem { 36 | int id; 37 | QVariantMap properties; 38 | }; 39 | 40 | Q_DECLARE_METATYPE(DBusMenuItem) 41 | 42 | QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuItem &item); 43 | const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuItem &item); 44 | 45 | typedef QList DBusMenuItemList; 46 | 47 | Q_DECLARE_METATYPE(DBusMenuItemList) 48 | 49 | //// DBusMenuItemKeys 50 | /** 51 | * Represents a list of keys for a menu item 52 | */ 53 | struct DBusMenuItemKeys { 54 | int id; 55 | QStringList properties; 56 | }; 57 | 58 | Q_DECLARE_METATYPE(DBusMenuItemKeys) 59 | 60 | QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuItemKeys &); 61 | const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuItemKeys &); 62 | 63 | typedef QList DBusMenuItemKeysList; 64 | 65 | Q_DECLARE_METATYPE(DBusMenuItemKeysList) 66 | 67 | //// DBusMenuLayoutItem 68 | /** 69 | * Represents an item with its children. GetLayout() returns a 70 | * DBusMenuLayoutItemList. 71 | */ 72 | struct DBusMenuLayoutItem; 73 | struct DBusMenuLayoutItem { 74 | int id; 75 | QVariantMap properties; 76 | QList children; 77 | }; 78 | 79 | Q_DECLARE_METATYPE(DBusMenuLayoutItem) 80 | 81 | QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuLayoutItem &); 82 | const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuLayoutItem &); 83 | 84 | typedef QList DBusMenuLayoutItemList; 85 | 86 | Q_DECLARE_METATYPE(DBusMenuLayoutItemList) 87 | 88 | //// DBusMenuShortcut 89 | 90 | class DBusMenuShortcut; 91 | 92 | QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuShortcut &); 93 | const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuShortcut &); 94 | 95 | void DBusMenuTypes_register(); 96 | #endif /* DBUSMENUTYPES_P_H */ 97 | -------------------------------------------------------------------------------- /src/AppMenuButton.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Chris Holland 3 | * Copyright (C) 2016 Kai Uwe Broulik 4 | * Copyright (C) 2014 by Hugo Pereira Da Costa 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | // own 21 | #include "AppMenuButton.h" 22 | #include "Material.h" 23 | #include "Button.h" 24 | #include "Decoration.h" 25 | #include "AppMenuButtonGroup.h" 26 | 27 | // KDecoration 28 | #include 29 | 30 | // KF 31 | #include 32 | 33 | // Qt 34 | #include 35 | 36 | 37 | namespace Material 38 | { 39 | 40 | AppMenuButton::AppMenuButton(Decoration *decoration, const int buttonIndex, QObject *parent) 41 | : Button(KDecoration2::DecorationButtonType::Custom, decoration, parent) 42 | , m_buttonIndex(buttonIndex) 43 | { 44 | setCheckable(true); 45 | 46 | connect(this, &AppMenuButton::clicked, 47 | this, &AppMenuButton::trigger); 48 | 49 | const auto *buttonGroup = qobject_cast(parent); 50 | if (buttonGroup) { 51 | setOpacity(buttonGroup->opacity()); 52 | } 53 | } 54 | 55 | AppMenuButton::~AppMenuButton() 56 | { 57 | } 58 | 59 | int AppMenuButton::buttonIndex() const 60 | { 61 | return m_buttonIndex; 62 | } 63 | 64 | QColor AppMenuButton::backgroundColor() const 65 | { 66 | const auto *buttonGroup = qobject_cast(parent()); 67 | if (buttonGroup 68 | && buttonGroup->isMenuOpen() 69 | && buttonGroup->currentIndex() != m_buttonIndex 70 | ) { 71 | return Qt::transparent; 72 | } else { 73 | return Button::backgroundColor(); 74 | } 75 | } 76 | 77 | QColor AppMenuButton::foregroundColor() const 78 | { 79 | const auto *buttonGroup = qobject_cast(parent()); 80 | if (buttonGroup 81 | && buttonGroup->isMenuOpen() 82 | && buttonGroup->currentIndex() != m_buttonIndex 83 | ) { 84 | const auto *deco = qobject_cast(decoration()); 85 | if (!deco) { 86 | return {}; 87 | } 88 | return KColorUtils::mix( 89 | deco->titleBarBackgroundColor(), 90 | deco->titleBarForegroundColor(), 91 | 0.8); 92 | } else { 93 | return Button::foregroundColor(); 94 | } 95 | } 96 | 97 | void AppMenuButton::trigger() { 98 | // qCDebug(category) << "AppMenuButton::trigger" << m_buttonIndex; 99 | 100 | auto *buttonGroup = qobject_cast(parent()); 101 | buttonGroup->trigger(m_buttonIndex); 102 | } 103 | 104 | } // namespace Material 105 | -------------------------------------------------------------------------------- /src/libdbusmenuqt/dbusmenushortcut_p.cpp: -------------------------------------------------------------------------------- 1 | /* This file is part of the dbusmenu-qt library 2 | Copyright 2009 Canonical 3 | Author: Aurelien Gateau 4 | 5 | This library is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU Library General Public 7 | License (LGPL) as published by the Free Software Foundation; 8 | either version 2 of the License, or (at your option) any later 9 | version. 10 | 11 | This library is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | Library General Public License for more details. 15 | 16 | You should have received a copy of the GNU Library General Public License 17 | along with this library; see the file COPYING.LIB. If not, write to 18 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 | Boston, MA 02110-1301, USA. 20 | */ 21 | #include "dbusmenushortcut_p.h" 22 | 23 | // Qt 24 | #include 25 | 26 | static const int QT_COLUMN = 0; 27 | static const int DM_COLUMN = 1; 28 | 29 | static void processKeyTokens(QStringList *tokens, int srcCol, int dstCol) 30 | { 31 | struct Row { 32 | const char *zero; 33 | const char *one; 34 | const char *operator[](int col) const 35 | { 36 | return col == 0 ? zero : one; 37 | } 38 | }; 39 | static const Row table[] = {{"Meta", "Super"}, 40 | {"Ctrl", "Control"}, 41 | // Special cases for compatibility with libdbusmenu-glib which uses 42 | // "plus" for "+" and "minus" for "-". 43 | // cf https://bugs.launchpad.net/libdbusmenu-qt/+bug/712565 44 | {"+", "plus"}, 45 | {"-", "minus"}, 46 | {nullptr, nullptr}}; 47 | 48 | const Row *ptr = table; 49 | for (; ptr->zero != nullptr; ++ptr) { 50 | const char *from = (*ptr)[srcCol]; 51 | const char *to = (*ptr)[dstCol]; 52 | tokens->replaceInStrings(from, to); 53 | } 54 | } 55 | 56 | DBusMenuShortcut DBusMenuShortcut::fromKeySequence(const QKeySequence &sequence) 57 | { 58 | QString string = sequence.toString(); 59 | DBusMenuShortcut shortcut; 60 | QStringList tokens = string.split(QStringLiteral(", ")); 61 | Q_FOREACH (QString token, tokens) { 62 | // Hack: Qt::CTRL | Qt::Key_Plus is turned into the string "Ctrl++", 63 | // but we don't want the call to token.split() to consider the 64 | // second '+' as a separator so we replace it with its final value. 65 | token.replace(QLatin1String("++"), QLatin1String("+plus")); 66 | QStringList keyTokens = token.split('+'); 67 | processKeyTokens(&keyTokens, QT_COLUMN, DM_COLUMN); 68 | shortcut << keyTokens; 69 | } 70 | return shortcut; 71 | } 72 | 73 | QKeySequence DBusMenuShortcut::toKeySequence() const 74 | { 75 | QStringList tmp; 76 | Q_FOREACH (const QStringList &keyTokens_, *this) { 77 | QStringList keyTokens = keyTokens_; 78 | processKeyTokens(&keyTokens, DM_COLUMN, QT_COLUMN); 79 | tmp << keyTokens.join(QLatin1String("+")); 80 | } 81 | QString string = tmp.join(QLatin1String(", ")); 82 | return QKeySequence::fromString(string); 83 | } 84 | -------------------------------------------------------------------------------- /src/AppMenuModel.h: -------------------------------------------------------------------------------- 1 | /****************************************************************** 2 | * Copyright 2016 Chinmoy Ranjan Pradhan 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public License as 6 | * published by the Free Software Foundation; either version 2 of 7 | * the License or (at your option) version 3 or any later version 8 | * accepted by the membership of KDE e.V. (or its successor approved 9 | * by the membership of KDE e.V.), which shall act as a proxy 10 | * defined in Section 14 of version 3 of the license. 11 | * 12 | * This program is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with this program. If not, see . 19 | * 20 | ******************************************************************/ 21 | 22 | #pragma once 23 | 24 | // Qt 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | 36 | namespace Material 37 | { 38 | 39 | class KDBusMenuImporter; 40 | 41 | class AppMenuModel : public QAbstractListModel, public QAbstractNativeEventFilter 42 | { 43 | Q_OBJECT 44 | 45 | Q_PROPERTY(bool menuAvailable READ menuAvailable WRITE setMenuAvailable NOTIFY menuAvailableChanged) 46 | Q_PROPERTY(QVariant winId READ winId WRITE setWinId NOTIFY winIdChanged) 47 | 48 | public: 49 | explicit AppMenuModel(QObject *parent = nullptr); 50 | ~AppMenuModel() override; 51 | 52 | private: 53 | void x11Init(); 54 | void waylandInit(); 55 | 56 | public: 57 | enum AppMenuRole 58 | { 59 | MenuRole = Qt::UserRole + 1, // TODO this should be Qt::DisplayRole 60 | ActionRole 61 | }; 62 | 63 | QVariant data(const QModelIndex &index, int role) const override; 64 | int rowCount(const QModelIndex &parent = QModelIndex()) const override; 65 | QHash roleNames() const override; 66 | 67 | void updateApplicationMenu(const QString &serviceName, const QString &menuObjectPath); 68 | 69 | bool menuAvailable() const; 70 | void setMenuAvailable(bool set); 71 | 72 | QVariant winId() const; 73 | void setWinId(const QVariant &id); 74 | 75 | signals: 76 | void requestActivateIndex(int index); 77 | 78 | protected: 79 | bool nativeEventFilter(const QByteArray &eventType, void *message, long int *result) override; 80 | 81 | private Q_SLOTS: 82 | void onWinIdChanged(); 83 | void onX11WindowChanged(WId id); 84 | void onX11WindowRemoved(WId id); 85 | 86 | void update(); 87 | 88 | signals: 89 | void menuAvailableChanged(); 90 | void modelNeedsUpdate(); 91 | void winIdChanged(); 92 | 93 | private: 94 | bool m_menuAvailable; 95 | bool m_updatePending = false; 96 | 97 | QVariant m_winId{-1}; 98 | 99 | //! window that its menu initialization may be delayed 100 | WId m_delayedMenuWindowId = 0; 101 | 102 | QPointer m_menu; 103 | 104 | QDBusServiceWatcher *m_serviceWatcher; 105 | QString m_serviceName; 106 | QString m_menuObjectPath; 107 | 108 | QPointer m_importer; 109 | }; 110 | 111 | } // namespace Material 112 | -------------------------------------------------------------------------------- /src/libdbusmenuqt/dbusmenuimporter.h: -------------------------------------------------------------------------------- 1 | /* This file is part of the dbusmenu-qt library 2 | Copyright 2009 Canonical 3 | Author: Aurelien Gateau 4 | 5 | This library is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU Library General Public 7 | License (LGPL) as published by the Free Software Foundation; 8 | either version 2 of the License, or (at your option) any later 9 | version. 10 | 11 | This library is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | Library General Public License for more details. 15 | 16 | You should have received a copy of the GNU Library General Public License 17 | along with this library; see the file COPYING.LIB. If not, write to 18 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 | Boston, MA 02110-1301, USA. 20 | */ 21 | #ifndef DBUSMENUIMPORTER_H 22 | #define DBUSMENUIMPORTER_H 23 | 24 | // Qt 25 | #include 26 | 27 | class QAction; 28 | class QDBusPendingCallWatcher; 29 | class QIcon; 30 | class QMenu; 31 | 32 | class DBusMenuImporterPrivate; 33 | 34 | /** 35 | * A DBusMenuImporter instance can recreate a menu serialized over DBus by 36 | * DBusMenuExporter 37 | */ 38 | class DBusMenuImporter : public QObject 39 | { 40 | Q_OBJECT 41 | public: 42 | /** 43 | * Creates a DBusMenuImporter listening over DBus on service, path 44 | */ 45 | DBusMenuImporter(const QString &service, const QString &path, QObject *parent = nullptr); 46 | 47 | ~DBusMenuImporter() override; 48 | 49 | QAction *actionForId(int id) const; 50 | 51 | /** 52 | * The menu created from listening to the DBusMenuExporter over DBus 53 | */ 54 | QMenu *menu() const; 55 | 56 | public Q_SLOTS: 57 | /** 58 | * Load the menu 59 | * 60 | * Will emit menuUpdated() when complete. 61 | * This should be done before showing a menu 62 | */ 63 | void updateMenu(); 64 | 65 | void updateMenu(QMenu *menu); 66 | 67 | Q_SIGNALS: 68 | /** 69 | * Emitted after a call to updateMenu(). 70 | * @see updateMenu() 71 | */ 72 | void menuUpdated(QMenu *); 73 | 74 | /** 75 | * Emitted when the exporter was asked to activate an action 76 | */ 77 | void actionActivationRequested(QAction *); 78 | 79 | protected: 80 | /** 81 | * Must create a menu, may be customized to fit host appearance. 82 | * Default implementation creates a simple QMenu. 83 | */ 84 | virtual QMenu *createMenu(QWidget *parent); 85 | 86 | /** 87 | * Must convert a name into an icon. 88 | * Default implementation returns a null icon. 89 | */ 90 | virtual QIcon iconForName(const QString &); 91 | 92 | private Q_SLOTS: 93 | void sendClickedEvent(int); 94 | void slotMenuAboutToShow(); 95 | void slotMenuAboutToHide(); 96 | void slotAboutToShowDBusCallFinished(QDBusPendingCallWatcher *); 97 | void slotItemActivationRequested(int id, uint timestamp); 98 | void processPendingLayoutUpdates(); 99 | void slotLayoutUpdated(uint revision, int parentId); 100 | void slotGetLayoutFinished(QDBusPendingCallWatcher *); 101 | 102 | private: 103 | Q_DISABLE_COPY(DBusMenuImporter) 104 | DBusMenuImporterPrivate *const d; 105 | friend class DBusMenuImporterPrivate; 106 | 107 | // Use Q_PRIVATE_SLOT to avoid exposing DBusMenuItemList 108 | Q_PRIVATE_SLOT(d, void slotItemsPropertiesUpdated(const DBusMenuItemList &updatedList, const DBusMenuItemKeysList &removedList)) 109 | }; 110 | 111 | #endif /* DBUSMENUIMPORTER_H */ 112 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: WebKit 4 | AccessModifierOffset: -4 5 | AlignAfterOpenBracket: DontAlign 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlines: Right 9 | AlignOperands: false 10 | AlignTrailingComments: true 11 | AllowAllParametersOfDeclarationOnNextLine: true 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: All 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: false 20 | AlwaysBreakTemplateDeclarations: false 21 | BinPackArguments: true 22 | BinPackParameters: true 23 | BraceWrapping: 24 | AfterClass: false 25 | AfterControlStatement: false 26 | AfterEnum: false 27 | AfterFunction: true 28 | AfterNamespace: false 29 | AfterObjCDeclaration: false 30 | AfterStruct: false 31 | AfterUnion: false 32 | AfterExternBlock: false 33 | BeforeCatch: false 34 | BeforeElse: false 35 | IndentBraces: false 36 | SplitEmptyFunction: true 37 | SplitEmptyRecord: true 38 | SplitEmptyNamespace: true 39 | BreakBeforeBinaryOperators: All 40 | BreakBeforeBraces: WebKit 41 | BreakBeforeInheritanceComma: false 42 | BreakBeforeTernaryOperators: true 43 | BreakConstructorInitializersBeforeComma: false 44 | BreakConstructorInitializers: BeforeComma 45 | BreakAfterJavaFieldAnnotations: false 46 | BreakStringLiterals: true 47 | ColumnLimit: 0 48 | CommentPragmas: '^ IWYU pragma:' 49 | CompactNamespaces: false 50 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 51 | ConstructorInitializerIndentWidth: 4 52 | ContinuationIndentWidth: 4 53 | Cpp11BracedListStyle: false 54 | DerivePointerAlignment: false 55 | DisableFormat: false 56 | ExperimentalAutoDetectBinPacking: false 57 | FixNamespaceComments: false 58 | ForEachMacros: 59 | - foreach 60 | - Q_FOREACH 61 | - BOOST_FOREACH 62 | IncludeBlocks: Preserve 63 | IncludeCategories: 64 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 65 | Priority: 2 66 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 67 | Priority: 3 68 | - Regex: '.*' 69 | Priority: 1 70 | IncludeIsMainRegex: '(Test)?$' 71 | IndentCaseLabels: false 72 | IndentPPDirectives: None 73 | IndentWidth: 4 74 | IndentWrappedFunctionNames: false 75 | JavaScriptQuotes: Leave 76 | JavaScriptWrapImports: true 77 | KeepEmptyLinesAtTheStartOfBlocks: false 78 | MacroBlockBegin: '' 79 | MacroBlockEnd: '' 80 | MaxEmptyLinesToKeep: 1 81 | NamespaceIndentation: Inner 82 | ObjCBlockIndentWidth: 4 83 | ObjCSpaceAfterProperty: true 84 | ObjCSpaceBeforeProtocolList: true 85 | PenaltyBreakAssignment: 2 86 | PenaltyBreakBeforeFirstCallParameter: 19 87 | PenaltyBreakComment: 300 88 | PenaltyBreakFirstLessLess: 120 89 | PenaltyBreakString: 1000 90 | PenaltyExcessCharacter: 1000000 91 | PenaltyReturnTypeOnItsOwnLine: 60 92 | PointerAlignment: Left 93 | RawStringFormats: 94 | - Delimiter: pb 95 | Language: TextProto 96 | BasedOnStyle: google 97 | ReflowComments: true 98 | SortIncludes: true 99 | SortUsingDeclarations: true 100 | SpaceAfterCStyleCast: false 101 | SpaceAfterTemplateKeyword: true 102 | SpaceBeforeAssignmentOperators: true 103 | SpaceBeforeParens: ControlStatements 104 | SpaceInEmptyParentheses: false 105 | SpacesBeforeTrailingComments: 1 106 | SpacesInAngles: false 107 | SpacesInContainerLiterals: true 108 | SpacesInCStyleCastParentheses: false 109 | SpacesInParentheses: false 110 | SpacesInSquareBrackets: false 111 | Standard: Cpp11 112 | TabWidth: 8 113 | UseTab: Never 114 | ... 115 | 116 | -------------------------------------------------------------------------------- /src/TextButton.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Chris Holland 3 | * Copyright (C) 2016 Kai Uwe Broulik 4 | * Copyright (C) 2014 by Hugo Pereira Da Costa 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | // own 21 | #include "TextButton.h" 22 | #include "Material.h" 23 | #include "AppMenuButton.h" 24 | #include "Decoration.h" 25 | 26 | // KDecoration 27 | #include 28 | #include 29 | 30 | // Qt 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | 37 | namespace Material 38 | { 39 | 40 | TextButton::TextButton(Decoration *decoration, const int buttonIndex, QObject *parent) 41 | : AppMenuButton(decoration, buttonIndex, parent) 42 | , m_action(nullptr) 43 | , m_text(QStringLiteral("Menu")) 44 | { 45 | const auto *deco = qobject_cast(decoration); 46 | 47 | setHorzPadding(deco->appMenuButtonHorzPadding()); 48 | 49 | setVisible(true); 50 | } 51 | 52 | TextButton::~TextButton() 53 | { 54 | } 55 | 56 | void TextButton::paintIcon(QPainter *painter, const QRectF &iconRect, const qreal gridUnit) 57 | { 58 | Q_UNUSED(iconRect) 59 | Q_UNUSED(gridUnit) 60 | 61 | // Font 62 | painter->setFont(decoration()->settings()->font()); 63 | 64 | // TODO: Use Qt::TextShowMnemonic when Alt is pressed 65 | const bool isAltPressed = false; 66 | const Qt::TextFlag mnemonicFlag = isAltPressed ? Qt::TextShowMnemonic : Qt::TextHideMnemonic; 67 | painter->drawText(geometry(), mnemonicFlag | Qt::AlignCenter, m_text); 68 | } 69 | 70 | QSize TextButton::getTextSize() 71 | { 72 | const auto *deco = qobject_cast(decoration()); 73 | if (!deco) { 74 | return QSize(0, 0); 75 | } 76 | 77 | // const QString elidedText = painter->fontMetrics().elidedText( 78 | // m_text, 79 | // Qt::ElideRight, 80 | // 100, // Max width TODO: scale by dpi 81 | // ); 82 | const int textWidth = deco->getTextWidth(m_text); 83 | const int titleBarHeight = deco->titleBarHeight(); 84 | const QSize size(textWidth, titleBarHeight); 85 | return size; 86 | } 87 | 88 | QAction* TextButton::action() const 89 | { 90 | return m_action; 91 | } 92 | 93 | void TextButton::setAction(QAction *set) 94 | { 95 | if (m_action != set) { 96 | m_action = set; 97 | emit actionChanged(); 98 | } 99 | } 100 | 101 | QString TextButton::text() const 102 | { 103 | return m_text; 104 | } 105 | 106 | void TextButton::setText(const QString set) 107 | { 108 | if (m_text != set) { 109 | m_text = set; 110 | emit textChanged(); 111 | 112 | updateGeometry(); 113 | } 114 | } 115 | 116 | void TextButton::setHeight(int buttonHeight) 117 | { 118 | Q_UNUSED(buttonHeight) 119 | 120 | updateGeometry(); 121 | } 122 | 123 | void TextButton::updateGeometry() 124 | { 125 | const QSize textSize = getTextSize(); 126 | updateSize(textSize.width(), textSize.height()); 127 | } 128 | 129 | 130 | } // namespace Material 131 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package (KDecoration2 REQUIRED) 2 | 3 | find_package (Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS 4 | Core 5 | Gui 6 | ) 7 | 8 | find_package (KF5 REQUIRED COMPONENTS 9 | Config 10 | ConfigWidgets 11 | CoreAddons 12 | GuiAddons 13 | I18n 14 | IconThemes 15 | WindowSystem 16 | ) 17 | 18 | 19 | # X11 20 | find_package(X11 REQUIRED) 21 | set_package_properties(X11 PROPERTIES DESCRIPTION "X11 libraries" 22 | URL "http://www.x.org" 23 | TYPE REQUIRED 24 | PURPOSE "Required for building the X11 based workspace" 25 | ) 26 | 27 | find_package(XCB MODULE REQUIRED COMPONENTS 28 | XCB 29 | RANDR 30 | ) 31 | set_package_properties(XCB PROPERTIES TYPE REQUIRED) 32 | 33 | if (Qt6_FOUND) 34 | # The QX11Info class has been removed. 35 | # Clients that still rely on the functionality can include the private header as a stopgap solution. 36 | # To enable private headers use QT += gui-private with qmake, or add a project dependency to Qt::GuiPrivate with CMake. 37 | # https://doc.qt.io/qt-6/extras-changes-qt6.html#changes-to-qt-x11-extras 38 | find_package(Qt${QT_VERSION_MAJOR} ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS GuiPrivate) 39 | elseif(Qt5_FOUND) 40 | find_package(Qt${QT_VERSION_MAJOR} ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS X11Extras) 41 | endif() 42 | 43 | 44 | if(X11_FOUND AND XCB_XCB_FOUND) 45 | set(HAVE_X11 ON) 46 | set(X11_LIBRARIES XCB::XCB) 47 | endif() 48 | 49 | 50 | # Wayland 51 | find_package (KF5 COMPONENTS 52 | Wayland 53 | ) 54 | 55 | if(KF5Wayland_FOUND) 56 | set(HAVE_Wayland ON) 57 | set(Wayland_LIBRARIES KF5::WaylandClient) 58 | endif() 59 | 60 | 61 | # KDecoration2/Plasma Version 62 | if(${KDecoration2_VERSION} VERSION_GREATER_EQUAL "5.25.0") 63 | set(HAVE_KDecoration2_5_25 ON) 64 | else() 65 | set(HAVE_KDecoration2_5_25 OFF) 66 | endif() 67 | message(STATUS "HAVE_KDecoration2_5_25: ${HAVE_KDecoration2_5_25} (${KDecoration2_VERSION})") 68 | 69 | # KF5 Version 70 | if(${KF5_VERSION} VERSION_GREATER_EQUAL "5.101.0") 71 | set(HAVE_KF5_101 ON) 72 | else() 73 | set(HAVE_KF5_101 OFF) 74 | endif() 75 | message(STATUS "HAVE_KF5_101: ${HAVE_KF5_101} (${KF5_VERSION})") 76 | 77 | 78 | configure_file(BuildConfig.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/BuildConfig.h) 79 | 80 | set (decoration_SRCS 81 | AppMenuModel.cc 82 | AppMenuButton.cc 83 | AppMenuButtonGroup.cc 84 | BoxShadowHelper.cc 85 | Button.cc 86 | Decoration.cc 87 | MenuOverflowButton.cc 88 | TextButton.cc 89 | ConfigurationModule.cc 90 | plugin.cc 91 | ) 92 | 93 | kconfig_add_kcfg_files(decoration_SRCS 94 | InternalSettings.kcfgc 95 | ) 96 | 97 | add_library (materialdecoration MODULE 98 | ${decoration_SRCS} 99 | ) 100 | 101 | target_link_libraries (materialdecoration 102 | PUBLIC 103 | dbusmenuqt 104 | Qt${QT_VERSION_MAJOR}::Core 105 | Qt${QT_VERSION_MAJOR}::Gui 106 | # Qt${QT_VERSION_MAJOR}::X11Extras 107 | KF5::ConfigCore 108 | KF5::ConfigGui 109 | KF5::ConfigWidgets 110 | KF5::CoreAddons 111 | KF5::I18n 112 | KF5::GuiAddons 113 | KF5::IconThemes 114 | KF5::WindowSystem 115 | ${X11_LIBRARIES} 116 | ${Wayland_LIBRARIES} 117 | 118 | PRIVATE 119 | KDecoration2::KDecoration 120 | ) 121 | if (Qt6_FOUND) 122 | # The QX11Info class has been removed. 123 | target_link_libraries (materialdecoration 124 | PUBLIC 125 | Qt${QT_VERSION_MAJOR}::GuiPrivate 126 | ) 127 | elseif(Qt5_FOUND) 128 | target_link_libraries (materialdecoration 129 | PUBLIC 130 | Qt${QT_VERSION_MAJOR}::X11Extras 131 | ) 132 | endif() 133 | 134 | install (TARGETS materialdecoration 135 | DESTINATION ${PLUGIN_INSTALL_DIR}/org.kde.kdecoration2) 136 | -------------------------------------------------------------------------------- /src/Button.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Chris Holland 3 | * Copyright (C) 2018 Vlad Zagorodniy 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | // KDecoration 22 | #include 23 | #include 24 | 25 | // Qt 26 | #include 27 | #include 28 | #include 29 | 30 | namespace Material 31 | { 32 | 33 | class Decoration; 34 | 35 | class Button : public KDecoration2::DecorationButton 36 | { 37 | Q_OBJECT 38 | 39 | public: 40 | Button(KDecoration2::DecorationButtonType type, Decoration *decoration, QObject *parent = nullptr); 41 | ~Button() override; 42 | 43 | Q_PROPERTY(bool animationEnabled READ animationEnabled WRITE setAnimationEnabled NOTIFY animationEnabledChanged) 44 | Q_PROPERTY(int animationDuration READ animationDuration WRITE setAnimationDuration NOTIFY animationDurationChanged) 45 | Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity NOTIFY opacityChanged) 46 | Q_PROPERTY(qreal transitionValue READ transitionValue WRITE setTransitionValue NOTIFY transitionValueChanged) 47 | Q_PROPERTY(QMargins* padding READ padding NOTIFY paddingChanged) 48 | 49 | // Passed to DecorationButtonGroup in Decoration 50 | static KDecoration2::DecorationButton *create(KDecoration2::DecorationButtonType type, KDecoration2::Decoration *decoration, QObject *parent = nullptr); 51 | 52 | // This is called by: 53 | // registerPlugin(QStringLiteral("button")) 54 | // It is needed to create buttons for applet-window-buttons. 55 | explicit Button(QObject *parent, const QVariantList &args); 56 | 57 | 58 | void paint(QPainter *painter, const QRect &repaintRegion) override; 59 | virtual void paintIcon(QPainter *painter, const QRectF &iconRect, const qreal gridUnit); 60 | 61 | virtual void updateSize(int contentWidth, int contentHeight); 62 | virtual void setHeight(int buttonHeight); 63 | 64 | virtual qreal iconLineWidth(const qreal gridUnit) const; 65 | void setPenWidth(QPainter *painter, const qreal gridUnit, const qreal scale); 66 | 67 | virtual QColor backgroundColor() const; 68 | virtual QColor foregroundColor() const; 69 | 70 | QRectF contentArea() const; 71 | 72 | bool animationEnabled() const; 73 | void setAnimationEnabled(bool value); 74 | 75 | int animationDuration() const; 76 | void setAnimationDuration(int duration); 77 | 78 | qreal opacity() const; 79 | void setOpacity(qreal value); 80 | 81 | qreal transitionValue() const; 82 | void setTransitionValue(qreal value); 83 | 84 | QMargins* padding(); 85 | void setHorzPadding(int value); 86 | void setVertPadding(int value); 87 | 88 | private Q_SLOTS: 89 | void updateAnimationState(bool hovered); 90 | 91 | signals: 92 | void animationEnabledChanged(); 93 | void animationDurationChanged(); 94 | void opacityChanged(); 95 | void transitionValueChanged(qreal); 96 | void paddingChanged(); 97 | 98 | private: 99 | bool m_animationEnabled; 100 | QVariantAnimation *m_animation; 101 | qreal m_opacity; 102 | qreal m_transitionValue; 103 | QMargins *m_padding; 104 | bool m_isGtkButton; 105 | }; 106 | 107 | } // namespace Material 108 | -------------------------------------------------------------------------------- /src/AppMenuButtonGroup.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Chris Holland 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #pragma once 19 | 20 | // own 21 | #include "AppMenuModel.h" 22 | 23 | // KDecoration 24 | #include 25 | #include 26 | #include 27 | 28 | // Qt 29 | #include 30 | #include 31 | 32 | namespace Material 33 | { 34 | 35 | class Decoration; 36 | 37 | class AppMenuButtonGroup : public KDecoration2::DecorationButtonGroup 38 | { 39 | Q_OBJECT 40 | 41 | public: 42 | AppMenuButtonGroup(Decoration *decoration); 43 | ~AppMenuButtonGroup() override; 44 | 45 | Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged) 46 | Q_PROPERTY(int overflowing READ overflowing WRITE setOverflowing NOTIFY overflowingChanged) 47 | Q_PROPERTY(bool hovered READ hovered WRITE setHovered NOTIFY hoveredChanged) 48 | Q_PROPERTY(bool showing READ showing WRITE setShowing NOTIFY showingChanged) 49 | Q_PROPERTY(bool alwaysShow READ alwaysShow WRITE setAlwaysShow NOTIFY alwaysShowChanged) 50 | Q_PROPERTY(bool animationEnabled READ animationEnabled WRITE setAnimationEnabled NOTIFY animationEnabledChanged) 51 | Q_PROPERTY(int animationDuration READ animationDuration WRITE setAnimationDuration NOTIFY animationDurationChanged) 52 | Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity NOTIFY opacityChanged) 53 | 54 | int currentIndex() const; 55 | void setCurrentIndex(int set); 56 | 57 | bool overflowing() const; 58 | void setOverflowing(bool set); 59 | 60 | bool hovered() const; 61 | void setHovered(bool value); 62 | 63 | bool showing() const; 64 | void setShowing(bool value); 65 | 66 | bool alwaysShow() const; 67 | void setAlwaysShow(bool value); 68 | 69 | bool animationEnabled() const; 70 | void setAnimationEnabled(bool value); 71 | 72 | int animationDuration() const; 73 | void setAnimationDuration(int duration); 74 | 75 | qreal opacity() const; 76 | void setOpacity(qreal value); 77 | 78 | bool isMenuOpen() const; 79 | 80 | KDecoration2::DecorationButton* buttonAt(int x, int y) const; 81 | 82 | void unPressAllButtons(); 83 | 84 | public slots: 85 | void initAppMenuModel(); 86 | void updateAppMenuModel(); 87 | void updateOverflow(QRectF availableRect); 88 | void trigger(int index); 89 | void triggerOverflow(); 90 | void updateShowing(); 91 | void onMenuAboutToHide(); 92 | 93 | private slots: 94 | void onShowingChanged(bool hovered); 95 | 96 | signals: 97 | void menuUpdated(); 98 | void requestActivateIndex(int index); 99 | void requestActivateOverflow(); 100 | 101 | void currentIndexChanged(); 102 | void overflowingChanged(); 103 | void hoveredChanged(bool); 104 | void showingChanged(bool); 105 | void alwaysShowChanged(bool); 106 | void animationEnabledChanged(bool); 107 | void animationDurationChanged(int); 108 | void opacityChanged(qreal); 109 | 110 | protected: 111 | bool eventFilter(QObject *watched, QEvent *event) override; 112 | 113 | private: 114 | void resetButtons(); 115 | 116 | AppMenuModel *m_appMenuModel; 117 | int m_currentIndex; 118 | int m_overflowIndex; 119 | bool m_overflowing; 120 | bool m_hovered; 121 | bool m_showing; 122 | bool m_alwaysShow; 123 | bool m_animationEnabled; 124 | QVariantAnimation *m_animation; 125 | qreal m_opacity; 126 | QPointer m_currentMenu; 127 | }; 128 | 129 | } // namespace Material 130 | -------------------------------------------------------------------------------- /src/libdbusmenuqt/dbusmenutypes_p.cpp: -------------------------------------------------------------------------------- 1 | /* This file is part of the dbusmenu-qt library 2 | Copyright 2009 Canonical 3 | Author: Aurelien Gateau 4 | 5 | This library is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU Library General Public 7 | License (LGPL) as published by the Free Software Foundation; 8 | either version 2 of the License, or (at your option) any later 9 | version. 10 | 11 | This library is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | Library General Public License for more details. 15 | 16 | You should have received a copy of the GNU Library General Public License 17 | along with this library; see the file COPYING.LIB. If not, write to 18 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 | Boston, MA 02110-1301, USA. 20 | */ 21 | #include "dbusmenutypes_p.h" 22 | 23 | // Local 24 | #include "dbusmenushortcut_p.h" 25 | 26 | // Qt 27 | #include 28 | #include 29 | 30 | //// DBusMenuItem 31 | QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuItem &obj) 32 | { 33 | argument.beginStructure(); 34 | argument << obj.id << obj.properties; 35 | argument.endStructure(); 36 | return argument; 37 | } 38 | 39 | const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuItem &obj) 40 | { 41 | argument.beginStructure(); 42 | argument >> obj.id >> obj.properties; 43 | argument.endStructure(); 44 | return argument; 45 | } 46 | 47 | //// DBusMenuItemKeys 48 | QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuItemKeys &obj) 49 | { 50 | argument.beginStructure(); 51 | argument << obj.id << obj.properties; 52 | argument.endStructure(); 53 | return argument; 54 | } 55 | 56 | const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuItemKeys &obj) 57 | { 58 | argument.beginStructure(); 59 | argument >> obj.id >> obj.properties; 60 | argument.endStructure(); 61 | return argument; 62 | } 63 | 64 | //// DBusMenuLayoutItem 65 | QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuLayoutItem &obj) 66 | { 67 | argument.beginStructure(); 68 | argument << obj.id << obj.properties; 69 | argument.beginArray(qMetaTypeId()); 70 | Q_FOREACH (const DBusMenuLayoutItem &child, obj.children) { 71 | argument << QDBusVariant(QVariant::fromValue(child)); 72 | } 73 | argument.endArray(); 74 | argument.endStructure(); 75 | return argument; 76 | } 77 | 78 | const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuLayoutItem &obj) 79 | { 80 | argument.beginStructure(); 81 | argument >> obj.id >> obj.properties; 82 | argument.beginArray(); 83 | while (!argument.atEnd()) { 84 | QDBusVariant dbusVariant; 85 | argument >> dbusVariant; 86 | QDBusArgument childArgument = dbusVariant.variant().value(); 87 | 88 | DBusMenuLayoutItem child; 89 | childArgument >> child; 90 | obj.children.append(child); 91 | } 92 | argument.endArray(); 93 | argument.endStructure(); 94 | return argument; 95 | } 96 | 97 | //// DBusMenuShortcut 98 | QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuShortcut &obj) 99 | { 100 | argument.beginArray(qMetaTypeId()); 101 | typename QList::ConstIterator it = obj.constBegin(); 102 | typename QList::ConstIterator end = obj.constEnd(); 103 | for (; it != end; ++it) 104 | argument << *it; 105 | argument.endArray(); 106 | return argument; 107 | } 108 | 109 | const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuShortcut &obj) 110 | { 111 | argument.beginArray(); 112 | obj.clear(); 113 | while (!argument.atEnd()) { 114 | QStringList item; 115 | argument >> item; 116 | obj.push_back(item); 117 | } 118 | argument.endArray(); 119 | return argument; 120 | } 121 | 122 | void DBusMenuTypes_register() 123 | { 124 | static bool registered = false; 125 | if (registered) { 126 | return; 127 | } 128 | qDBusRegisterMetaType(); 129 | qDBusRegisterMetaType(); 130 | qDBusRegisterMetaType(); 131 | qDBusRegisterMetaType(); 132 | qDBusRegisterMetaType(); 133 | qDBusRegisterMetaType(); 134 | qDBusRegisterMetaType(); 135 | registered = true; 136 | } 137 | -------------------------------------------------------------------------------- /src/Decoration.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Chris Holland 3 | * Copyright (C) 2018 Vlad Zagorodniy 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #pragma once 20 | 21 | // own 22 | #include "BuildConfig.h" 23 | #include "AppMenuButtonGroup.h" 24 | #include "InternalSettings.h" 25 | 26 | // KDecoration 27 | #include 28 | #include 29 | #include 30 | 31 | // Qt 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | #if HAVE_X11 40 | #include 41 | #endif 42 | 43 | 44 | namespace Material 45 | { 46 | 47 | class Button; 48 | class TextButton; 49 | class MenuOverflowButton; 50 | 51 | class Decoration : public KDecoration2::Decoration 52 | { 53 | Q_OBJECT 54 | 55 | public: 56 | Decoration(QObject *parent = nullptr, const QVariantList &args = QVariantList()); 57 | ~Decoration() override; 58 | 59 | QRect titleBarRect() const; 60 | QRect centerRect() const; 61 | 62 | void paint(QPainter *painter, const QRect &repaintRegion) override; 63 | 64 | public slots: 65 | void init() override; 66 | void reconfigure(); 67 | 68 | protected: 69 | void hoverEnterEvent(QHoverEvent *event) override; 70 | void hoverMoveEvent(QHoverEvent *event) override; 71 | void hoverLeaveEvent(QHoverEvent *event) override; 72 | void mousePressEvent(QMouseEvent *event) override; 73 | void mouseReleaseEvent(QMouseEvent *event) override; 74 | void wheelEvent(QWheelEvent *event) override; 75 | 76 | private slots: 77 | void onSectionUnderMouseChanged(const Qt::WindowFrameSection value); 78 | 79 | private: 80 | void updateBlur(); 81 | void updateBorders(); 82 | void updateResizeBorders(); 83 | void updateTitleBar(); 84 | void updateTitleBarHoverState(); 85 | void setButtonGroupHeight(KDecoration2::DecorationButtonGroup *buttonGroup, int buttonHeight); 86 | void setButtonGroupHorzPadding(KDecoration2::DecorationButtonGroup *buttonGroup, int value); 87 | void setButtonGroupVertPadding(KDecoration2::DecorationButtonGroup *buttonGroup, int value); 88 | void updateButtonHeight(); 89 | void updateButtonsGeometry(); 90 | void setButtonGroupAnimation(KDecoration2::DecorationButtonGroup *buttonGroup, bool enabled, int duration); 91 | void updateButtonAnimation(); 92 | void updateShadow(); 93 | 94 | bool menuAlwaysShow() const; 95 | bool animationsEnabled() const; 96 | int animationsDuration() const; 97 | int buttonPadding() const; 98 | int titleBarHeight() const; 99 | int appMenuButtonHorzPadding() const; 100 | int appMenuCaptionSpacing() const; 101 | int captionMinWidth() const; 102 | 103 | int bottomBorderSize() const; 104 | int sideBorderSize() const; 105 | 106 | bool leftBorderVisible() const; 107 | bool rightBorderVisible() const; 108 | bool topBorderVisible() const; 109 | bool bottomBorderVisible() const; 110 | 111 | bool titleBarIsHovered() const; 112 | int getTextWidth(const QString text, bool showMnemonic = false) const; 113 | QPoint windowPos() const; 114 | 115 | void initDragMove(const QPoint pos); 116 | void resetDragMove(); 117 | bool dragMoveTick(const QPoint pos); 118 | void sendMoveEvent(const QPoint pos); 119 | 120 | QColor borderColor() const; 121 | QColor titleBarBackgroundColor() const; 122 | QColor titleBarForegroundColor() const; 123 | 124 | void paintFrameBackground(QPainter *painter, const QRect &repaintRegion) const; 125 | void paintTitleBarBackground(QPainter *painter, const QRect &repaintRegion) const; 126 | void paintCaption(QPainter *painter, const QRect &repaintRegion) const; 127 | void paintButtons(QPainter *painter, const QRect &repaintRegion) const; 128 | void paintOutline(QPainter *painter, const QRect &repaintRegion) const; 129 | 130 | KDecoration2::DecorationButtonGroup *m_leftButtons; 131 | KDecoration2::DecorationButtonGroup *m_rightButtons; 132 | AppMenuButtonGroup *m_menuButtons; 133 | 134 | QSharedPointer m_internalSettings; 135 | 136 | QPoint m_pressedPoint; 137 | 138 | #if HAVE_X11 139 | xcb_atom_t m_moveResizeAtom = 0; 140 | #endif 141 | 142 | friend class AppMenuButtonGroup; 143 | friend class Button; 144 | friend class AppIconButton; 145 | friend class AppMenuButton; 146 | friend class TextButton; 147 | // friend class MenuOverflowButton; 148 | }; 149 | 150 | } // namespace Material 151 | -------------------------------------------------------------------------------- /src/BoxShadowHelper.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2018 Vlad Zagorodniy 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 2 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | // own 19 | #include "BoxShadowHelper.h" 20 | 21 | // Qt 22 | #include 23 | 24 | // std 25 | #include 26 | 27 | 28 | namespace Material 29 | { 30 | namespace BoxShadowHelper 31 | { 32 | 33 | namespace 34 | { 35 | // According to the CSS Level 3 spec, standard deviation must be equal to 36 | // half of the blur radius. https://www.w3.org/TR/css-backgrounds-3/#shadow-blur 37 | // Current window size is too small for sigma equal to half of the blur radius. 38 | // As a workaround, sigma blur scale is lowered. With the lowered sigma 39 | // blur scale, area under the kernel equals to 0.98, which is pretty enough. 40 | // Maybe, it should be changed in the future. 41 | const qreal SIGMA_BLUR_SCALE = 0.4375; 42 | } // anonymous namespace 43 | 44 | inline qreal radiusToSigma(qreal radius) 45 | { 46 | return radius * SIGMA_BLUR_SCALE; 47 | } 48 | 49 | inline int boxSizeToRadius(int boxSize) 50 | { 51 | return (boxSize - 1) / 2; 52 | } 53 | 54 | QVector computeBoxSizes(int radius, int numIterations) 55 | { 56 | const qreal sigma = radiusToSigma(radius); 57 | 58 | // Box sizes are computed according to the "Fast Almost-Gaussian Filtering" 59 | // paper by Peter Kovesi. 60 | int lower = std::floor(std::sqrt(12 * std::pow(sigma, 2) / numIterations + 1)); 61 | if (lower % 2 == 0) { 62 | lower--; 63 | } 64 | 65 | const int upper = lower + 2; 66 | const int threshold = std::round((12 * std::pow(sigma, 2) - numIterations * std::pow(lower, 2) 67 | - 4 * numIterations * lower - 3 * numIterations) / (-4 * lower - 4)); 68 | 69 | QVector boxSizes; 70 | boxSizes.reserve(numIterations); 71 | for (int i = 0; i < numIterations; ++i) { 72 | boxSizes.append(i < threshold ? lower : upper); 73 | } 74 | 75 | return boxSizes; 76 | } 77 | 78 | void boxBlurPass(const QImage &src, QImage &dst, int boxSize) 79 | { 80 | const int alphaStride = src.depth() >> 3; 81 | const int alphaOffset = QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3; 82 | 83 | const int radius = boxSizeToRadius(boxSize); 84 | const qreal invSize = 1.0 / boxSize; 85 | 86 | const int dstStride = dst.width() * alphaStride; 87 | 88 | for (int y = 0; y < src.height(); ++y) { 89 | const uchar *srcAlpha = src.scanLine(y); 90 | uchar *dstAlpha = dst.scanLine(0); 91 | 92 | srcAlpha += alphaOffset; 93 | dstAlpha += alphaOffset + y * alphaStride; 94 | 95 | const uchar *left = srcAlpha; 96 | const uchar *right = left + alphaStride * radius; 97 | 98 | int window = 0; 99 | for (int x = 0; x < radius; ++x) { 100 | window += *srcAlpha; 101 | srcAlpha += alphaStride; 102 | } 103 | 104 | for (int x = 0; x <= radius; ++x) { 105 | window += *right; 106 | right += alphaStride; 107 | *dstAlpha = static_cast(window * invSize); 108 | dstAlpha += dstStride; 109 | } 110 | 111 | for (int x = radius + 1; x < src.width() - radius; ++x) { 112 | window += *right - *left; 113 | left += alphaStride; 114 | right += alphaStride; 115 | *dstAlpha = static_cast(window * invSize); 116 | dstAlpha += dstStride; 117 | } 118 | 119 | for (int x = src.width() - radius; x < src.width(); ++x) { 120 | window -= *left; 121 | left += alphaStride; 122 | *dstAlpha = static_cast(window * invSize); 123 | dstAlpha += dstStride; 124 | } 125 | } 126 | } 127 | 128 | void boxBlurAlpha(QImage &image, int radius, int numIterations) 129 | { 130 | // Temporary buffer is transposed so we always read memory 131 | // in linear order. 132 | QImage tmp(image.height(), image.width(), image.format()); 133 | 134 | const QVector boxSizes = computeBoxSizes(radius, numIterations); 135 | for (const int &boxSize : boxSizes) { 136 | boxBlurPass(image, tmp, boxSize); // horizontal pass 137 | boxBlurPass(tmp, image, boxSize); // vertical pass 138 | } 139 | } 140 | 141 | void boxShadow(QPainter *p, const QRect &box, const QPoint &offset, int radius, const QColor &color) 142 | { 143 | const QSize size = box.size() + 2 * QSize(radius, radius); 144 | const qreal dpr = p->device()->devicePixelRatioF(); 145 | 146 | QPainter painter; 147 | 148 | QImage shadow(size * dpr, QImage::Format_ARGB32_Premultiplied); 149 | shadow.setDevicePixelRatio(dpr); 150 | shadow.fill(Qt::transparent); 151 | 152 | painter.begin(&shadow); 153 | painter.fillRect(QRect(QPoint(radius, radius), box.size()), Qt::black); 154 | painter.end(); 155 | 156 | // There is no need to blur RGB channels. Blur the alpha 157 | // channel and then give the shadow a tint of the desired color. 158 | const int numIterations = 3; 159 | boxBlurAlpha(shadow, radius, numIterations); 160 | 161 | painter.begin(&shadow); 162 | painter.setCompositionMode(QPainter::CompositionMode_SourceIn); 163 | painter.fillRect(shadow.rect(), color); 164 | painter.end(); 165 | 166 | QRect shadowRect = shadow.rect(); 167 | shadowRect.setSize(shadowRect.size() / dpr); 168 | shadowRect.moveCenter(box.center() + offset); 169 | p->drawImage(shadowRect, shadow); 170 | } 171 | 172 | } // namespace BoxShadowHelper 173 | } // namespace Material 174 | -------------------------------------------------------------------------------- /src/ConfigurationModule.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Chris Holland 3 | * Copyright (C) 2012 Martin Gräßlin 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | // own 20 | #include "ConfigurationModule.h" 21 | #include "Material.h" 22 | #include "InternalSettings.h" 23 | 24 | // KF 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | // KDecoration 33 | #include 34 | 35 | // Qt 36 | #include 37 | 38 | // QWidget 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | 51 | namespace Material 52 | { 53 | 54 | 55 | ConfigurationModule::ConfigurationModule(QWidget *parent, const QVariantList &args) 56 | : KCModule(parent, args) 57 | , m_titleAlignment(InternalSettings::AlignCenterFullWidth) 58 | , m_buttonSize(InternalSettings::ButtonDefault) 59 | , m_shadowSize(InternalSettings::ShadowVeryLarge) 60 | { 61 | init(); 62 | } 63 | 64 | void ConfigurationModule::init() 65 | { 66 | KCoreConfigSkeleton *skel = new KCoreConfigSkeleton(KSharedConfig::openConfig(s_configFilename), this); 67 | skel->setCurrentGroup(QStringLiteral("Windeco")); 68 | 69 | // See fr.po for the messages we can reuse from breeze: 70 | // https://websvn.kde.org/*checkout*/trunk/l10n-kf5/fr/messages/breeze/breeze_kwin_deco.po 71 | 72 | //--- Tabs 73 | QTabWidget *tabWidget = new QTabWidget(this); 74 | QVBoxLayout *mainLayout = new QVBoxLayout(this); 75 | mainLayout->addWidget(tabWidget); 76 | mainLayout->addStretch(1); 77 | setLayout(mainLayout); 78 | 79 | //--- General 80 | QWidget *generalTab = new QWidget(tabWidget); 81 | tabWidget->addTab(generalTab, i18nd("breeze_kwin_deco", "General")); 82 | QFormLayout *generalForm = new QFormLayout(generalTab); 83 | generalTab->setLayout(generalForm); 84 | 85 | QComboBox *titleAlignment = new QComboBox(generalTab); 86 | titleAlignment->addItem(i18nd("breeze_kwin_deco", "Left")); 87 | titleAlignment->addItem(i18nd("breeze_kwin_deco", "Center")); 88 | titleAlignment->addItem(i18nd("breeze_kwin_deco", "Center (Full Width)")); 89 | titleAlignment->addItem(i18nd("breeze_kwin_deco", "Right")); 90 | titleAlignment->addItem(i18n("Hidden")); 91 | titleAlignment->setObjectName(QStringLiteral("kcfg_TitleAlignment")); 92 | generalForm->addRow(i18nd("breeze_kwin_deco", "Tit&le alignment:"), titleAlignment); 93 | 94 | QComboBox *buttonSizes = new QComboBox(generalTab); 95 | buttonSizes->addItem(i18nd("breeze_kwin_deco", "Tiny")); 96 | buttonSizes->addItem(i18ndc("breeze_kwin_deco", "@item:inlistbox Button size:", "Small")); 97 | buttonSizes->addItem(i18ndc("breeze_kwin_deco", "@item:inlistbox Button size:", "Medium")); 98 | buttonSizes->addItem(i18ndc("breeze_kwin_deco", "@item:inlistbox Button size:", "Large")); 99 | buttonSizes->addItem(i18ndc("breeze_kwin_deco", "@item:inlistbox Button size:", "Very Large")); 100 | buttonSizes->setObjectName(QStringLiteral("kcfg_ButtonSize")); 101 | generalForm->addRow(i18nd("breeze_kwin_deco", "B&utton size:"), buttonSizes); 102 | 103 | QDoubleSpinBox *activeOpacity = new QDoubleSpinBox(generalTab); 104 | activeOpacity->setMinimum(0.0); 105 | activeOpacity->setMaximum(1.0); 106 | activeOpacity->setSingleStep(0.05); 107 | activeOpacity->setObjectName(QStringLiteral("kcfg_ActiveOpacity")); 108 | generalForm->addRow(i18n("Active Opacity:"), activeOpacity); 109 | 110 | QDoubleSpinBox *inactiveOpacity = new QDoubleSpinBox(generalTab); 111 | inactiveOpacity->setMinimum(0.0); 112 | inactiveOpacity->setMaximum(1.0); 113 | inactiveOpacity->setSingleStep(0.05); 114 | inactiveOpacity->setObjectName(QStringLiteral("kcfg_InactiveOpacity")); 115 | generalForm->addRow(i18n("Inactive Opacity:"), inactiveOpacity); 116 | 117 | 118 | //--- Menu 119 | QWidget *menuTab = new QWidget(tabWidget); 120 | tabWidget->addTab(menuTab, i18n("Menu")); 121 | QFormLayout *menuForm = new QFormLayout(menuTab); 122 | menuTab->setLayout(menuForm); 123 | 124 | QLabel *menuLabel = new QLabel(menuTab); 125 | menuLabel->setText(i18n("To enable the Locally Integrated Menus in the titlebar:\nSystem Settings > Window Decorations > Titlebar Buttons Tab\nDrag the 'Application Menu' button to bar.")); 126 | menuForm->addRow(QStringLiteral(""), menuLabel); 127 | 128 | QRadioButton *menuAlwaysShow = new QRadioButton(menuTab); 129 | menuAlwaysShow->setText(i18n("Always Show Menu")); 130 | menuAlwaysShow->setObjectName(QStringLiteral("kcfg_MenuAlwaysShow")); 131 | menuForm->addRow(QStringLiteral(""), menuAlwaysShow); 132 | 133 | // Since there's no easy way to bind this to !MenuAlwaysShow, we 134 | // workaround this by marking the button as checked on init. 135 | // When the config is loaded: 136 | // * If menuAlwaysShow is toggled true, this will be toggled false. 137 | // * If menuAlwaysShow is left false, then this remains true. 138 | QRadioButton *menuRevealOnHover = new QRadioButton(menuTab); 139 | menuRevealOnHover->setText(i18n("Reveal Menu on Hover")); 140 | menuRevealOnHover->setChecked(true); 141 | menuForm->addRow(QStringLiteral(""), menuRevealOnHover); 142 | 143 | QButtonGroup *menuAlwaysShowGroup = new QButtonGroup(menuTab); 144 | menuAlwaysShowGroup->addButton(menuAlwaysShow); 145 | menuAlwaysShowGroup->addButton(menuRevealOnHover); 146 | 147 | QSpinBox *menuButtonHorzPadding = new QSpinBox(menuTab); 148 | menuButtonHorzPadding->setMinimum(0); 149 | menuButtonHorzPadding->setMaximum(INT_MAX); 150 | menuButtonHorzPadding->setObjectName(QStringLiteral("kcfg_MenuButtonHorzPadding")); 151 | menuForm->addRow(i18n("Padding:"), menuButtonHorzPadding); 152 | 153 | 154 | //--- Animations 155 | QWidget *animationsTab = new QWidget(tabWidget); 156 | tabWidget->addTab(animationsTab, i18nd("breeze_kwin_deco", "Animations")); 157 | QFormLayout *animationsForm = new QFormLayout(animationsTab); 158 | animationsTab->setLayout(animationsForm); 159 | 160 | QCheckBox *animationsEnabled = new QCheckBox(animationsTab); 161 | animationsEnabled->setText(i18nd("breeze_kwin_deco", "Enable animations")); 162 | animationsEnabled->setObjectName(QStringLiteral("kcfg_AnimationsEnabled")); 163 | animationsForm->addRow(QStringLiteral(""), animationsEnabled); 164 | 165 | QSpinBox *animationsDuration = new QSpinBox(animationsTab); 166 | animationsDuration->setMinimum(0); 167 | animationsDuration->setMaximum(INT_MAX); 168 | animationsDuration->setSuffix(i18nd("breeze_kwin_deco", " ms")); 169 | animationsDuration->setObjectName(QStringLiteral("kcfg_AnimationsDuration")); 170 | animationsForm->addRow(i18nd("breeze_kwin_deco", "Animations:"), animationsDuration); 171 | 172 | 173 | //--- Shadows 174 | QWidget *shadowTab = new QWidget(tabWidget); 175 | tabWidget->addTab(shadowTab, i18nd("breeze_kwin_deco", "Shadows")); 176 | QFormLayout *shadowForm = new QFormLayout(shadowTab); 177 | shadowTab->setLayout(shadowForm); 178 | 179 | QComboBox *shadowSizes = new QComboBox(shadowTab); 180 | shadowSizes->addItem(i18ndc("breeze_kwin_deco", "@item:inlistbox Button size:", "None")); 181 | shadowSizes->addItem(i18ndc("breeze_kwin_deco", "@item:inlistbox Button size:", "Small")); 182 | shadowSizes->addItem(i18ndc("breeze_kwin_deco", "@item:inlistbox Button size:", "Medium")); 183 | shadowSizes->addItem(i18ndc("breeze_kwin_deco", "@item:inlistbox Button size:", "Large")); 184 | shadowSizes->addItem(i18ndc("breeze_kwin_deco", "@item:inlistbox Button size:", "Very Large")); 185 | shadowSizes->setObjectName(QStringLiteral("kcfg_ShadowSize")); 186 | shadowForm->addRow(i18nd("breeze_kwin_deco", "Si&ze:"), shadowSizes); 187 | 188 | QSpinBox *shadowStrength = new QSpinBox(shadowTab); 189 | shadowStrength->setMinimum(25); 190 | shadowStrength->setMaximum(255); 191 | // shadowStrength->setSuffix(i18nd("breeze_kwin_deco", "%")); 192 | shadowStrength->setObjectName(QStringLiteral("kcfg_ShadowStrength")); 193 | shadowForm->addRow(i18ndc("breeze_kwin_deco", "strength of the shadow (from transparent to opaque)", "S&trength:"), shadowStrength); 194 | 195 | KColorButton *shadowColor = new KColorButton(shadowTab); 196 | shadowColor->setObjectName(QStringLiteral("kcfg_ShadowColor")); 197 | shadowForm->addRow(i18nd("breeze_kwin_deco", "Color:"), shadowColor); 198 | 199 | //--- Config Bindings 200 | skel->addItemInt( 201 | QStringLiteral("TitleAlignment"), 202 | m_titleAlignment, 203 | InternalSettings::AlignCenterFullWidth, 204 | QStringLiteral("TitleAlignment") 205 | ); 206 | skel->addItemInt( 207 | QStringLiteral("ButtonSize"), 208 | m_buttonSize, 209 | InternalSettings::ButtonDefault, 210 | QStringLiteral("ButtonSize") 211 | ); 212 | skel->addItemDouble( 213 | QStringLiteral("ActiveOpacity"), 214 | m_activeOpacity, 215 | 0.75, 216 | QStringLiteral("ActiveOpacity") 217 | ); 218 | skel->addItemDouble( 219 | QStringLiteral("InactiveOpacity"), 220 | m_inactiveOpacity, 221 | 0.85, 222 | QStringLiteral("InactiveOpacity") 223 | ); 224 | skel->addItemBool( 225 | QStringLiteral("MenuAlwaysShow"), 226 | m_menuAlwaysShow, 227 | true, 228 | QStringLiteral("MenuAlwaysShow") 229 | ); 230 | skel->addItemInt( 231 | QStringLiteral("MenuButtonHorzPadding"), 232 | m_menuButtonHorzPadding, 233 | 1, 234 | QStringLiteral("MenuButtonHorzPadding") 235 | ); 236 | skel->addItemBool( 237 | QStringLiteral("AnimationsEnabled"), 238 | m_animationsEnabled, 239 | true, 240 | QStringLiteral("AnimationsEnabled") 241 | ); 242 | skel->addItemInt( 243 | QStringLiteral("AnimationsDuration"), 244 | m_animationsDuration, 245 | 150, 246 | QStringLiteral("AnimationsDuration") 247 | ); 248 | skel->addItemInt( 249 | QStringLiteral("ShadowSize"), 250 | m_shadowSize, 251 | InternalSettings::ShadowVeryLarge, 252 | QStringLiteral("ShadowSize") 253 | ); 254 | skel->addItemInt( 255 | QStringLiteral("ShadowStrength"), 256 | m_shadowStrength, 257 | 255, 258 | QStringLiteral("ShadowStrength") 259 | ); 260 | skel->addItem(new KConfigSkeleton::ItemColor( 261 | skel->currentGroup(), 262 | QStringLiteral("ShadowColor"), 263 | m_shadowColor, 264 | QColor(33, 33, 33) 265 | ), QStringLiteral("ShadowColor")); 266 | 267 | //--- 268 | addConfig(skel, this); 269 | } 270 | 271 | } // namespace Material 272 | -------------------------------------------------------------------------------- /src/AppMenuModel.cc: -------------------------------------------------------------------------------- 1 | /****************************************************************** 2 | * Copyright 2016 Kai Uwe Broulik 3 | * Copyright 2016 Chinmoy Ranjan Pradhan 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License as 7 | * published by the Free Software Foundation; either version 2 of 8 | * the License or (at your option) version 3 or any later version 9 | * accepted by the membership of KDE e.V. (or its successor approved 10 | * by the membership of KDE e.V.), which shall act as a proxy 11 | * defined in Section 14 of version 3 of the license. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program. If not, see . 20 | * 21 | ******************************************************************/ 22 | 23 | // Based on: 24 | // https://invent.kde.org/plasma/plasma-workspace/-/blob/master/applets/appmenu/plugin/appmenumodel.cpp 25 | // https://github.com/psifidotos/applet-window-appmenu/blob/master/plugin/appmenumodel.cpp 26 | 27 | // own 28 | #include "AppMenuModel.h" 29 | #include "Material.h" 30 | #include "BuildConfig.h" 31 | 32 | // KF 33 | #include 34 | // In KF5 5.101, KWindowSystem moved several signals to KX11Extras 35 | // Eg: https://invent.kde.org/frameworks/kwindowsystem/-/commit/7cfd7c36eb017242d7a0202db82895be6b8fb81c 36 | #if HAVE_KF5_101 // KX11Extras 37 | #include 38 | #endif 39 | 40 | // Qt 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | 49 | // libdbusmenuqt 50 | #include 51 | 52 | #if HAVE_X11 53 | #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 54 | #include 55 | #else 56 | #include 57 | #endif 58 | #include 59 | #endif 60 | 61 | 62 | namespace Material 63 | { 64 | 65 | static const QByteArray s_x11AppMenuServiceNamePropertyName = QByteArrayLiteral("_KDE_NET_WM_APPMENU_SERVICE_NAME"); 66 | static const QByteArray s_x11AppMenuObjectPathPropertyName = QByteArrayLiteral("_KDE_NET_WM_APPMENU_OBJECT_PATH"); 67 | 68 | #if HAVE_X11 69 | static QHash s_atoms; 70 | #endif 71 | 72 | class KDBusMenuImporter : public DBusMenuImporter 73 | { 74 | 75 | public: 76 | KDBusMenuImporter(const QString &service, const QString &path, QObject *parent) 77 | : DBusMenuImporter(service, path, parent) { 78 | 79 | } 80 | 81 | protected: 82 | QIcon iconForName(const QString &name) override { 83 | return QIcon::fromTheme(name); 84 | } 85 | 86 | }; 87 | 88 | AppMenuModel::AppMenuModel(QObject *parent) 89 | : QAbstractListModel(parent), 90 | m_serviceWatcher(new QDBusServiceWatcher(this)) 91 | { 92 | if (KWindowSystem::isPlatformX11()) { 93 | #if HAVE_X11 94 | x11Init(); 95 | #else 96 | // Not compiled with X11 97 | return; 98 | #endif 99 | 100 | } else if (KWindowSystem::isPlatformWayland()) { 101 | #if HAVE_Wayland 102 | // TODO 103 | // waylandInit(); 104 | return; 105 | #else 106 | // Not compiled with KWayland 107 | return; 108 | #endif 109 | 110 | } else { 111 | // Not X11 or Wayland 112 | return; 113 | } 114 | 115 | m_serviceWatcher->setConnection(QDBusConnection::sessionBus()); 116 | // If our current DBus connection gets lost, close the menu 117 | // we'll select the new menu when the focus changes 118 | connect(m_serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, [this](const QString & serviceName) { 119 | if (serviceName == m_serviceName) { 120 | setMenuAvailable(false); 121 | emit modelNeedsUpdate(); 122 | } 123 | }); 124 | } 125 | 126 | AppMenuModel::~AppMenuModel() = default; 127 | 128 | void AppMenuModel::x11Init() 129 | { 130 | #if HAVE_X11 131 | connect(this, &AppMenuModel::winIdChanged, 132 | this, &AppMenuModel::onWinIdChanged); 133 | 134 | // In KF5 5.101, KWindowSystem moved several signals to KX11Extras 135 | // Eg: https://invent.kde.org/frameworks/kwindowsystem/-/commit/7cfd7c36eb017242d7a0202db82895be6b8fb81c 136 | #if HAVE_KF5_101 // KX11Extras 137 | // Select non-deprecated overloaded method. Uses coding pattern from: 138 | // https://invent.kde.org/plasma/plasma-workspace/blame/master/libtaskmanager/xwindowsystemeventbatcher.cpp#L30 139 | void (KX11Extras::*myWindowChangeSignal)(WId window, NET::Properties properties, NET::Properties2 properties2) = &KX11Extras::windowChanged; 140 | connect(KX11Extras::self(), myWindowChangeSignal, 141 | this, &AppMenuModel::onX11WindowChanged); 142 | 143 | // There are apps that are not releasing their menu properly after closing 144 | // and as such their menu is still shown even though the app does not exist 145 | // any more. Such apps are Java based e.g. smartgit 146 | connect(KX11Extras::self(), &KX11Extras::windowRemoved, 147 | this, &AppMenuModel::onX11WindowRemoved); 148 | #else // KF5 5.100 KWindowSystem 149 | void (KWindowSystem::*myWindowChangeSignal)(WId window, NET::Properties properties, NET::Properties2 properties2) = &KWindowSystem::windowChanged; 150 | connect(KWindowSystem::self(), myWindowChangeSignal, 151 | this, &AppMenuModel::onX11WindowChanged); 152 | connect(KWindowSystem::self(), &KWindowSystem::windowRemoved, 153 | this, &AppMenuModel::onX11WindowRemoved); 154 | #endif 155 | 156 | connect(this, &AppMenuModel::modelNeedsUpdate, this, [this] { 157 | if (!m_updatePending) { 158 | m_updatePending = true; 159 | QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection); 160 | } 161 | }); 162 | #endif 163 | } 164 | 165 | void AppMenuModel::waylandInit() 166 | { 167 | #if HAVE_Wayland 168 | // TODO 169 | #endif 170 | } 171 | 172 | bool AppMenuModel::menuAvailable() const 173 | { 174 | return m_menuAvailable; 175 | } 176 | 177 | void AppMenuModel::setMenuAvailable(bool set) 178 | { 179 | if (m_menuAvailable != set) { 180 | m_menuAvailable = set; 181 | emit menuAvailableChanged(); 182 | } 183 | } 184 | 185 | QVariant AppMenuModel::winId() const 186 | { 187 | return m_winId; 188 | } 189 | 190 | void AppMenuModel::setWinId(const QVariant &id) 191 | { 192 | if (m_winId == id) { 193 | return; 194 | } 195 | qCDebug(category) << "AppMenuModel::setWinId" << m_winId << " => " << id; 196 | m_winId = id; 197 | emit winIdChanged(); 198 | } 199 | 200 | int AppMenuModel::rowCount(const QModelIndex &parent) const 201 | { 202 | Q_UNUSED(parent); 203 | 204 | if (!m_menuAvailable || !m_menu) { 205 | return 0; 206 | } 207 | 208 | return m_menu->actions().count(); 209 | } 210 | 211 | void AppMenuModel::update() 212 | { 213 | // qCDebug(category) << "AppMenuModel::update (" << m_winId << ")"; 214 | beginResetModel(); 215 | endResetModel(); 216 | m_updatePending = false; 217 | } 218 | 219 | 220 | void AppMenuModel::onWinIdChanged() 221 | { 222 | 223 | if (KWindowSystem::isPlatformX11()) { 224 | #if HAVE_X11 225 | 226 | qApp->removeNativeEventFilter(this); 227 | 228 | const WId id = m_winId.toUInt(); 229 | 230 | if (!id) { 231 | setMenuAvailable(false); 232 | emit modelNeedsUpdate(); 233 | return; 234 | } 235 | 236 | auto *c = QX11Info::connection(); 237 | 238 | auto getWindowPropertyString = [c](WId id, const QByteArray &name) -> QByteArray { 239 | QByteArray value; 240 | 241 | if (!s_atoms.contains(name)) 242 | { 243 | const xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom(c, false, name.length(), name.constData()); 244 | QScopedPointer atomReply(xcb_intern_atom_reply(c, atomCookie, nullptr)); 245 | 246 | if (atomReply.isNull()) { 247 | return value; 248 | } 249 | 250 | s_atoms[name] = atomReply->atom; 251 | 252 | if (s_atoms[name] == XCB_ATOM_NONE) { 253 | return value; 254 | } 255 | } 256 | 257 | static const long MAX_PROP_SIZE = 10000; 258 | auto propertyCookie = xcb_get_property(c, false, id, s_atoms[name], XCB_ATOM_STRING, 0, MAX_PROP_SIZE); 259 | QScopedPointer propertyReply(xcb_get_property_reply(c, propertyCookie, nullptr)); 260 | 261 | if (propertyReply.isNull()) 262 | { 263 | return value; 264 | } 265 | 266 | if (propertyReply->type == XCB_ATOM_STRING && propertyReply->format == 8 && propertyReply->value_len > 0) 267 | { 268 | const char *data = (const char *) xcb_get_property_value(propertyReply.data()); 269 | int len = propertyReply->value_len; 270 | 271 | if (data) { 272 | value = QByteArray(data, data[len - 1] ? len : len - 1); 273 | } 274 | } 275 | 276 | return value; 277 | }; 278 | 279 | auto updateMenuFromWindowIfHasMenu = [this, &getWindowPropertyString](WId id) { 280 | const QString serviceName = QString::fromUtf8(getWindowPropertyString(id, s_x11AppMenuServiceNamePropertyName)); 281 | const QString menuObjectPath = QString::fromUtf8(getWindowPropertyString(id, s_x11AppMenuObjectPathPropertyName)); 282 | 283 | if (!serviceName.isEmpty() && !menuObjectPath.isEmpty()) { 284 | updateApplicationMenu(serviceName, menuObjectPath); 285 | return true; 286 | } 287 | 288 | return false; 289 | }; 290 | 291 | if (updateMenuFromWindowIfHasMenu(id)) { 292 | return; 293 | } 294 | 295 | // monitor whether an app menu becomes available later 296 | // this can happen when an app starts, shows its window, and only later announces global menu (e.g. Firefox) 297 | qApp->installNativeEventFilter(this); 298 | m_delayedMenuWindowId = id; 299 | 300 | //no menu found, set it to unavailable 301 | setMenuAvailable(false); 302 | emit modelNeedsUpdate(); 303 | #endif 304 | 305 | } else if (KWindowSystem::isPlatformWayland()) { 306 | #if HAVE_Wayland 307 | // TODO 308 | #endif 309 | } 310 | } 311 | 312 | void AppMenuModel::onX11WindowChanged(WId id) 313 | { 314 | if (m_winId.toUInt() == id) { 315 | 316 | } 317 | } 318 | 319 | void AppMenuModel::onX11WindowRemoved(WId id) 320 | { 321 | if (m_winId.toUInt() == id) { 322 | setMenuAvailable(false); 323 | } 324 | } 325 | 326 | QHash AppMenuModel::roleNames() const 327 | { 328 | QHash roleNames; 329 | roleNames[MenuRole] = QByteArrayLiteral("activeMenu"); 330 | roleNames[ActionRole] = QByteArrayLiteral("activeActions"); 331 | return roleNames; 332 | } 333 | 334 | QVariant AppMenuModel::data(const QModelIndex &index, int role) const 335 | { 336 | const int row = index.row(); 337 | 338 | if (row < 0 || !m_menuAvailable || !m_menu) { 339 | return QVariant(); 340 | } 341 | 342 | const auto actions = m_menu->actions(); 343 | 344 | if (row >= actions.count()) { 345 | return QVariant(); 346 | } 347 | 348 | if (role == MenuRole) { // TODO this should be Qt::DisplayRole 349 | return actions.at(row)->text(); 350 | } else if (role == ActionRole) { 351 | return QVariant::fromValue((void *) actions.at(row)); 352 | } 353 | 354 | return QVariant(); 355 | } 356 | 357 | void AppMenuModel::updateApplicationMenu(const QString &serviceName, const QString &menuObjectPath) 358 | { 359 | if (m_serviceName == serviceName && m_menuObjectPath == menuObjectPath) { 360 | if (m_importer) { 361 | QMetaObject::invokeMethod(m_importer, "updateMenu", Qt::QueuedConnection); 362 | } 363 | return; 364 | } 365 | 366 | m_serviceName = serviceName; 367 | m_serviceWatcher->setWatchedServices(QStringList({m_serviceName})); 368 | 369 | m_menuObjectPath = menuObjectPath; 370 | 371 | if (m_importer) { 372 | m_importer->deleteLater(); 373 | } 374 | 375 | m_importer = new KDBusMenuImporter(serviceName, menuObjectPath, this); 376 | QMetaObject::invokeMethod(m_importer, "updateMenu", Qt::QueuedConnection); 377 | 378 | connect(m_importer.data(), &DBusMenuImporter::menuUpdated, this, [=](QMenu *menu) { 379 | m_menu = m_importer->menu(); 380 | if (m_menu.isNull() || menu != m_menu) { 381 | return; 382 | } 383 | 384 | // cache first layer of sub menus, which we'll be popping up 385 | const auto actions = m_menu->actions(); 386 | for (QAction *a : actions) { 387 | // signal dataChanged when the action changes 388 | connect(a, &QAction::changed, this, [this, a] { 389 | if (m_menuAvailable && m_menu) { 390 | const int actionIdx = m_menu->actions().indexOf(a); 391 | if (actionIdx > -1) { 392 | const QModelIndex modelIdx = index(actionIdx, 0); 393 | emit dataChanged(modelIdx, modelIdx); 394 | } 395 | } 396 | }); 397 | 398 | connect(a, &QAction::destroyed, this, &AppMenuModel::modelNeedsUpdate); 399 | 400 | if (a->menu()) { 401 | m_importer->updateMenu(a->menu()); 402 | } 403 | } 404 | 405 | setMenuAvailable(true); 406 | emit modelNeedsUpdate(); 407 | }); 408 | 409 | connect(m_importer.data(), &DBusMenuImporter::actionActivationRequested, this, [this](QAction *action) { 410 | // TODO submenus 411 | if (!m_menuAvailable || !m_menu) { 412 | return; 413 | } 414 | 415 | const auto actions = m_menu->actions(); 416 | auto it = std::find(actions.begin(), actions.end(), action); 417 | if (it != actions.end()) { 418 | emit requestActivateIndex(it - actions.begin()); 419 | } 420 | }); 421 | } 422 | 423 | bool AppMenuModel::nativeEventFilter(const QByteArray &eventType, void *message, long *result) 424 | { 425 | Q_UNUSED(result); 426 | 427 | if (!KWindowSystem::isPlatformX11() || eventType != "xcb_generic_event_t") { 428 | return false; 429 | } 430 | 431 | #if HAVE_X11 432 | auto e = static_cast(message); 433 | const uint8_t type = e->response_type & ~0x80; 434 | 435 | if (type == XCB_PROPERTY_NOTIFY) { 436 | auto *event = reinterpret_cast(e); 437 | 438 | if (event->window == m_delayedMenuWindowId) { 439 | 440 | auto serviceNameAtom = s_atoms.value(s_x11AppMenuServiceNamePropertyName); 441 | auto objectPathAtom = s_atoms.value(s_x11AppMenuObjectPathPropertyName); 442 | 443 | if (serviceNameAtom != XCB_ATOM_NONE && objectPathAtom != XCB_ATOM_NONE) { // shouldn't happen 444 | if (event->atom == serviceNameAtom || event->atom == objectPathAtom) { 445 | // see if we now have a menu 446 | onWinIdChanged(); 447 | } 448 | } 449 | } 450 | } 451 | 452 | #else 453 | Q_UNUSED(message); 454 | #endif 455 | 456 | return false; 457 | } 458 | 459 | } // namespace Material 460 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /src/Button.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Chris Holland 3 | * Copyright (C) 2018 Vlad Zagorodniy 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | // own 20 | #include "Button.h" 21 | #include "Material.h" 22 | #include "Decoration.h" 23 | 24 | #include "AppIconButton.h" 25 | #include "ApplicationMenuButton.h" 26 | #include "OnAllDesktopsButton.h" 27 | #include "ContextHelpButton.h" 28 | #include "ShadeButton.h" 29 | #include "KeepAboveButton.h" 30 | #include "KeepBelowButton.h" 31 | #include "CloseButton.h" 32 | #include "MaximizeButton.h" 33 | #include "MinimizeButton.h" 34 | 35 | // KDecoration 36 | #include 37 | #include 38 | #include 39 | 40 | // KF 41 | #include 42 | 43 | // Qt 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include // qFloor 49 | 50 | 51 | namespace Material 52 | { 53 | 54 | Button::Button(KDecoration2::DecorationButtonType type, Decoration *decoration, QObject *parent) 55 | : DecorationButton(type, decoration, parent) 56 | , m_animationEnabled(true) 57 | , m_animation(new QVariantAnimation(this)) 58 | , m_opacity(1) 59 | , m_transitionValue(0) 60 | , m_padding(new QMargins()) 61 | , m_isGtkButton(false) 62 | { 63 | connect(this, &Button::hoveredChanged, this, 64 | [this](bool hovered) { 65 | updateAnimationState(hovered); 66 | update(); 67 | }); 68 | 69 | if (QCoreApplication::applicationName() == QStringLiteral("kded5")) { 70 | // See: https://github.com/Zren/material-decoration/issues/22 71 | // kde-gtk-config has a kded5 module which renders the buttons to svgs for gtk. 72 | m_isGtkButton = true; 73 | } 74 | 75 | // Animation based on SierraBreezeEnhanced 76 | // https://github.com/kupiqu/SierraBreezeEnhanced/blob/master/breezebutton.cpp#L45 77 | // The GTK bridge needs animations disabled to render hover states. See Issue #50. 78 | // https://invent.kde.org/plasma/kde-gtk-config/-/blob/master/kded/kwin_bridge/dummydecorationbridge.cpp#L35 79 | m_animationEnabled = !m_isGtkButton && decoration->animationsEnabled(); 80 | m_animation->setDuration(decoration->animationsDuration()); 81 | m_animation->setStartValue(0.0); 82 | m_animation->setEndValue(1.0); 83 | m_animation->setEasingCurve(QEasingCurve::InOutQuad); 84 | connect(m_animation, &QVariantAnimation::valueChanged, this, [this](const QVariant &value) { 85 | setTransitionValue(value.toReal()); 86 | }); 87 | connect(this, &Button::transitionValueChanged, this, [this]() { 88 | update(); 89 | }); 90 | 91 | connect(this, &Button::opacityChanged, this, [this]() { 92 | update(); 93 | }); 94 | 95 | setHeight(decoration->titleBarHeight()); 96 | 97 | auto *decoratedClient = decoration->client().toStrongRef().data(); 98 | 99 | switch (type) { 100 | case KDecoration2::DecorationButtonType::Menu: 101 | AppIconButton::init(this, decoratedClient); 102 | break; 103 | 104 | case KDecoration2::DecorationButtonType::ApplicationMenu: 105 | ApplicationMenuButton::init(this, decoratedClient); 106 | break; 107 | 108 | case KDecoration2::DecorationButtonType::OnAllDesktops: 109 | OnAllDesktopsButton::init(this, decoratedClient); 110 | break; 111 | 112 | case KDecoration2::DecorationButtonType::ContextHelp: 113 | ContextHelpButton::init(this, decoratedClient); 114 | break; 115 | 116 | case KDecoration2::DecorationButtonType::Shade: 117 | ShadeButton::init(this, decoratedClient); 118 | break; 119 | 120 | case KDecoration2::DecorationButtonType::KeepAbove: 121 | KeepAboveButton::init(this, decoratedClient); 122 | break; 123 | 124 | case KDecoration2::DecorationButtonType::KeepBelow: 125 | KeepBelowButton::init(this, decoratedClient); 126 | break; 127 | 128 | case KDecoration2::DecorationButtonType::Close: 129 | CloseButton::init(this, decoratedClient); 130 | break; 131 | 132 | case KDecoration2::DecorationButtonType::Maximize: 133 | MaximizeButton::init(this, decoratedClient); 134 | break; 135 | 136 | case KDecoration2::DecorationButtonType::Minimize: 137 | MinimizeButton::init(this, decoratedClient); 138 | break; 139 | 140 | default: 141 | break; 142 | } 143 | } 144 | 145 | Button::~Button() 146 | { 147 | } 148 | 149 | KDecoration2::DecorationButton* Button::create(KDecoration2::DecorationButtonType type, KDecoration2::Decoration *decoration, QObject *parent) 150 | { 151 | auto deco = qobject_cast(decoration); 152 | if (!deco) { 153 | return nullptr; 154 | } 155 | 156 | switch (type) { 157 | case KDecoration2::DecorationButtonType::Menu: 158 | // case KDecoration2::DecorationButtonType::ApplicationMenu: 159 | case KDecoration2::DecorationButtonType::OnAllDesktops: 160 | case KDecoration2::DecorationButtonType::ContextHelp: 161 | case KDecoration2::DecorationButtonType::Shade: 162 | case KDecoration2::DecorationButtonType::KeepAbove: 163 | case KDecoration2::DecorationButtonType::KeepBelow: 164 | case KDecoration2::DecorationButtonType::Close: 165 | case KDecoration2::DecorationButtonType::Maximize: 166 | case KDecoration2::DecorationButtonType::Minimize: 167 | return new Button(type, deco, parent); 168 | 169 | default: 170 | return nullptr; 171 | } 172 | } 173 | 174 | Button::Button(QObject *parent, const QVariantList &args) 175 | : Button(args.at(0).value(), args.at(1).value(), parent) 176 | { 177 | } 178 | 179 | void Button::paint(QPainter *painter, const QRect &repaintRegion) 180 | { 181 | Q_UNUSED(repaintRegion) 182 | 183 | // Buttons are coded assuming 24 units in size. 184 | const QRectF buttonRect = geometry(); 185 | const QRectF contentRect = contentArea(); 186 | 187 | const qreal iconScale = contentRect.height()/24; 188 | int iconSize; 189 | if (m_isGtkButton) { 190 | // See: https://github.com/Zren/material-decoration/issues/22 191 | // kde-gtk-config has a kded5 module which renders the buttons to svgs for gtk. 192 | 193 | // The svgs are 50x50, located at ~/.config/gtk-3.0/assets/ 194 | // They are usually scaled down to just 18x18 when drawn in gtk headerbars. 195 | // The Gtk theme already has a fairly large amount of padding, as 196 | // the Breeze theme doesn't currently follow fitt's law. So use less padding 197 | // around the icon so that the icon is not a very tiny 8px. 198 | 199 | // 15% top/bottom padding, 70% leftover for the icon. 200 | // 24 = 3.5 topPadding + 17 icon + 3.5 bottomPadding 201 | // 17/24 * 18 = 12.75 202 | iconSize = qRound(iconScale * 17); 203 | } else { 204 | // 30% top/bottom padding, 40% leftover for the icon. 205 | // 24 = 7 topPadding + 10 icon + 7 bottomPadding 206 | iconSize = qRound(iconScale * 10); 207 | } 208 | QRectF iconRect = QRectF(0, 0, iconSize, iconSize); 209 | iconRect.moveCenter(contentRect.center().toPoint()); 210 | 211 | const qreal gridUnit = iconRect.height()/10; 212 | 213 | painter->save(); 214 | 215 | painter->setRenderHints(QPainter::Antialiasing, false); 216 | 217 | // Opacity 218 | painter->setOpacity(m_opacity); 219 | 220 | // Background. 221 | painter->setPen(Qt::NoPen); 222 | painter->setBrush(backgroundColor()); 223 | painter->drawRect(buttonRect); 224 | 225 | // Foreground. 226 | setPenWidth(painter, gridUnit, 1); 227 | painter->setBrush(Qt::NoBrush); 228 | 229 | 230 | // Icon 231 | switch (type()) { 232 | case KDecoration2::DecorationButtonType::Menu: 233 | AppIconButton::paintIcon(this, painter, iconRect, gridUnit); 234 | break; 235 | 236 | case KDecoration2::DecorationButtonType::ApplicationMenu: 237 | ApplicationMenuButton::paintIcon(this, painter, iconRect, gridUnit); 238 | break; 239 | 240 | case KDecoration2::DecorationButtonType::OnAllDesktops: 241 | OnAllDesktopsButton::paintIcon(this, painter, iconRect, gridUnit); 242 | break; 243 | 244 | case KDecoration2::DecorationButtonType::ContextHelp: 245 | ContextHelpButton::paintIcon(this, painter, iconRect, gridUnit); 246 | break; 247 | 248 | case KDecoration2::DecorationButtonType::Shade: 249 | ShadeButton::paintIcon(this, painter, iconRect, gridUnit); 250 | break; 251 | 252 | case KDecoration2::DecorationButtonType::KeepAbove: 253 | KeepAboveButton::paintIcon(this, painter, iconRect, gridUnit); 254 | break; 255 | 256 | case KDecoration2::DecorationButtonType::KeepBelow: 257 | KeepBelowButton::paintIcon(this, painter, iconRect, gridUnit); 258 | break; 259 | 260 | case KDecoration2::DecorationButtonType::Close: 261 | CloseButton::paintIcon(this, painter, iconRect, gridUnit); 262 | break; 263 | 264 | case KDecoration2::DecorationButtonType::Maximize: 265 | MaximizeButton::paintIcon(this, painter, iconRect, gridUnit); 266 | break; 267 | 268 | case KDecoration2::DecorationButtonType::Minimize: 269 | MinimizeButton::paintIcon(this, painter, iconRect, gridUnit); 270 | break; 271 | 272 | default: 273 | paintIcon(painter, iconRect, gridUnit); 274 | break; 275 | } 276 | 277 | painter->restore(); 278 | } 279 | 280 | void Button::paintIcon(QPainter *painter, const QRectF &iconRect, const qreal gridUnit) 281 | { 282 | Q_UNUSED(painter) 283 | Q_UNUSED(iconRect) 284 | Q_UNUSED(gridUnit) 285 | } 286 | 287 | void Button::updateSize(int contentWidth, int contentHeight) 288 | { 289 | const QSize size( 290 | m_padding->left() + contentWidth + m_padding->right(), 291 | m_padding->top() + contentHeight + m_padding->bottom() 292 | ); 293 | setGeometry(QRect(geometry().topLeft().toPoint(), size)); 294 | } 295 | 296 | void Button::setHeight(int buttonHeight) 297 | { 298 | // For simplicity, don't count the 1.33:1 scaling in the left/right padding. 299 | // The left/right padding is mainly for the border offset alignment. 300 | updateSize(qRound(buttonHeight * 1.33), buttonHeight); 301 | } 302 | 303 | qreal Button::iconLineWidth(const qreal gridUnit) const 304 | { 305 | return PenWidth::Symbol * qMax(1.0, gridUnit); 306 | } 307 | 308 | void Button::setPenWidth(QPainter *painter, const qreal gridUnit, const qreal scale) 309 | { 310 | QPen pen(foregroundColor()); 311 | pen.setCapStyle(Qt::RoundCap); 312 | pen.setJoinStyle(Qt::MiterJoin); 313 | pen.setWidthF(iconLineWidth(gridUnit) * scale); 314 | painter->setPen(pen); 315 | } 316 | 317 | QColor Button::backgroundColor() const 318 | { 319 | const auto *deco = qobject_cast(decoration()); 320 | if (!deco) { 321 | return {}; 322 | } 323 | 324 | if (m_isGtkButton) { 325 | // Breeze GTK has huge margins around the button. It looks better 326 | // when we just change the fgColor on hover instead of the bgColor. 327 | return Qt::transparent; 328 | } 329 | 330 | //--- CloseButton 331 | if (type() == KDecoration2::DecorationButtonType::Close) { 332 | auto *decoratedClient = deco->client().toStrongRef().data(); 333 | const QColor hoveredColor = decoratedClient->color( 334 | KDecoration2::ColorGroup::Warning, 335 | KDecoration2::ColorRole::Foreground 336 | ); 337 | QColor normalColor = QColor(hoveredColor); 338 | normalColor.setAlphaF(0); 339 | 340 | if (isPressed()) { 341 | const QColor pressedColor = decoratedClient->color( 342 | KDecoration2::ColorGroup::Warning, 343 | KDecoration2::ColorRole::Foreground 344 | ).lighter(); 345 | return KColorUtils::mix(normalColor, pressedColor, m_transitionValue); 346 | } 347 | 348 | if (isHovered()) { 349 | return KColorUtils::mix(normalColor, hoveredColor, m_transitionValue); 350 | } 351 | } 352 | 353 | //--- Checked 354 | if (isChecked() && type() != KDecoration2::DecorationButtonType::Maximize) { 355 | const QColor normalColor = deco->titleBarForegroundColor(); 356 | 357 | if (isPressed()) { 358 | const QColor pressedColor = KColorUtils::mix( 359 | deco->titleBarBackgroundColor(), 360 | deco->titleBarForegroundColor(), 361 | 0.7); 362 | return KColorUtils::mix(normalColor, pressedColor, m_transitionValue); 363 | } 364 | if (isHovered()) { 365 | const QColor hoveredColor = KColorUtils::mix( 366 | deco->titleBarBackgroundColor(), 367 | deco->titleBarForegroundColor(), 368 | 0.8); 369 | return KColorUtils::mix(normalColor, hoveredColor, m_transitionValue); 370 | } 371 | return normalColor; 372 | } 373 | 374 | //--- Normal 375 | const QColor hoveredColor = KColorUtils::mix( 376 | deco->titleBarBackgroundColor(), 377 | deco->titleBarForegroundColor(), 378 | 0.2); 379 | QColor normalColor = QColor(hoveredColor); 380 | normalColor.setAlphaF(0); 381 | 382 | if (isPressed()) { 383 | const QColor pressedColor = KColorUtils::mix( 384 | deco->titleBarBackgroundColor(), 385 | deco->titleBarForegroundColor(), 386 | 0.3); 387 | return KColorUtils::mix(normalColor, pressedColor, m_transitionValue); 388 | } 389 | if (isHovered()) { 390 | return KColorUtils::mix(normalColor, hoveredColor, m_transitionValue); 391 | } 392 | return normalColor; 393 | } 394 | 395 | QColor Button::foregroundColor() const 396 | { 397 | const auto *deco = qobject_cast(decoration()); 398 | if (!deco) { 399 | return {}; 400 | } 401 | 402 | //--- Checked 403 | if (isChecked() && type() != KDecoration2::DecorationButtonType::Maximize) { 404 | const QColor activeColor = KColorUtils::mix( 405 | deco->titleBarBackgroundColor(), 406 | deco->titleBarForegroundColor(), 407 | 0.2); 408 | 409 | if (isPressed() || isHovered()) { 410 | return KColorUtils::mix( 411 | activeColor, 412 | deco->titleBarBackgroundColor(), 413 | m_transitionValue); 414 | } 415 | return activeColor; 416 | } 417 | 418 | //--- Normal 419 | const QColor normalColor = KColorUtils::mix( 420 | deco->titleBarBackgroundColor(), 421 | deco->titleBarForegroundColor(), 422 | 0.8); 423 | 424 | if (isPressed() || isHovered()) { 425 | // Breeze GTK has huge margins around the button. It looks better 426 | // when we just change the fgColor on hover instead of the bgColor. 427 | QColor hoveredColor; 428 | if (m_isGtkButton && type() == KDecoration2::DecorationButtonType::Close) { 429 | auto *decoratedClient = deco->client().toStrongRef().data(); 430 | hoveredColor = decoratedClient->color( 431 | KDecoration2::ColorGroup::Warning, 432 | KDecoration2::ColorRole::Foreground 433 | ); 434 | } else if (m_isGtkButton && type() == KDecoration2::DecorationButtonType::Maximize) { 435 | const int grayValue = qGray(deco->titleBarBackgroundColor().rgb()); 436 | if (grayValue < 128) { // Dark Bg 437 | hoveredColor = QColor(100, 196, 86); // from SierraBreeze 438 | } else { // Light Bg 439 | hoveredColor = QColor(40, 200, 64); // from SierraBreeze 440 | } 441 | } else if (m_isGtkButton && type() == KDecoration2::DecorationButtonType::Minimize) { 442 | const int grayValue = qGray(deco->titleBarBackgroundColor().rgb()); 443 | if (grayValue < 128) { 444 | hoveredColor = QColor(223, 192, 76); // from SierraBreeze 445 | } else { // Light Bg 446 | hoveredColor = QColor(255, 188, 48); // from SierraBreeze 447 | } 448 | } else { 449 | hoveredColor = deco->titleBarForegroundColor(); 450 | } 451 | 452 | return KColorUtils::mix( 453 | normalColor, 454 | hoveredColor, 455 | m_transitionValue); 456 | } 457 | 458 | return normalColor; 459 | } 460 | 461 | 462 | QRectF Button::contentArea() const 463 | { 464 | return geometry().adjusted( 465 | m_padding->left(), 466 | m_padding->top(), 467 | -m_padding->right(), 468 | -m_padding->bottom() 469 | ); 470 | } 471 | 472 | bool Button::animationEnabled() const 473 | { 474 | return m_animationEnabled; 475 | } 476 | 477 | void Button::setAnimationEnabled(bool value) 478 | { 479 | if (m_animationEnabled != value) { 480 | m_animationEnabled = value; 481 | emit animationEnabledChanged(); 482 | } 483 | } 484 | 485 | int Button::animationDuration() const 486 | { 487 | return m_animation->duration(); 488 | } 489 | 490 | void Button::setAnimationDuration(int value) 491 | { 492 | if (m_animation->duration() != value) { 493 | m_animation->setDuration(value); 494 | emit animationDurationChanged(); 495 | } 496 | } 497 | 498 | qreal Button::opacity() const 499 | { 500 | return m_opacity; 501 | } 502 | 503 | void Button::setOpacity(qreal value) 504 | { 505 | if (m_opacity != value) { 506 | m_opacity = value; 507 | emit opacityChanged(); 508 | } 509 | } 510 | 511 | qreal Button::transitionValue() const 512 | { 513 | return m_transitionValue; 514 | } 515 | 516 | void Button::setTransitionValue(qreal value) 517 | { 518 | if (m_transitionValue != value) { 519 | m_transitionValue = value; 520 | emit transitionValueChanged(value); 521 | } 522 | } 523 | 524 | QMargins* Button::padding() 525 | { 526 | return m_padding; 527 | } 528 | 529 | void Button::setHorzPadding(int value) 530 | { 531 | padding()->setLeft(value); 532 | padding()->setRight(value); 533 | } 534 | 535 | void Button::setVertPadding(int value) 536 | { 537 | padding()->setTop(value); 538 | padding()->setBottom(value); 539 | } 540 | 541 | void Button::updateAnimationState(bool hovered) 542 | { 543 | if (m_animationEnabled) { 544 | QAbstractAnimation::Direction dir = hovered ? QAbstractAnimation::Forward : QAbstractAnimation::Backward; 545 | if (m_animation->state() == QAbstractAnimation::Running && m_animation->direction() != dir) { 546 | m_animation->stop(); 547 | } 548 | m_animation->setDirection(dir); 549 | if (m_animation->state() != QAbstractAnimation::Running) { 550 | m_animation->start(); 551 | } 552 | } else { 553 | setTransitionValue(1); 554 | } 555 | } 556 | 557 | 558 | } // namespace Material 559 | -------------------------------------------------------------------------------- /src/libdbusmenuqt/dbusmenuimporter.cpp: -------------------------------------------------------------------------------- 1 | /* This file is part of the dbusmenu-qt library 2 | Copyright 2009 Canonical 3 | Author: Aurelien Gateau 4 | 5 | This library is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU Library General Public 7 | License (LGPL) as published by the Free Software Foundation; 8 | either version 2 of the License, or (at your option) any later 9 | version. 10 | 11 | This library is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | Library General Public License for more details. 15 | 16 | You should have received a copy of the GNU Library General Public License 17 | along with this library; see the file COPYING.LIB. If not, write to 18 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 19 | Boston, MA 02110-1301, USA. 20 | */ 21 | #include "dbusmenuimporter.h" 22 | 23 | #include "debug.h" 24 | 25 | // Qt 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | 41 | // Local 42 | #include "dbusmenushortcut_p.h" 43 | #include "dbusmenutypes_p.h" 44 | #include "utils_p.h" 45 | 46 | // Generated 47 | #include "dbusmenu_interface.h" 48 | 49 | //#define BENCHMARK 50 | #ifdef BENCHMARK 51 | static QTime sChrono; 52 | #endif 53 | 54 | #define DMRETURN_IF_FAIL(cond) \ 55 | if (!(cond)) { \ 56 | qCWarning(DBUSMENUQT) << "Condition failed: " #cond; \ 57 | return; \ 58 | } 59 | 60 | static const char *DBUSMENU_PROPERTY_ID = "_dbusmenu_id"; 61 | static const char *DBUSMENU_PROPERTY_ICON_NAME = "_dbusmenu_icon_name"; 62 | static const char *DBUSMENU_PROPERTY_ICON_DATA_HASH = "_dbusmenu_icon_data_hash"; 63 | 64 | static QAction *createKdeTitle(QAction *action, QWidget *parent) 65 | { 66 | QToolButton *titleWidget = new QToolButton(nullptr); 67 | QFont font = titleWidget->font(); 68 | font.setBold(true); 69 | titleWidget->setFont(font); 70 | titleWidget->setIcon(action->icon()); 71 | titleWidget->setText(action->text()); 72 | titleWidget->setDown(true); 73 | titleWidget->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); 74 | 75 | QWidgetAction *titleAction = new QWidgetAction(parent); 76 | titleAction->setDefaultWidget(titleWidget); 77 | return titleAction; 78 | } 79 | 80 | class DBusMenuImporterPrivate 81 | { 82 | public: 83 | DBusMenuImporter *q; 84 | 85 | DBusMenuInterface *m_interface; 86 | QMenu *m_menu; 87 | using ActionForId = QMap; 88 | ActionForId m_actionForId; 89 | QTimer *m_pendingLayoutUpdateTimer; 90 | 91 | QSet m_idsRefreshedByAboutToShow; 92 | QSet m_pendingLayoutUpdates; 93 | 94 | QDBusPendingCallWatcher *refresh(int id) 95 | { 96 | auto call = m_interface->GetLayout(id, 1, QStringList()); 97 | QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, q); 98 | watcher->setProperty(DBUSMENU_PROPERTY_ID, id); 99 | QObject::connect(watcher, &QDBusPendingCallWatcher::finished, q, &DBusMenuImporter::slotGetLayoutFinished); 100 | 101 | return watcher; 102 | } 103 | 104 | QMenu *createMenu(QWidget *parent) 105 | { 106 | QMenu *menu = q->createMenu(parent); 107 | return menu; 108 | } 109 | 110 | /** 111 | * Init all the immutable action properties here 112 | * TODO: Document immutable properties? 113 | * 114 | * Note: we remove properties we handle from the map (using QMap::take() 115 | * instead of QMap::value()) to avoid warnings about these properties in 116 | * updateAction() 117 | */ 118 | QAction *createAction(int id, const QVariantMap &_map, QWidget *parent) 119 | { 120 | QVariantMap map = _map; 121 | QAction *action = new QAction(parent); 122 | action->setProperty(DBUSMENU_PROPERTY_ID, id); 123 | 124 | QString type = map.take(QStringLiteral("type")).toString(); 125 | if (type == QLatin1String("separator")) { 126 | action->setSeparator(true); 127 | } 128 | 129 | if (map.take(QStringLiteral("children-display")).toString() == QLatin1String("submenu")) { 130 | QMenu *menu = createMenu(parent); 131 | action->setMenu(menu); 132 | } 133 | 134 | QString toggleType = map.take(QStringLiteral("toggle-type")).toString(); 135 | if (!toggleType.isEmpty()) { 136 | action->setCheckable(true); 137 | if (toggleType == QLatin1String("radio")) { 138 | QActionGroup *group = new QActionGroup(action); 139 | group->addAction(action); 140 | } 141 | } 142 | 143 | bool isKdeTitle = map.take(QStringLiteral("x-kde-title")).toBool(); 144 | updateAction(action, map, map.keys()); 145 | 146 | if (isKdeTitle) { 147 | action = createKdeTitle(action, parent); 148 | } 149 | 150 | return action; 151 | } 152 | 153 | /** 154 | * Update mutable properties of an action. A property may be listed in 155 | * requestedProperties but not in map, this means we should use the default value 156 | * for this property. 157 | * 158 | * @param action the action to update 159 | * @param map holds the property values 160 | * @param requestedProperties which properties has been requested 161 | */ 162 | void updateAction(QAction *action, const QVariantMap &map, const QStringList &requestedProperties) 163 | { 164 | Q_FOREACH (const QString &key, requestedProperties) { 165 | updateActionProperty(action, key, map.value(key)); 166 | } 167 | } 168 | 169 | void updateActionProperty(QAction *action, const QString &key, const QVariant &value) 170 | { 171 | if (key == QLatin1String("label")) { 172 | updateActionLabel(action, value); 173 | } else if (key == QLatin1String("enabled")) { 174 | updateActionEnabled(action, value); 175 | } else if (key == QLatin1String("toggle-state")) { 176 | updateActionChecked(action, value); 177 | } else if (key == QLatin1String("icon-name")) { 178 | updateActionIconByName(action, value); 179 | } else if (key == QLatin1String("icon-data")) { 180 | updateActionIconByData(action, value); 181 | } else if (key == QLatin1String("visible")) { 182 | updateActionVisible(action, value); 183 | } else if (key == QLatin1String("shortcut")) { 184 | updateActionShortcut(action, value); 185 | } else { 186 | qDebug(DBUSMENUQT) << "Unhandled property update" << key; 187 | } 188 | } 189 | 190 | void updateActionLabel(QAction *action, const QVariant &value) 191 | { 192 | QString text = swapMnemonicChar(value.toString(), '_', '&'); 193 | action->setText(text); 194 | } 195 | 196 | void updateActionEnabled(QAction *action, const QVariant &value) 197 | { 198 | action->setEnabled(value.isValid() ? value.toBool() : true); 199 | } 200 | 201 | void updateActionChecked(QAction *action, const QVariant &value) 202 | { 203 | if (action->isCheckable() && value.isValid()) { 204 | action->setChecked(value.toInt() == 1); 205 | } 206 | } 207 | 208 | void updateActionIconByName(QAction *action, const QVariant &value) 209 | { 210 | const QString iconName = value.toString(); 211 | const QString previous = action->property(DBUSMENU_PROPERTY_ICON_NAME).toString(); 212 | if (previous == iconName) { 213 | return; 214 | } 215 | action->setProperty(DBUSMENU_PROPERTY_ICON_NAME, iconName); 216 | if (iconName.isEmpty()) { 217 | action->setIcon(QIcon()); 218 | return; 219 | } 220 | action->setIcon(q->iconForName(iconName)); 221 | } 222 | 223 | void updateActionIconByData(QAction *action, const QVariant &value) 224 | { 225 | const QByteArray data = value.toByteArray(); 226 | uint dataHash = qHash(data); 227 | uint previousDataHash = action->property(DBUSMENU_PROPERTY_ICON_DATA_HASH).toUInt(); 228 | if (previousDataHash == dataHash) { 229 | return; 230 | } 231 | action->setProperty(DBUSMENU_PROPERTY_ICON_DATA_HASH, dataHash); 232 | QPixmap pix; 233 | if (!pix.loadFromData(data)) { 234 | qDebug(DBUSMENUQT) << "Failed to decode icon-data property for action" << action->text(); 235 | action->setIcon(QIcon()); 236 | return; 237 | } 238 | action->setIcon(QIcon(pix)); 239 | } 240 | 241 | void updateActionVisible(QAction *action, const QVariant &value) 242 | { 243 | action->setVisible(value.isValid() ? value.toBool() : true); 244 | } 245 | 246 | void updateActionShortcut(QAction *action, const QVariant &value) 247 | { 248 | QDBusArgument arg = value.value(); 249 | DBusMenuShortcut dmShortcut; 250 | arg >> dmShortcut; 251 | QKeySequence keySequence = dmShortcut.toKeySequence(); 252 | action->setShortcut(keySequence); 253 | } 254 | 255 | QMenu *menuForId(int id) const 256 | { 257 | if (id == 0) { 258 | return q->menu(); 259 | } 260 | QAction *action = m_actionForId.value(id); 261 | if (!action) { 262 | return nullptr; 263 | } 264 | return action->menu(); 265 | } 266 | 267 | void slotItemsPropertiesUpdated(const DBusMenuItemList &updatedList, const DBusMenuItemKeysList &removedList); 268 | 269 | void sendEvent(int id, const QString &eventId) 270 | { 271 | m_interface->Event(id, eventId, QDBusVariant(QString()), 0u); 272 | } 273 | }; 274 | 275 | DBusMenuImporter::DBusMenuImporter(const QString &service, const QString &path, QObject *parent) 276 | : QObject(parent) 277 | , d(new DBusMenuImporterPrivate) 278 | { 279 | DBusMenuTypes_register(); 280 | 281 | d->q = this; 282 | d->m_interface = new DBusMenuInterface(service, path, QDBusConnection::sessionBus(), this); 283 | d->m_menu = nullptr; 284 | 285 | d->m_pendingLayoutUpdateTimer = new QTimer(this); 286 | d->m_pendingLayoutUpdateTimer->setSingleShot(true); 287 | connect(d->m_pendingLayoutUpdateTimer, &QTimer::timeout, this, &DBusMenuImporter::processPendingLayoutUpdates); 288 | 289 | connect(d->m_interface, &DBusMenuInterface::LayoutUpdated, this, &DBusMenuImporter::slotLayoutUpdated); 290 | connect(d->m_interface, &DBusMenuInterface::ItemActivationRequested, this, &DBusMenuImporter::slotItemActivationRequested); 291 | connect(d->m_interface, 292 | &DBusMenuInterface::ItemsPropertiesUpdated, 293 | this, 294 | [this](const DBusMenuItemList &updatedList, const DBusMenuItemKeysList &removedList) { 295 | d->slotItemsPropertiesUpdated(updatedList, removedList); 296 | }); 297 | 298 | d->refresh(0); 299 | } 300 | 301 | DBusMenuImporter::~DBusMenuImporter() 302 | { 303 | // Do not use "delete d->m_menu": even if we are being deleted we should 304 | // leave enough time for the menu to finish what it was doing, for example 305 | // if it was being displayed. 306 | d->m_menu->deleteLater(); 307 | delete d; 308 | } 309 | 310 | void DBusMenuImporter::slotLayoutUpdated(uint revision, int parentId) 311 | { 312 | Q_UNUSED(revision) 313 | if (d->m_idsRefreshedByAboutToShow.remove(parentId)) { 314 | return; 315 | } 316 | d->m_pendingLayoutUpdates << parentId; 317 | if (!d->m_pendingLayoutUpdateTimer->isActive()) { 318 | d->m_pendingLayoutUpdateTimer->start(); 319 | } 320 | } 321 | 322 | void DBusMenuImporter::processPendingLayoutUpdates() 323 | { 324 | QSet ids = d->m_pendingLayoutUpdates; 325 | d->m_pendingLayoutUpdates.clear(); 326 | Q_FOREACH (int id, ids) { 327 | d->refresh(id); 328 | } 329 | } 330 | 331 | QMenu *DBusMenuImporter::menu() const 332 | { 333 | if (!d->m_menu) { 334 | d->m_menu = d->createMenu(nullptr); 335 | } 336 | return d->m_menu; 337 | } 338 | 339 | void DBusMenuImporterPrivate::slotItemsPropertiesUpdated(const DBusMenuItemList &updatedList, const DBusMenuItemKeysList &removedList) 340 | { 341 | Q_FOREACH (const DBusMenuItem &item, updatedList) { 342 | QAction *action = m_actionForId.value(item.id); 343 | if (!action) { 344 | // We don't know this action. It probably is in a menu we haven't fetched yet. 345 | continue; 346 | } 347 | 348 | QVariantMap::ConstIterator it = item.properties.constBegin(), end = item.properties.constEnd(); 349 | for (; it != end; ++it) { 350 | updateActionProperty(action, it.key(), it.value()); 351 | } 352 | } 353 | 354 | Q_FOREACH (const DBusMenuItemKeys &item, removedList) { 355 | QAction *action = m_actionForId.value(item.id); 356 | if (!action) { 357 | // We don't know this action. It probably is in a menu we haven't fetched yet. 358 | continue; 359 | } 360 | 361 | Q_FOREACH (const QString &key, item.properties) { 362 | updateActionProperty(action, key, QVariant()); 363 | } 364 | } 365 | } 366 | 367 | QAction *DBusMenuImporter::actionForId(int id) const 368 | { 369 | return d->m_actionForId.value(id); 370 | } 371 | 372 | void DBusMenuImporter::slotItemActivationRequested(int id, uint /*timestamp*/) 373 | { 374 | QAction *action = d->m_actionForId.value(id); 375 | DMRETURN_IF_FAIL(action); 376 | actionActivationRequested(action); 377 | } 378 | 379 | void DBusMenuImporter::slotGetLayoutFinished(QDBusPendingCallWatcher *watcher) 380 | { 381 | int parentId = watcher->property(DBUSMENU_PROPERTY_ID).toInt(); 382 | watcher->deleteLater(); 383 | 384 | QMenu *menu = d->menuForId(parentId); 385 | 386 | QDBusPendingReply reply = *watcher; 387 | if (!reply.isValid()) { 388 | qDebug(DBUSMENUQT) << reply.error().message(); 389 | if (menu) { 390 | emit menuUpdated(menu); 391 | } 392 | return; 393 | } 394 | 395 | #ifdef BENCHMARK 396 | DMDEBUG << "- items received:" << sChrono.elapsed() << "ms"; 397 | #endif 398 | DBusMenuLayoutItem rootItem = reply.argumentAt<1>(); 399 | 400 | if (!menu) { 401 | qDebug(DBUSMENUQT) << "No menu for id" << parentId; 402 | return; 403 | } 404 | 405 | // remove outdated actions 406 | QSet newDBusMenuItemIds; 407 | newDBusMenuItemIds.reserve(rootItem.children.count()); 408 | for (const DBusMenuLayoutItem &item : qAsConst(rootItem.children)) { 409 | newDBusMenuItemIds << item.id; 410 | } 411 | for (QAction *action : menu->actions()) { 412 | int id = action->property(DBUSMENU_PROPERTY_ID).toInt(); 413 | if (!newDBusMenuItemIds.contains(id)) { 414 | // Not calling removeAction() as QMenu will immediately close when it becomes empty, 415 | // which can happen when an application completely reloads this menu. 416 | // When the action is deleted deferred, it is removed from the menu. 417 | action->deleteLater(); 418 | if (action->menu()) { 419 | action->menu()->deleteLater(); 420 | } 421 | d->m_actionForId.remove(id); 422 | } 423 | } 424 | 425 | // insert or update new actions into our menu 426 | for (const DBusMenuLayoutItem &dbusMenuItem : qAsConst(rootItem.children)) { 427 | DBusMenuImporterPrivate::ActionForId::Iterator it = d->m_actionForId.find(dbusMenuItem.id); 428 | QAction *action = nullptr; 429 | if (it == d->m_actionForId.end()) { 430 | int id = dbusMenuItem.id; 431 | action = d->createAction(id, dbusMenuItem.properties, menu); 432 | d->m_actionForId.insert(id, action); 433 | 434 | connect(action, &QObject::destroyed, this, [this, id]() { 435 | d->m_actionForId.remove(id); 436 | }); 437 | 438 | connect(action, &QAction::triggered, this, [id, this]() { 439 | sendClickedEvent(id); 440 | }); 441 | 442 | if (QMenu *menuAction = action->menu()) { 443 | connect(menuAction, &QMenu::aboutToShow, this, &DBusMenuImporter::slotMenuAboutToShow, Qt::UniqueConnection); 444 | } 445 | connect(menu, &QMenu::aboutToHide, this, &DBusMenuImporter::slotMenuAboutToHide, Qt::UniqueConnection); 446 | 447 | menu->addAction(action); 448 | } else { 449 | action = *it; 450 | QStringList filteredKeys = dbusMenuItem.properties.keys(); 451 | filteredKeys.removeOne("type"); 452 | filteredKeys.removeOne("toggle-type"); 453 | filteredKeys.removeOne("children-display"); 454 | d->updateAction(*it, dbusMenuItem.properties, filteredKeys); 455 | // Move the action to the tail so we can keep the order same as the dbus request. 456 | menu->removeAction(action); 457 | menu->addAction(action); 458 | } 459 | } 460 | 461 | emit menuUpdated(menu); 462 | } 463 | 464 | void DBusMenuImporter::sendClickedEvent(int id) 465 | { 466 | d->sendEvent(id, QStringLiteral("clicked")); 467 | } 468 | 469 | void DBusMenuImporter::updateMenu() 470 | { 471 | updateMenu(DBusMenuImporter::menu()); 472 | } 473 | 474 | void DBusMenuImporter::updateMenu(QMenu *menu) 475 | { 476 | Q_ASSERT(menu); 477 | 478 | QAction *action = menu->menuAction(); 479 | Q_ASSERT(action); 480 | 481 | int id = action->property(DBUSMENU_PROPERTY_ID).toInt(); 482 | 483 | auto call = d->m_interface->AboutToShow(id); 484 | QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this); 485 | watcher->setProperty(DBUSMENU_PROPERTY_ID, id); 486 | connect(watcher, &QDBusPendingCallWatcher::finished, this, &DBusMenuImporter::slotAboutToShowDBusCallFinished); 487 | 488 | // Firefox deliberately ignores "aboutToShow" whereas Qt ignores" opened", so we'll just send both all the time... 489 | d->sendEvent(id, QStringLiteral("opened")); 490 | } 491 | 492 | void DBusMenuImporter::slotAboutToShowDBusCallFinished(QDBusPendingCallWatcher *watcher) 493 | { 494 | int id = watcher->property(DBUSMENU_PROPERTY_ID).toInt(); 495 | watcher->deleteLater(); 496 | 497 | QMenu *menu = d->menuForId(id); 498 | if (!menu) { 499 | return; 500 | } 501 | 502 | QDBusPendingReply reply = *watcher; 503 | if (reply.isError()) { 504 | qDebug(DBUSMENUQT) << "Call to AboutToShow() failed:" << reply.error().message(); 505 | Q_EMIT menuUpdated(menu); 506 | return; 507 | } 508 | // Note, this isn't used by Qt's QPT - but we get a LayoutChanged emitted before 509 | // this returns, which equates to the same thing 510 | bool needRefresh = reply.argumentAt<0>(); 511 | 512 | if (needRefresh || menu->actions().isEmpty()) { 513 | d->m_idsRefreshedByAboutToShow << id; 514 | d->refresh(id); 515 | } else if (menu) { 516 | Q_EMIT menuUpdated(menu); 517 | } 518 | } 519 | 520 | void DBusMenuImporter::slotMenuAboutToHide() 521 | { 522 | QMenu *menu = qobject_cast(sender()); 523 | Q_ASSERT(menu); 524 | 525 | QAction *action = menu->menuAction(); 526 | Q_ASSERT(action); 527 | 528 | int id = action->property(DBUSMENU_PROPERTY_ID).toInt(); 529 | d->sendEvent(id, QStringLiteral("closed")); 530 | } 531 | 532 | void DBusMenuImporter::slotMenuAboutToShow() 533 | { 534 | QMenu *menu = qobject_cast(sender()); 535 | Q_ASSERT(menu); 536 | 537 | updateMenu(menu); 538 | } 539 | 540 | QMenu *DBusMenuImporter::createMenu(QWidget *parent) 541 | { 542 | return new QMenu(parent); 543 | } 544 | 545 | QIcon DBusMenuImporter::iconForName(const QString & /*name*/) 546 | { 547 | return QIcon(); 548 | } 549 | 550 | #include "moc_dbusmenuimporter.cpp" 551 | -------------------------------------------------------------------------------- /src/AppMenuButtonGroup.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Chris Holland 3 | * Copyright (C) 2016 Kai Uwe Broulik 4 | * Copyright (C) 2014 by Hugo Pereira Da Costa 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | // own 21 | #include "AppMenuButtonGroup.h" 22 | #include "Material.h" 23 | #include "BuildConfig.h" 24 | #include "AppMenuModel.h" 25 | #include "Decoration.h" 26 | #include "AppMenuButton.h" 27 | #include "TextButton.h" 28 | #include "MenuOverflowButton.h" 29 | 30 | // KDecoration 31 | #include 32 | #include 33 | #include 34 | 35 | // KF 36 | #include 37 | 38 | // Qt 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | 45 | 46 | namespace Material 47 | { 48 | 49 | AppMenuButtonGroup::AppMenuButtonGroup(Decoration *decoration) 50 | : KDecoration2::DecorationButtonGroup(decoration) 51 | , m_appMenuModel(nullptr) 52 | , m_currentIndex(-1) 53 | , m_overflowIndex(-1) 54 | , m_hovered(false) 55 | , m_showing(true) 56 | , m_alwaysShow(true) 57 | , m_animationEnabled(false) 58 | , m_animation(new QVariantAnimation(this)) 59 | , m_opacity(1) 60 | { 61 | // Assign showing and opacity before we bind the onShowingChanged animation 62 | // so that new windows do not animate. 63 | setAlwaysShow(decoration->menuAlwaysShow()); 64 | updateShowing(); 65 | setOpacity(m_showing ? 1 : 0); 66 | 67 | connect(this, &AppMenuButtonGroup::showingChanged, 68 | this, &AppMenuButtonGroup::onShowingChanged); 69 | connect(this, &AppMenuButtonGroup::hoveredChanged, 70 | this, &AppMenuButtonGroup::updateShowing); 71 | connect(this, &AppMenuButtonGroup::alwaysShowChanged, 72 | this, &AppMenuButtonGroup::updateShowing); 73 | connect(this, &AppMenuButtonGroup::currentIndexChanged, 74 | this, &AppMenuButtonGroup::updateShowing); 75 | 76 | m_animationEnabled = decoration->animationsEnabled(); 77 | m_animation->setDuration(decoration->animationsDuration()); 78 | m_animation->setStartValue(0.0); 79 | m_animation->setEndValue(1.0); 80 | m_animation->setEasingCurve(QEasingCurve::InOutQuad); 81 | connect(m_animation, &QVariantAnimation::valueChanged, this, [this](const QVariant &value) { 82 | setOpacity(value.toReal()); 83 | }); 84 | connect(this, &AppMenuButtonGroup::opacityChanged, this, [this]() { 85 | // update(); 86 | }); 87 | 88 | auto *decoratedClient = decoration->client().toStrongRef().data(); 89 | connect(decoratedClient, &KDecoration2::DecoratedClient::hasApplicationMenuChanged, 90 | this, &AppMenuButtonGroup::updateAppMenuModel); 91 | connect(this, &AppMenuButtonGroup::requestActivateIndex, 92 | this, &AppMenuButtonGroup::trigger); 93 | connect(this, &AppMenuButtonGroup::requestActivateOverflow, 94 | this, &AppMenuButtonGroup::triggerOverflow); 95 | } 96 | 97 | AppMenuButtonGroup::~AppMenuButtonGroup() 98 | { 99 | } 100 | 101 | int AppMenuButtonGroup::currentIndex() const 102 | { 103 | return m_currentIndex; 104 | } 105 | 106 | void AppMenuButtonGroup::setCurrentIndex(int set) 107 | { 108 | if (m_currentIndex != set) { 109 | m_currentIndex = set; 110 | // qCDebug(category) << this << "setCurrentIndex" << m_currentIndex; 111 | emit currentIndexChanged(); 112 | } 113 | } 114 | 115 | bool AppMenuButtonGroup::overflowing() const 116 | { 117 | return m_overflowing; 118 | } 119 | 120 | void AppMenuButtonGroup::setOverflowing(bool set) 121 | { 122 | if (m_overflowing != set) { 123 | m_overflowing = set; 124 | // qCDebug(category) << this << "setOverflowing" << m_overflowing; 125 | emit overflowingChanged(); 126 | } 127 | } 128 | 129 | bool AppMenuButtonGroup::hovered() const 130 | { 131 | return m_hovered; 132 | } 133 | 134 | void AppMenuButtonGroup::setHovered(bool value) 135 | { 136 | if (m_hovered != value) { 137 | m_hovered = value; 138 | // qCDebug(category) << this << "setHovered" << m_hovered; 139 | emit hoveredChanged(value); 140 | } 141 | } 142 | 143 | bool AppMenuButtonGroup::showing() const 144 | { 145 | return m_showing; 146 | } 147 | 148 | void AppMenuButtonGroup::setShowing(bool value) 149 | { 150 | if (m_showing != value) { 151 | m_showing = value; 152 | // qCDebug(category) << this << "setShowing" << m_showing << "alwaysShow" << m_alwaysShow << "currentIndex" << m_currentIndex << "opacity" << m_opacity; 153 | emit showingChanged(value); 154 | } 155 | } 156 | 157 | bool AppMenuButtonGroup::alwaysShow() const 158 | { 159 | return m_alwaysShow; 160 | } 161 | 162 | void AppMenuButtonGroup::setAlwaysShow(bool value) 163 | { 164 | if (m_alwaysShow != value) { 165 | m_alwaysShow = value; 166 | // qCDebug(category) << this << "setAlwaysShow" << m_alwaysShow; 167 | emit alwaysShowChanged(value); 168 | } 169 | } 170 | 171 | bool AppMenuButtonGroup::animationEnabled() const 172 | { 173 | return m_animationEnabled; 174 | } 175 | 176 | void AppMenuButtonGroup::setAnimationEnabled(bool value) 177 | { 178 | if (m_animationEnabled != value) { 179 | m_animationEnabled = value; 180 | emit animationEnabledChanged(value); 181 | } 182 | } 183 | 184 | int AppMenuButtonGroup::animationDuration() const 185 | { 186 | return m_animation->duration(); 187 | } 188 | 189 | void AppMenuButtonGroup::setAnimationDuration(int value) 190 | { 191 | if (m_animation->duration() != value) { 192 | m_animation->setDuration(value); 193 | emit animationDurationChanged(value); 194 | } 195 | } 196 | 197 | qreal AppMenuButtonGroup::opacity() const 198 | { 199 | return m_opacity; 200 | } 201 | 202 | void AppMenuButtonGroup::setOpacity(qreal value) 203 | { 204 | if (m_opacity != value) { 205 | m_opacity = value; 206 | 207 | for (int i = 0; i < buttons().length(); i++) { 208 | KDecoration2::DecorationButton* decoButton = buttons().value(i); 209 | auto *button = qobject_cast