pull/1/head
cutefishd 6 years ago
commit bcabaa6149

55
.gitignore vendored

@ -0,0 +1,55 @@
# C++ objects and libs
*.slo
*.lo
*.o
*.a
*.la
*.lai
*.so
*.so.*
*.dll
*.dylib
# Qt-es
object_script.*.Release
object_script.*.Debug
*_plugin_import.cpp
/.qmake.cache
/.qmake.stash
*.pro.user
*.pro.user.*
*.qbs.user
*.qbs.user.*
*.moc
moc_*.cpp
moc_*.h
qrc_*.cpp
ui_*.h
*.qmlc
*.jsc
Makefile*
*build-*
*.qm
*.prl
# Qt unit tests
target_wrapper.*
# QtCreator
*.autosave
# QtCreator Qml
*.qmlproject.user
*.qmlproject.user.*
# QtCreator CMake
CMakeLists.txt.user*
# QtCreator 4.8< compilation database
compile_commands.json
# QtCreator local machine specific files for imported projects
*creator.user*
build/*
.vscode/*

@ -0,0 +1,8 @@
cmake_minimum_required(VERSION 3.0)
project(cutefish-qt-plugins)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
add_subdirectory(platformtheme)
add_subdirectory(widgetstyle)

@ -0,0 +1,21 @@
# Qt Plugins
Unify Qt application style of CutefishOS.
## Dependencies
`sudo pacman -S gcc extra-cmake-modules qt5-base qt5-tools qt5-x11extras libqtxdg libdbusmenu-qt5 libxcb`
## Build
```shell
mkdir build
cd build
cmake ..
make
sudo make install
```
## License
cutefish-qt-plugins is licensed under GPLv3.

@ -0,0 +1,121 @@
#.rst:
# FindQt5PlatformSupport
# -------
#
# Try to find Qt5PlatformSupport on a Unix system.
#
# This will define the following variables:
#
# ``Qt5PlatformSupport_FOUND``
# True if (the requested version of) Qt5PlatformSupport is available
# ``Qt5PlatformSupport_VERSION``
# The version of Qt5PlatformSupport
# ``Qt5PlatformSupport_LIBRARIES``
# This can be passed to target_link_libraries() instead of the ``Qt5PlatformSupport::Qt5PlatformSupport``
# target
# ``Qt5PlatformSupport_INCLUDE_DIRS``
# This should be passed to target_include_directories() if the target is not
# used for linking
# ``Qt5PlatformSupport_DEFINITIONS``
# This should be passed to target_compile_options() if the target is not
# used for linking
#
# If ``Qt5PlatformSupport_FOUND`` is TRUE, it will also define the following imported target:
#
# ``Qt5PlatformSupport::Qt5PlatformSupport``
# The Qt5PlatformSupport library
#
# In general we recommend using the imported target, as it is easier to use.
# Bear in mind, however, that if the target is in the link interface of an
# exported library, it must be made available by the package config file.
#=============================================================================
# Copyright 2014 Alex Merry <alex.merry@kde.org>
# Copyright 2014 Martin Gräßlin <mgraesslin@kde.org>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. The name of the author may not be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#=============================================================================
if(CMAKE_VERSION VERSION_LESS 2.8.12)
message(FATAL_ERROR "CMake 2.8.12 is required by FindQt5PlatformSupport.cmake")
endif()
if(CMAKE_MINIMUM_REQUIRED_VERSION VERSION_LESS 2.8.12)
message(AUTHOR_WARNING "Your project should require at least CMake 2.8.12 to use FindQt5PlatformSupport.cmake")
endif()
# Use pkg-config to get the directories and then use these values
# in the FIND_PATH() and FIND_LIBRARY() calls
find_package(PkgConfig)
pkg_check_modules(PKG_Qt5PlatformSupport QUIET Qt5Gui)
set(Qt5PlatformSupport_DEFINITIONS ${PKG_Qt5PlatformSupport_CFLAGS_OTHER})
set(Qt5PlatformSupport_VERSION ${PKG_Qt5PlatformSupport_VERSION})
find_path(Qt5PlatformSupport_INCLUDE_DIR
NAMES
QtPlatformSupport/private/qfontconfigdatabase_p.h
HINTS
${PKG_Qt5PlatformSupport_INCLUDEDIR}/QtPlatformSupport/${PKG_Qt5PlatformSupport_VERSION}/
)
find_library(Qt5PlatformSupport_LIBRARY
NAMES
Qt5PlatformSupport
HINTS
${PKG_Qt5PlatformSupport_LIBRARY_DIRS}
)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Qt5PlatformSupport
FOUND_VAR
Qt5PlatformSupport_FOUND
REQUIRED_VARS
Qt5PlatformSupport_LIBRARY
Qt5PlatformSupport_INCLUDE_DIR
VERSION_VAR
Qt5PlatformSupport_VERSION
)
if(Qt5PlatformSupport_FOUND AND NOT TARGET Qt5PlatformSupport::Qt5PlatformSupport)
add_library(Qt5PlatformSupport::Qt5PlatformSupport UNKNOWN IMPORTED)
set_target_properties(Qt5PlatformSupport::Qt5PlatformSupport PROPERTIES
IMPORTED_LOCATION "${Qt5PlatformSupport_LIBRARY}"
INTERFACE_COMPILE_OPTIONS "${Qt5PlatformSupport_DEFINITIONS}"
INTERFACE_INCLUDE_DIRECTORIES "${Qt5PlatformSupport_INCLUDE_DIR}"
)
endif()
mark_as_advanced(Qt5PlatformSupport_LIBRARY Qt5PlatformSupport_INCLUDE_DIR)
# compatibility variables
set(Qt5PlatformSupport_LIBRARIES ${Qt5PlatformSupport_LIBRARY})
set(Qt5PlatformSupport_INCLUDE_DIRS ${Qt5PlatformSupport_INCLUDE_DIR})
set(Qt5PlatformSupport_VERSION_STRING ${Qt5PlatformSupport_VERSION})
include(FeatureSummary)
set_package_properties(Qt5PlatformSupport PROPERTIES
URL "http://www.qt.io"
DESCRIPTION "Qt PlatformSupport module."
)

@ -0,0 +1,122 @@
#.rst:
# FindQt5ThemeSupport
# -------
#
# Try to find Qt5ThemeSupport on a Unix system.
#
# This will define the following variables:
#
# ``Qt5ThemeSupport_FOUND``
# True if (the requested version of) Qt5ThemeSupport is available
# ``Qt5ThemeSupport_VERSION``
# The version of Qt5ThemeSupport
# ``Qt5ThemeSupport_LIBRARIES``
# This can be passed to target_link_libraries() instead of the ``Qt5ThemeSupport::Qt5ThemeSupport``
# target
# ``Qt5ThemeSupport_INCLUDE_DIRS``
# This should be passed to target_include_directories() if the target is not
# used for linking
# ``Qt5ThemeSupport_DEFINITIONS``
# This should be passed to target_compile_options() if the target is not
# used for linking
#
# If ``Qt5ThemeSupport_FOUND`` is TRUE, it will also define the following imported target:
#
# ``Qt5ThemeSupport::Qt5ThemeSupport``
# The Qt5ThemeSupport library
#
# In general we recommend using the imported target, as it is easier to use.
# Bear in mind, however, that if the target is in the link interface of an
# exported library, it must be made available by the package config file.
#=============================================================================
# Copyright 2014 Alex Merry <alex.merry@kde.org>
# Copyright 2014 Martin Gräßlin <mgraesslin@kde.org>
# Copyright 2016 Takahiro Hashimoto <kenya888@gmail.com>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. The name of the author may not be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#=============================================================================
if(CMAKE_VERSION VERSION_LESS 2.8.12)
message(FATAL_ERROR "CMake 2.8.12 is required by FindQt5ThemeSupport.cmake")
endif()
if(CMAKE_MINIMUM_REQUIRED_VERSION VERSION_LESS 2.8.12)
message(AUTHOR_WARNING "Your project should require at least CMake 2.8.12 to use FindQt5ThemeSupport.cmake")
endif()
# Use pkg-config to get the directories and then use these values
# in the FIND_PATH() and FIND_LIBRARY() calls
find_package(PkgConfig)
pkg_check_modules(PKG_Qt5ThemeSupport QUIET Qt5Gui)
set(Qt5ThemeSupport_DEFINITIONS ${PKG_Qt5ThemeSupport_CFLAGS_OTHER})
set(Qt5ThemeSupport_VERSION ${PKG_Qt5ThemeSupport_VERSION})
find_path(Qt5ThemeSupport_INCLUDE_DIR
NAMES
QtThemeSupport/private/qgenericunixthemes_p.h
HINTS
${PKG_Qt5ThemeSupport_INCLUDEDIR}/QtThemeSupport/${PKG_Qt5ThemeSupport_VERSION}/
)
find_library(Qt5ThemeSupport_LIBRARY
NAMES
Qt5ThemeSupport
HINTS
${PKG_Qt5ThemeSupport_LIBRARY_DIRS}
)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Qt5ThemeSupport
FOUND_VAR
Qt5ThemeSupport_FOUND
REQUIRED_VARS
Qt5ThemeSupport_LIBRARY
Qt5ThemeSupport_INCLUDE_DIR
VERSION_VAR
Qt5ThemeSupport_VERSION
)
if(Qt5ThemeSupport_FOUND AND NOT TARGET Qt5ThemeSupport::Qt5ThemeSupport)
add_library(Qt5ThemeSupport::Qt5ThemeSupport UNKNOWN IMPORTED)
set_target_properties(Qt5ThemeSupport::Qt5ThemeSupport PROPERTIES
IMPORTED_LOCATION "${Qt5ThemeSupport_LIBRARY}"
INTERFACE_COMPILE_OPTIONS "${Qt5ThemeSupport_DEFINITIONS}"
INTERFACE_INCLUDE_DIRECTORIES "${Qt5ThemeSupport_INCLUDE_DIR}"
)
endif()
mark_as_advanced(Qt5ThemeSupport_LIBRARY Qt5ThemeSupport_INCLUDE_DIR)
# compatibility variables
set(Qt5ThemeSupport_LIBRARIES ${Qt5ThemeSupport_LIBRARY})
set(Qt5ThemeSupport_INCLUDE_DIRS ${Qt5ThemeSupport_INCLUDE_DIR})
set(Qt5ThemeSupport_VERSION_STRING ${Qt5ThemeSupport_VERSION})
include(FeatureSummary)
set_package_properties(Qt5ThemeSupport PROPERTIES
URL "http://www.qt.io"
DESCRIPTION "Qt ThemeSupport module."
)

@ -0,0 +1,93 @@
project(platformthemeplugin)
include(GNUInstallDirs)
set(CMAKE_AUTOMOC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
find_package(Qt5Core REQUIRED)
find_package(Qt5Widgets REQUIRED)
find_package(Qt5QuickControls2 REQUIRED)
find_package(Qt5DBus REQUIRED)
find_package(Qt5X11Extras REQUIRED)
find_package(Qt5Gui CONFIG REQUIRED Private)
find_package(Qt5XdgIconLoader REQUIRED)
find_package(dbusmenu-qt5 REQUIRED)
find_package(KF5WindowSystem REQUIRED)
# dependencies for QPA plugin
find_package(Qt5ThemeSupport REQUIRED)
set(QT5PLATFORMSUPPORT_LIBS Qt5ThemeSupport::Qt5ThemeSupport)
# qdbusmenubar uses them
remove_definitions(-DQT_NO_SIGNALS_SLOTS_KEYWORDS)
pkg_check_modules(XCB_EWMH REQUIRED xcb xcb-ewmh x11)
set (SRCS
main.cpp
platformtheme.h
platformtheme.cpp
hintsettings.h
hintsettings.cpp
systemtrayicon.h
systemtrayicon.cpp
qdbusmenubar_p.h
qdbusmenubar.cpp
x11integration.h
x11integration.cpp
statusnotifier/dbustypes.h
statusnotifier/dbustypes.cpp
statusnotifier/statusnotifieritem.h
statusnotifier/statusnotifieritem.cpp
)
qt5_add_dbus_interface(SRCS org.kde.StatusNotifierWatcher.xml statusnotifierwatcher_interface)
qt5_add_dbus_adaptor(SRCS
statusnotifier/org.kde.StatusNotifierItem.xml
statusnotifier/statusnotifieritem.h
StatusNotifierItem
)
add_library(cutefishplatformtheme MODULE ${SRCS})
target_compile_definitions(cutefishplatformtheme
PRIVATE
"QT_NO_FOREACH"
"LIB_FM_QT_SONAME=\"${LIB_FM_QT_SONAME}\""
)
target_link_libraries(cutefishplatformtheme PRIVATE
Qt5::GuiPrivate
Qt5::X11Extras
Qt5::Widgets
Qt5::QuickControls2
Qt5::Core
Qt5::DBus
dbusmenu-qt5
Qt5XdgIconLoader
KF5::WindowSystem
${XCB_LIBRARIES}
${QT5PLATFORMSUPPORT_LIBS}
)
get_target_property(QT_QMAKE_EXECUTABLE ${Qt5Core_QMAKE_EXECUTABLE} IMPORTED_LOCATION)
if(NOT QT_QMAKE_EXECUTABLE)
message(FATAL_ERROR "qmake is not found.")
endif()
# execute the command "qmake -query QT_INSTALL_PLUGINS" to get the path of plugins dir.
execute_process(COMMAND ${QT_QMAKE_EXECUTABLE} -query QT_INSTALL_PLUGINS
OUTPUT_VARIABLE QT_PLUGINS_DIR
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(QT_PLUGINS_DIR)
message(STATUS "Qt5 plugin directory:" "${QT_PLUGINS_DIR}")
else()
message(FATAL_ERROR "Qt5 plugin directory cannot be detected.")
endif()
install(TARGETS cutefishplatformtheme LIBRARY DESTINATION "${QT_PLUGINS_DIR}/platformthemes")

@ -0,0 +1,3 @@
{
"Keys": [ "cutefish" ]
}

@ -0,0 +1,126 @@
#include "hintsettings.h"
#include <QDebug>
#include <QDir>
#include <QString>
#include <QFileInfo>
#include <QToolBar>
#include <QPalette>
#include <QToolButton>
#include <QMainWindow>
#include <QApplication>
#include <QGuiApplication>
#include <QDialogButtonBox>
#include <QScreen>
#include <QStandardPaths>
#include <QTemporaryFile>
#include <qpa/qplatformdialoghelper.h>
#include <QDBusArgument>
#include <QDBusConnection>
#include <QDBusInterface>
static const QByteArray s_systemFontName = QByteArrayLiteral("Font");
static const QByteArray s_systemFixedFontName = QByteArrayLiteral("FixedFont");
static const QByteArray s_systemPointFontSize = QByteArrayLiteral("FontSize");
static const QByteArray s_darkModeName = QByteArrayLiteral("DarkMode");
static const QByteArray s_lightIconName = QByteArrayLiteral("Crule");
static const QByteArray s_darkIconName = QByteArrayLiteral("Crule-dark");
HintsSettings::HintsSettings(QObject *parent)
: QObject(parent),
m_settings(new QSettings(QSettings::UserScope, "cutefishos", "theme"))
{
m_hints[QPlatformTheme::SystemIconThemeName] = darkMode() ? s_darkIconName : s_lightIconName;
m_hints[QPlatformTheme::StyleNames] = "cutefish";
m_hints[QPlatformTheme::SystemIconFallbackThemeName] = QStringLiteral("hicolor");
m_hints[QPlatformTheme::IconThemeSearchPaths] = xdgIconThemePaths();
m_hints[QPlatformTheme::UseFullScreenForPopupMenu] = false;
m_hints[QPlatformTheme::DialogButtonBoxLayout] = QPlatformDialogHelper::MacLayout;
m_settingsFile = m_settings->fileName();
QMetaObject::invokeMethod(this, "lazyInit", Qt::QueuedConnection);
}
HintsSettings::~HintsSettings()
{
}
void HintsSettings::lazyInit()
{
m_fileWatcher = new QFileSystemWatcher();
m_fileWatcher->addPath(m_settingsFile);
connect(m_fileWatcher, &QFileSystemWatcher::fileChanged, this, &HintsSettings::onFileChanged);
}
QStringList HintsSettings::xdgIconThemePaths() const
{
QStringList paths;
// make sure we have ~/.local/share/icons in paths if it exists
paths << QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("icons"), QStandardPaths::LocateDirectory);
const QFileInfo homeIconDir(QDir::homePath() + QStringLiteral("/.icons"));
if (homeIconDir.isDir()) {
paths << homeIconDir.absoluteFilePath();
}
return paths;
}
QString HintsSettings::systemFont() const
{
return m_settings->value(s_systemFontName, "Noto Sans").toString();
}
QString HintsSettings::systemFixedFont() const
{
return m_settings->value(s_systemFixedFontName, "Monospace").toString();
}
qreal HintsSettings::systemFontPointSize() const
{
return m_settings->value(s_systemPointFontSize, 10.5).toDouble();
}
bool HintsSettings::darkMode()
{
return m_settings->value(s_darkModeName, false).toBool();
}
void HintsSettings::onFileChanged(const QString &path)
{
Q_UNUSED(path);
QVariantMap map;
for (const QString &value : m_settings->allKeys()) {
map[value] = m_settings->value(value);
}
m_settings->sync();
for (const QString &value : m_settings->allKeys()) {
const QVariant &oldValue = map.value(value);
const QVariant &newValue = m_settings->value(value);
if (oldValue != newValue) {
if (value == s_systemFontName)
emit systemFontChanged(newValue.toString());
else if (value == s_systemFixedFontName)
emit systemFixedFontChanged(newValue.toString());
else if (value == s_systemPointFontSize)
emit systemFontPointSizeChanged(newValue.toDouble());
else if (value == s_darkModeName) {
emit darkModeChanged(newValue.toBool());
// Need to update the icon to dark
m_hints[QPlatformTheme::SystemIconThemeName] = darkMode() ? s_darkIconName : s_lightIconName;
emit iconThemeChanged();
}
}
}
bool fileDeleted = !m_fileWatcher->files().contains(m_settingsFile);
if (fileDeleted)
m_fileWatcher->addPath(m_settingsFile);
}

@ -0,0 +1,57 @@
#ifndef HINTSSETTINGS_H
#define HINTSSETTINGS_H
#include <QDBusVariant>
#include <QFileSystemWatcher>
#include <QObject>
#include <QVariant>
#include <QSettings>
#include <qpa/qplatformtheme.h>
class QPalette;
class HintsSettings : public QObject
{
Q_OBJECT
public:
explicit HintsSettings(QObject *parent = nullptr);
~HintsSettings() override;
QStringList xdgIconThemePaths() const;
inline QVariant hint(QPlatformTheme::ThemeHint hint) const {
return m_hints[hint];
}
QString systemFont() const;
QString systemFixedFont() const;
qreal systemFontPointSize() const;
bool darkMode();
public Q_SLOTS:
void lazyInit();
Q_SIGNALS:
void systemFontChanged(QString font);
void systemFixedFontChanged(QString fixedFont);
void systemFontPointSizeChanged(qreal systemFontPointSize);
void iconThemeChanged();
void darkModeChanged(bool darkMode);
private:
void onFileChanged(const QString &path);
private:
QHash<QPlatformTheme::ThemeHint, QVariant> m_hints;
QSettings *m_settings;
QString m_settingsFile;
QFileSystemWatcher *m_fileWatcher;
QString m_systemFont;
QString m_systemFixedFont;
qreal m_systemFontPointSize;
};
#endif //HINTSSETTINGS_H

@ -0,0 +1,32 @@
#include <qpa/qplatformthemeplugin.h>
#include "platformtheme.h"
#include <private/xdgiconloader/xdgiconloader_p.h>
QT_BEGIN_NAMESPACE
void updateXdgIconSystemTheme()
{
XdgIconLoader::instance()->updateSystemTheme();
}
class PlatformThemePlugin : public QPlatformThemePlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID QPlatformThemeFactoryInterface_iid FILE "cutefish-platformtheme.json")
public:
PlatformThemePlugin(QObject *parent = nullptr)
: QPlatformThemePlugin(parent) {}
QPlatformTheme *create(const QString &key, const QStringList &paramList) override
{
Q_UNUSED(key)
Q_UNUSED(paramList)
return new PlatformTheme;
}
};
QT_END_NAMESPACE
#include "main.moc"

@ -0,0 +1,42 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.kde.StatusNotifierWatcher">
<!-- methods -->
<method name="RegisterStatusNotifierItem">
<arg name="service" type="s" direction="in"/>
</method>
<method name="RegisterStatusNotifierHost">
<arg name="service" type="s" direction="in"/>
</method>
<!-- properties -->
<property name="RegisteredStatusNotifierItems" type="as" access="read">
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QStringList"/>
</property>
<property name="IsStatusNotifierHostRegistered" type="b" access="read"/>
<property name="ProtocolVersion" type="i" access="read"/>
<!-- signals -->
<signal name="StatusNotifierItemRegistered">
<arg type="s"/>
</signal>
<signal name="StatusNotifierItemUnregistered">
<arg type="s"/>
</signal>
<signal name="StatusNotifierHostRegistered">
</signal>
<signal name="StatusNotifierHostUnregistered">
</signal>
</interface>
</node>

@ -0,0 +1,202 @@
#include "platformtheme.h"
#include "x11integration.h"
#include "qdbusmenubar_p.h"
#include <QApplication>
#include <QFont>
#include <QPalette>
#include <QString>
#include <QVariant>
#include <QDebug>
#include <QLibrary>
#include <QStyleFactory>
#include <QtQuickControls2/QQuickStyle>
// Qt Private
#include <private/qicon_p.h>
#include <private/qiconloader_p.h>
#include <private/qwindow_p.h>
#include <private/qguiapplication_p.h>
// Qt DBus
#include <QDBusConnection>
#include <QDBusInterface>
#include <KWindowSystem>
static const QByteArray s_x11AppMenuServiceNamePropertyName = QByteArrayLiteral("_KDE_NET_WM_APPMENU_SERVICE_NAME");
static const QByteArray s_x11AppMenuObjectPathPropertyName = QByteArrayLiteral("_KDE_NET_WM_APPMENU_OBJECT_PATH");
static bool checkDBusGlobalMenuAvailable()
{
QDBusConnection connection = QDBusConnection::sessionBus();
QString registrarService = QStringLiteral("com.canonical.AppMenu.Registrar");
return connection.interface()->isServiceRegistered(registrarService);
}
static bool isDBusGlobalMenuAvailable()
{
static bool dbusGlobalMenuAvailable = checkDBusGlobalMenuAvailable();
return dbusGlobalMenuAvailable;
}
extern void updateXdgIconSystemTheme();
void onDarkModeChanged()
{
QStyle *style = QStyleFactory::create("cutefish");
if (style) {
qApp->setStyle(style);
}
}
PlatformTheme::PlatformTheme()
: m_hints(new HintsSettings)
{
// qApp->setProperty("_hints_settings_object", (quintptr)m_hints);
if (KWindowSystem::isPlatformX11()) {
m_x11Integration.reset(new X11Integration());
m_x11Integration->init();
}
connect(m_hints, &HintsSettings::systemFontChanged, this, &PlatformTheme::onFontChanged);
connect(m_hints, &HintsSettings::systemFontPointSizeChanged, this, &PlatformTheme::onFontChanged);
connect(m_hints, &HintsSettings::iconThemeChanged, this, &PlatformTheme::onIconThemeChanged);
connect(m_hints, &HintsSettings::darkModeChanged, &onDarkModeChanged);
QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar, false);
setQtQuickControlsTheme();
}
PlatformTheme::~PlatformTheme()
{
}
QVariant PlatformTheme::themeHint(QPlatformTheme::ThemeHint hintType) const
{
QVariant hint = m_hints->hint(hintType);
if (hint.isValid()) {
return hint;
} else {
return QPlatformTheme::themeHint(hintType);
}
}
const QFont* PlatformTheme::font(Font type) const
{
switch (type) {
case SystemFont:
case MessageBoxFont:
case LabelFont:
case TipLabelFont:
case StatusBarFont:
case PushButtonFont:
case ItemViewFont:
case ListViewFont:
case HeaderViewFont:
case ListBoxFont:
case ComboMenuItemFont:
case ComboLineEditFont: {
const QString &fontName = m_hints->systemFont();
qreal fontSize = m_hints->systemFontPointSize();
static QFont font = QFont(QString());
font.setFamily(fontName);
font.setPointSizeF(fontSize);
return &font;
}
case FixedFont: {
const QString &fontName = m_hints->systemFixedFont();
qreal fontSize = m_hints->systemFontPointSize();
static QFont font = QFont(QString());
font.setFamily(fontName);
font.setPointSizeF(fontSize);
return &font;
}
default: {
const QString &fontName = m_hints->systemFont();
qreal fontSize = m_hints->systemFontPointSize();
static QFont font = QFont(QString());
font.setFamily(fontName);
font.setPointSizeF(fontSize);
return &font;
}
}
return QPlatformTheme::font(type);
}
QPlatformMenuBar *PlatformTheme::createPlatformMenuBar() const
{
// if (isDBusGlobalMenuAvailable()) {
// auto *menu = new QDBusMenuBar();
// QObject::connect(menu, &QDBusMenuBar::windowChanged, menu, [this, menu](QWindow *newWindow, QWindow *oldWindow) {
// const QString &serviceName = QDBusConnection::sessionBus().baseService();
// const QString &objectPath = menu->objectPath();
// if (m_x11Integration) {
// if (oldWindow) {
// m_x11Integration->setWindowProperty(oldWindow, s_x11AppMenuServiceNamePropertyName, {});
// m_x11Integration->setWindowProperty(oldWindow, s_x11AppMenuObjectPathPropertyName, {});
// }
// if (newWindow) {
// m_x11Integration->setWindowProperty(newWindow, s_x11AppMenuServiceNamePropertyName, serviceName.toUtf8());
// m_x11Integration->setWindowProperty(newWindow, s_x11AppMenuObjectPathPropertyName, objectPath.toUtf8());
// }
// }
// // if (m_kwaylandIntegration) {
// // if (oldWindow) {
// // m_kwaylandIntegration->setAppMenu(oldWindow, QString(), QString());
// // }
// //
// // if (newWindow) {
// // m_kwaylandIntegration->setAppMenu(newWindow, serviceName, objectPath);
// // }
// // }
// });
// return menu;
// }
return nullptr;
}
void PlatformTheme::onFontChanged()
{
QFont font;
font.setFamily(m_hints->systemFont());
font.setPointSizeF(m_hints->systemFontPointSize());
// Change font
if (qobject_cast<QApplication *>(QCoreApplication::instance()))
QApplication::setFont(font);
else if (qobject_cast<QGuiApplication *>(QCoreApplication::instance()))
QGuiApplication::setFont(font);
}
void PlatformTheme::onIconThemeChanged()
{
QIconLoader::instance()->updateSystemTheme();
updateXdgIconSystemTheme();
QEvent update(QEvent::UpdateRequest);
for (QWindow *window : qGuiApp->allWindows()) {
if (window->type() == Qt::Desktop)
continue;
qApp->sendEvent(window, &update);
}
}
void PlatformTheme::setQtQuickControlsTheme()
{
//if the user has explicitly set something else, don't meddle
if (!QQuickStyle::name().isEmpty()) {
return;
}
QQuickStyle::setStyle(QLatin1String("meui-style"));
}

@ -0,0 +1,48 @@
#ifndef PLATFORMTHEME_H
#define PLATFORMTHEME_H
#include <qpa/qplatformtheme.h>
#include "hintsettings.h"
#include "systemtrayicon.h"
#include <QHash>
#include <QKeySequence>
class QIconEngine;
class QWindow;
class X11Integration;
class PlatformTheme : public QObject, public QPlatformTheme
{
Q_OBJECT
public:
PlatformTheme();
~PlatformTheme() override;
QVariant themeHint(ThemeHint hint) const override;
const QFont *font(Font type) const override;
QPlatformMenuBar *createPlatformMenuBar() const override;
QPlatformSystemTrayIcon *createPlatformSystemTrayIcon() const override {
auto trayIcon = new SystemTrayIcon;
if (trayIcon->isSystemTrayAvailable())
return trayIcon;
else {
delete trayIcon;
return nullptr;
}
}
private:
void onFontChanged();
void onIconThemeChanged();
void setQtQuickControlsTheme();
private:
HintsSettings *m_hints;
QScopedPointer<X11Integration> m_x11Integration;
};
#endif // PLATFORMTHEME_H

@ -0,0 +1,156 @@
#include "qdbusmenubar_p.h"
QT_BEGIN_NAMESPACE
/* note: do not change these to QStringLiteral;
we are unloaded before QtDBus is done using the strings.
*/
#define REGISTRAR_SERVICE QLatin1String("com.canonical.AppMenu.Registrar")
#define REGISTRAR_PATH QLatin1String("/com/canonical/AppMenu/Registrar")
QDBusMenuBar::QDBusMenuBar()
: QPlatformMenuBar()
, m_menu(new QDBusPlatformMenu())
, m_menuAdaptor(new QDBusMenuAdaptor(m_menu))
{
QDBusMenuItem::registerDBusTypes();
connect(m_menu, &QDBusPlatformMenu::propertiesUpdated,
m_menuAdaptor, &QDBusMenuAdaptor::ItemsPropertiesUpdated);
connect(m_menu, &QDBusPlatformMenu::updated,
m_menuAdaptor, &QDBusMenuAdaptor::LayoutUpdated);
// This signal is new in Qt 5.8 but distros might have backported it, hence a runtime look-up
if (m_menu->metaObject()->indexOfSignal("popupRequested(int,uint)") != -1) {
connect(m_menu, SIGNAL(popupRequested(int,uint)), m_menuAdaptor, SIGNAL(ItemActivationRequested(int,uint)));
}
}
QDBusMenuBar::~QDBusMenuBar()
{
unregisterMenuBar();
delete m_menuAdaptor;
delete m_menu;
qDeleteAll(m_menuItems);
}
QDBusPlatformMenuItem *QDBusMenuBar::menuItemForMenu(QPlatformMenu *menu)
{
if (!menu)
return nullptr;
quintptr tag = menu->tag();
const auto it = m_menuItems.constFind(tag);
if (it != m_menuItems.cend()) {
return *it;
} else {
QDBusPlatformMenuItem *item = new QDBusPlatformMenuItem;
updateMenuItem(item, menu);
m_menuItems.insert(tag, item);
return item;
}
}
void QDBusMenuBar::updateMenuItem(QDBusPlatformMenuItem *item, QPlatformMenu *menu)
{
const QDBusPlatformMenu *ourMenu = qobject_cast<const QDBusPlatformMenu *>(menu);
item->setText(ourMenu->text());
item->setIcon(ourMenu->icon());
item->setEnabled(ourMenu->isEnabled());
item->setVisible(ourMenu->isVisible());
item->setMenu(menu);
}
void QDBusMenuBar::insertMenu(QPlatformMenu *menu, QPlatformMenu *before)
{
QDBusPlatformMenuItem *menuItem = menuItemForMenu(menu);
QDBusPlatformMenuItem *beforeItem = menuItemForMenu(before);
m_menu->insertMenuItem(menuItem, beforeItem);
m_menu->emitUpdated();
}
void QDBusMenuBar::removeMenu(QPlatformMenu *menu)
{
QDBusPlatformMenuItem *menuItem = menuItemForMenu(menu);
m_menu->removeMenuItem(menuItem);
m_menu->emitUpdated();
}
void QDBusMenuBar::syncMenu(QPlatformMenu *menu)
{
QDBusPlatformMenuItem *menuItem = menuItemForMenu(menu);
updateMenuItem(menuItem, menu);
}
void QDBusMenuBar::handleReparent(QWindow *newParentWindow)
{
if (newParentWindow == m_window) {
return;
}
QWindow *oldWindow = m_window;
unregisterMenuBar();
m_window = newParentWindow;
if (newParentWindow) {
registerMenuBar();
}
emit windowChanged(newParentWindow, oldWindow);
}
QPlatformMenu *QDBusMenuBar::menuForTag(quintptr tag) const
{
QDBusPlatformMenuItem *menuItem = m_menuItems.value(tag);
if (menuItem)
return const_cast<QPlatformMenu *>(menuItem->menu());
return nullptr;
}
QPlatformMenu *QDBusMenuBar::createMenu() const
{
return new QDBusPlatformMenu;
}
void QDBusMenuBar::registerMenuBar()
{
static uint menuBarId = 0;
if (!m_window) {
qWarning("Cannot register window menu without window");
return;
}
QDBusConnection connection = QDBusConnection::sessionBus();
m_objectPath = QStringLiteral("/MenuBar/%1").arg(++menuBarId);
if (!connection.registerObject(m_objectPath, m_menu))
return;
QDBusMenuRegistrarInterface registrar(REGISTRAR_SERVICE, REGISTRAR_PATH, connection, this);
QDBusPendingReply<> r = registrar.RegisterWindow(static_cast<uint>(window()->winId()), QDBusObjectPath(m_objectPath));
r.waitForFinished();
if (r.isError()) {
qWarning("Failed to register window menu, reason: %s (\"%s\")",
qUtf8Printable(r.error().name()), qUtf8Printable(r.error().message()));
connection.unregisterObject(m_objectPath);
}
}
void QDBusMenuBar::unregisterMenuBar()
{
QDBusConnection connection = QDBusConnection::sessionBus();
if (m_window) {
QDBusMenuRegistrarInterface registrar(REGISTRAR_SERVICE, REGISTRAR_PATH, connection, this);
QDBusPendingReply<> r = registrar.UnregisterWindow(static_cast<uint>(window()->winId()));
r.waitForFinished();
if (r.isError())
qWarning("Failed to unregister window menu, reason: %s (\"%s\")",
qUtf8Printable(r.error().name()), qUtf8Printable(r.error().message()));
}
if (!m_objectPath.isEmpty())
connection.unregisterObject(m_objectPath);
}
QT_END_NAMESPACE

@ -0,0 +1,101 @@
/****************************************************************************
**
** Copyright (C) 2016 Dmitry Shachnev <mitya57@gmail.com>
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QDBUSMENUBAR_P_H
#define QDBUSMENUBAR_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <QHash>
#include <QString>
#include <QWindow>
#include <QtThemeSupport/private/qdbusplatformmenu_p.h>
#include <QtThemeSupport/private/qdbusmenuadaptor_p.h>
#include <QtThemeSupport/private/qdbusmenuconnection_p.h>
#include <QtThemeSupport/private/qdbusmenuregistrarproxy_p.h>
QT_BEGIN_NAMESPACE
class QDBusMenuBar : public QPlatformMenuBar
{
Q_OBJECT
public:
QDBusMenuBar();
~QDBusMenuBar() override;
void insertMenu(QPlatformMenu *menu, QPlatformMenu *before) override;
void removeMenu(QPlatformMenu *menu) override;
void syncMenu(QPlatformMenu *menu) override;
void handleReparent(QWindow *newParentWindow) override;
QPlatformMenu *menuForTag(quintptr tag) const override;
QPlatformMenu *createMenu() const override;
QWindow *window() const { return m_window; }
QString objectPath() const { return m_objectPath; }
Q_SIGNALS:
void windowChanged(QWindow *newWindow, QWindow *oldWindow);
private:
QDBusPlatformMenu *m_menu;
QDBusMenuAdaptor *m_menuAdaptor;
QHash<quintptr, QDBusPlatformMenuItem *> m_menuItems;
QPointer<QWindow> m_window;
QString m_objectPath;
QDBusPlatformMenuItem *menuItemForMenu(QPlatformMenu *menu);
static void updateMenuItem(QDBusPlatformMenuItem *item, QPlatformMenu *menu);
void registerMenuBar();
void unregisterMenuBar();
};
QT_END_NAMESPACE
#endif // QDBUSMENUBAR_P_H

@ -0,0 +1,47 @@
#include "dbustypes.h"
// Marshall the IconPixmap data into a D-Bus argument
QDBusArgument &operator<<(QDBusArgument &argument, const IconPixmap &icon)
{
argument.beginStructure();
argument << icon.width;
argument << icon.height;
argument << icon.bytes;
argument.endStructure();
return argument;
}
// Retrieve the ImageStruct data from the D-Bus argument
const QDBusArgument &operator>>(const QDBusArgument &argument, IconPixmap &icon)
{
argument.beginStructure();
argument >> icon.width;
argument >> icon.height;
argument >> icon.bytes;
argument.endStructure();
return argument;
}
// Marshall the ToolTip data into a D-Bus argument
QDBusArgument &operator<<(QDBusArgument &argument, const ToolTip &toolTip)
{
argument.beginStructure();
argument << toolTip.iconName;
argument << toolTip.iconPixmap;
argument << toolTip.title;
argument << toolTip.description;
argument.endStructure();
return argument;
}
// Retrieve the ToolTip data from the D-Bus argument
const QDBusArgument &operator>>(const QDBusArgument &argument, ToolTip &toolTip)
{
argument.beginStructure();
argument >> toolTip.iconName;
argument >> toolTip.iconPixmap;
argument >> toolTip.title;
argument >> toolTip.description;
argument.endStructure();
return argument;
}

@ -0,0 +1,32 @@
#include <QDBusArgument>
#ifndef DBUSTYPES_H
#define DBUSTYPES_H
struct IconPixmap {
int width;
int height;
QByteArray bytes;
};
typedef QList<IconPixmap> IconPixmapList;
Q_DECLARE_METATYPE(IconPixmap)
Q_DECLARE_METATYPE(IconPixmapList)
struct ToolTip {
QString iconName;
QList<IconPixmap> iconPixmap;
QString title;
QString description;
};
Q_DECLARE_METATYPE(ToolTip)
QDBusArgument &operator<<(QDBusArgument &argument, const IconPixmap &icon);
const QDBusArgument &operator>>(const QDBusArgument &argument, IconPixmap &icon);
QDBusArgument &operator<<(QDBusArgument &argument, const ToolTip &toolTip);
const QDBusArgument &operator>>(const QDBusArgument &argument, ToolTip &toolTip);
#endif // DBUSTYPES_H

@ -0,0 +1,69 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.kde.StatusNotifierItem">
<property name="Category" type="s" access="read"/>
<property name="Id" type="s" access="read"/>
<property name="Title" type="s" access="read"/>
<property name="Status" type="s" access="read"/>
<property name="WindowId" type="i" access="read"/>
<property name="IconThemePath" type="s" access="read"/>
<property name="Menu" type="o" access="read"/>
<property name="ItemIsMenu" type="b" access="read"/>
<property name="IconName" type="s" access="read"/>
<property name="IconPixmap" type="a(iiay)" access="read">
<annotation name="org.qtproject.QtDBus.QtTypeName" value="IconPixmapList"/>
</property>
<property name="OverlayIconName" type="s" access="read"/>
<property name="OverlayIconPixmap" type="a(iiay)" access="read">
<annotation name="org.qtproject.QtDBus.QtTypeName" value="IconPixmapList"/>
</property>
<property name="AttentionIconName" type="s" access="read"/>
<property name="AttentionIconPixmap" type="a(iiay)" access="read">
<annotation name="org.qtproject.QtDBus.QtTypeName" value="IconPixmapList"/>
</property>
<property name="AttentionMovieName" type="s" access="read"/>
<property name="ToolTip" type="(sa(iiay)ss)" access="read">
<annotation name="org.qtproject.QtDBus.QtTypeName" value="ToolTip"/>
</property>
<method name="ContextMenu">
<arg name="x" type="i" direction="in"/>
<arg name="y" type="i" direction="in"/>
</method>
<method name="Activate">
<arg name="x" type="i" direction="in"/>
<arg name="y" type="i" direction="in"/>
</method>
<method name="SecondaryActivate">
<arg name="x" type="i" direction="in"/>
<arg name="y" type="i" direction="in"/>
</method>
<method name="Scroll">
<arg name="delta" type="i" direction="in"/>
<arg name="orientation" type="s" direction="in"/>
</method>
<signal name="NewTitle">
</signal>
<signal name="NewIcon">
</signal>
<signal name="NewAttentionIcon">
</signal>
<signal name="NewOverlayIcon">
</signal>
<signal name="NewToolTip">
</signal>
<signal name="NewStatus">
<arg name="status" type="s"/>
</signal>
</interface>
</node>

@ -0,0 +1,313 @@
#include "statusnotifieritem.h"
#include "statusnotifieritemadaptor.h"
#include <QDBusInterface>
#include <QDBusServiceWatcher>
#include <dbusmenuexporter.h>
int StatusNotifierItem::mServiceCounter = 0;
StatusNotifierItem::StatusNotifierItem(QString id, QObject *parent)
: QObject(parent),
mAdaptor(new StatusNotifierItemAdaptor(this)),
mService(QString::fromLatin1("org.freedesktop.StatusNotifierItem-%1-%2")
.arg(QCoreApplication::applicationPid())
.arg(++mServiceCounter)),
mId(id),
mTitle(QLatin1String("Test")),
mStatus(QLatin1String("Active")),
mCategory(QLatin1String("ApplicationStatus")),
mMenu(nullptr),
mMenuPath(QLatin1String("/NO_DBUSMENU")),
mMenuExporter(nullptr),
mSessionBus(QDBusConnection::connectToBus(QDBusConnection::SessionBus, mService))
{
// Separate DBus connection to the session bus is created, because QDbus does not provide
// a way to register different objects for different services with the same paths.
// For status notifiers we need different /StatusNotifierItem for each service.
// register service
mSessionBus.registerObject(QLatin1String("/StatusNotifierItem"), this);
registerToHost();
// monitor the watcher service in case the host restarts
QDBusServiceWatcher *watcher = new QDBusServiceWatcher(QLatin1String("org.kde.StatusNotifierWatcher"),
mSessionBus,
QDBusServiceWatcher::WatchForOwnerChange,
this);
connect(watcher, &QDBusServiceWatcher::serviceOwnerChanged,
this, &StatusNotifierItem::onServiceOwnerChanged);
}
StatusNotifierItem::~StatusNotifierItem()
{
mSessionBus.unregisterObject(QLatin1String("/StatusNotifierItem"));
QDBusConnection::disconnectFromBus(mService);
}
void StatusNotifierItem::registerToHost()
{
QDBusInterface interface(QLatin1String("org.kde.StatusNotifierWatcher"),
QLatin1String("/StatusNotifierWatcher"),
QLatin1String("org.kde.StatusNotifierWatcher"),
mSessionBus);
interface.asyncCall(QLatin1String("RegisterStatusNotifierItem"), mSessionBus.baseService());
}
void StatusNotifierItem::onServiceOwnerChanged(const QString& service, const QString& oldOwner,
const QString& newOwner)
{
Q_UNUSED(service);
Q_UNUSED(oldOwner);
if (!newOwner.isEmpty())
registerToHost();
}
void StatusNotifierItem::onMenuDestroyed()
{
mMenu = nullptr;
setMenuPath(QLatin1String("/NO_DBUSMENU"));
mMenuExporter = nullptr; //mMenu is a QObject parent of the mMenuExporter
}
void StatusNotifierItem::setTitle(const QString &title)
{
if (mTitle == title)
return;
mTitle = title;
Q_EMIT mAdaptor->NewTitle();
}
void StatusNotifierItem::setStatus(const QString &status)
{
if (mStatus == status)
return;
mStatus = status;
Q_EMIT mAdaptor->NewStatus(mStatus);
}
void StatusNotifierItem::setCategory(const QString &category)
{
if (mCategory == category)
return;
mCategory = category;
}
void StatusNotifierItem::setMenuPath(const QString& path)
{
mMenuPath.setPath(path);
}
void StatusNotifierItem::setIconByName(const QString &name)
{
if (mIconName == name)
return;
mIconName = name;
Q_EMIT mAdaptor->NewIcon();
}
void StatusNotifierItem::setIconByPixmap(const QIcon &icon)
{
if (mIconCacheKey == icon.cacheKey())
return;
mIconCacheKey = icon.cacheKey();
mIcon = iconToPixmapList(icon);
mIconName.clear();
Q_EMIT mAdaptor->NewIcon();
}
void StatusNotifierItem::setOverlayIconByName(const QString &name)
{
if (mOverlayIconName == name)
return;
mOverlayIconName = name;
Q_EMIT mAdaptor->NewOverlayIcon();
}
void StatusNotifierItem::setOverlayIconByPixmap(const QIcon &icon)
{
if (mOverlayIconCacheKey == icon.cacheKey())
return;
mOverlayIconCacheKey = icon.cacheKey();
mOverlayIcon = iconToPixmapList(icon);
mOverlayIconName.clear();
Q_EMIT mAdaptor->NewOverlayIcon();
}
void StatusNotifierItem::setAttentionIconByName(const QString &name)
{
if (mAttentionIconName == name)
return;
mAttentionIconName = name;
Q_EMIT mAdaptor->NewAttentionIcon();
}
void StatusNotifierItem::setAttentionIconByPixmap(const QIcon &icon)
{
if (mAttentionIconCacheKey == icon.cacheKey())
return;
mAttentionIconCacheKey = icon.cacheKey();
mAttentionIcon = iconToPixmapList(icon);
mAttentionIconName.clear();
Q_EMIT mAdaptor->NewAttentionIcon();
}
void StatusNotifierItem::setToolTipTitle(const QString &title)
{
if (mTooltipTitle == title)
return;
mTooltipTitle = title;
Q_EMIT mAdaptor->NewToolTip();
}
void StatusNotifierItem::setToolTipSubTitle(const QString &subTitle)
{
if (mTooltipSubtitle == subTitle)
return;
mTooltipSubtitle = subTitle;
Q_EMIT mAdaptor->NewToolTip();
}
void StatusNotifierItem::setToolTipIconByName(const QString &name)
{
if (mTooltipIconName == name)
return;
mTooltipIconName = name;
Q_EMIT mAdaptor->NewToolTip();
}
void StatusNotifierItem::setToolTipIconByPixmap(const QIcon &icon)
{
if (mTooltipIconCacheKey == icon.cacheKey())
return;
mTooltipIconCacheKey = icon.cacheKey();
mTooltipIcon = iconToPixmapList(icon);
mTooltipIconName.clear();
Q_EMIT mAdaptor->NewToolTip();
}
void StatusNotifierItem::setContextMenu(QMenu* menu)
{
if (mMenu == menu)
return;
if (nullptr != mMenu)
{
disconnect(mMenu, &QObject::destroyed, this, &StatusNotifierItem::onMenuDestroyed);
}
mMenu = menu;
if (nullptr != mMenu)
setMenuPath(QLatin1String("/MenuBar"));
else
setMenuPath(QLatin1String("/NO_DBUSMENU"));
//Note: we need to destroy menu exporter before creating new one -> to free the DBus object path for new menu
delete mMenuExporter;
if (nullptr != mMenu)
{
connect(mMenu, &QObject::destroyed, this, &StatusNotifierItem::onMenuDestroyed);
mMenuExporter = new DBusMenuExporter{this->menu().path(), mMenu, mSessionBus};
}
}
void StatusNotifierItem::Activate(int x, int y)
{
if (mStatus == QLatin1String("NeedsAttention"))
mStatus = QLatin1String("Active");
Q_EMIT activateRequested(QPoint(x, y));
}
void StatusNotifierItem::SecondaryActivate(int x, int y)
{
if (mStatus == QLatin1String("NeedsAttention"))
mStatus = QLatin1String("Active");
Q_EMIT secondaryActivateRequested(QPoint(x, y));
}
void StatusNotifierItem::ContextMenu(int x, int y)
{
if (mMenu)
{
if (mMenu->isVisible())
mMenu->popup(QPoint(x, y));
else
mMenu->hide();
}
}
void StatusNotifierItem::Scroll(int delta, const QString &orientation)
{
Qt::Orientation orient = Qt::Vertical;
if (orientation.toLower() == QLatin1String("horizontal"))
orient = Qt::Horizontal;
Q_EMIT scrollRequested(delta, orient);
}
void StatusNotifierItem::showMessage(const QString& title, const QString& msg,
const QString& iconName, int secs)
{
QDBusInterface interface(QLatin1String("org.freedesktop.Notifications"), QLatin1String("/org/freedesktop/Notifications"),
QLatin1String("org.freedesktop.Notifications"), mSessionBus);
interface.call(QLatin1String("Notify"), mTitle, (uint) 0, iconName, title,
msg, QStringList(), QVariantMap(), secs);
}
IconPixmapList StatusNotifierItem::iconToPixmapList(const QIcon& icon)
{
IconPixmapList pixmapList;
// long live KDE!
const QList<QSize> sizes = icon.availableSizes();
for (const QSize &size : sizes)
{
QImage image = icon.pixmap(size).toImage();
IconPixmap pix;
pix.height = image.height();
pix.width = image.width();
if (image.format() != QImage::Format_ARGB32)
image = image.convertToFormat(QImage::Format_ARGB32);
pix.bytes = QByteArray((char *) image.bits(),
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
image.byteCount());
#else
image.sizeInBytes());
#endif
// swap to network byte order if we are little endian
if (QSysInfo::ByteOrder == QSysInfo::LittleEndian)
{
quint32 *uintBuf = (quint32 *) pix.bytes.data();
for (uint i = 0; i < pix.bytes.size() / sizeof(quint32); ++i)
{
*uintBuf = qToBigEndian(*uintBuf);
++uintBuf;
}
}
pixmapList.append(pix);
}
return pixmapList;
}

@ -0,0 +1,163 @@
#ifndef STATUS_NOTIFIER_ITEM_H
#define STATUS_NOTIFIER_ITEM_H
#include <QObject>
#include <QIcon>
#include <QMenu>
#include <QDBusConnection>
#include "dbustypes.h"
class StatusNotifierItemAdaptor;
class DBusMenuExporter;
class StatusNotifierItem : public QObject
{
Q_OBJECT
Q_PROPERTY(QString Category READ category)
Q_PROPERTY(QString Title READ title)
Q_PROPERTY(QString Id READ id)
Q_PROPERTY(QString Status READ status)
Q_PROPERTY(QDBusObjectPath Menu READ menu)
Q_PROPERTY(QString IconName READ iconName)
Q_PROPERTY(IconPixmapList IconPixmap READ iconPixmap)
Q_PROPERTY(QString OverlayIconName READ overlayIconName)
Q_PROPERTY(IconPixmapList OverlayIconPixmap READ overlayIconPixmap)
Q_PROPERTY(QString AttentionIconName READ attentionIconName)
Q_PROPERTY(IconPixmapList AttentionIconPixmap READ attentionIconPixmap)
Q_PROPERTY(ToolTip ToolTip READ toolTip)
public:
StatusNotifierItem(QString id, QObject *parent = nullptr);
~StatusNotifierItem() override;
QString id() const
{ return mId; }
QString title() const
{ return mTitle; }
void setTitle(const QString &title);
QString status() const
{ return mStatus; }
void setStatus(const QString &status);
QString category() const
{ return mCategory; }
void setCategory(const QString &category);
QDBusObjectPath menu() const
{ return mMenuPath; }
void setMenuPath(const QString &path);
QString iconName() const
{ return mIconName; }
void setIconByName(const QString &name);
IconPixmapList iconPixmap() const
{ return mIcon; }
void setIconByPixmap(const QIcon &icon);
QString overlayIconName() const
{ return mOverlayIconName; }
void setOverlayIconByName(const QString &name);
IconPixmapList overlayIconPixmap() const
{ return mOverlayIcon; }
void setOverlayIconByPixmap(const QIcon &icon);
QString attentionIconName() const
{ return mAttentionIconName; }
void setAttentionIconByName(const QString &name);
IconPixmapList attentionIconPixmap() const
{ return mAttentionIcon; }
void setAttentionIconByPixmap(const QIcon &icon);
QString toolTipTitle() const
{ return mTooltipTitle; }
void setToolTipTitle(const QString &title);
QString toolTipSubTitle() const
{ return mTooltipSubtitle; }
void setToolTipSubTitle(const QString &subTitle);
QString toolTipIconName() const
{ return mTooltipIconName; }
void setToolTipIconByName(const QString &name);
IconPixmapList toolTipIconPixmap() const
{ return mTooltipIcon; }
void setToolTipIconByPixmap(const QIcon &icon);
ToolTip toolTip() const
{
ToolTip tt;
tt.title = mTooltipTitle;
tt.description = mTooltipSubtitle;
tt.iconName = mTooltipIconName;
tt.iconPixmap = mTooltipIcon;
return tt;
}
/*!
* \Note: we don't take ownership for the \param menu
*/
void setContextMenu(QMenu *menu);
public Q_SLOTS:
void Activate(int x, int y);
void SecondaryActivate(int x, int y);
void ContextMenu(int x, int y);
void Scroll(int delta, const QString &orientation);
void showMessage(const QString &title, const QString &msg, const QString &iconName, int secs);
private:
void registerToHost();
IconPixmapList iconToPixmapList(const QIcon &icon);
private Q_SLOTS:
void onServiceOwnerChanged(const QString &service, const QString &oldOwner,
const QString &newOwner);
void onMenuDestroyed();
Q_SIGNALS:
void activateRequested(const QPoint &pos);
void secondaryActivateRequested(const QPoint &pos);
void scrollRequested(int delta, Qt::Orientation orientation);
private:
StatusNotifierItemAdaptor *mAdaptor;
QString mService;
QString mId;
QString mTitle;
QString mStatus;
QString mCategory;
// icons
QString mIconName, mOverlayIconName, mAttentionIconName;
IconPixmapList mIcon, mOverlayIcon, mAttentionIcon;
qint64 mIconCacheKey, mOverlayIconCacheKey, mAttentionIconCacheKey;
// tooltip
QString mTooltipTitle, mTooltipSubtitle, mTooltipIconName;
IconPixmapList mTooltipIcon;
qint64 mTooltipIconCacheKey;
// menu
QMenu *mMenu;
QDBusObjectPath mMenuPath;
DBusMenuExporter *mMenuExporter;
QDBusConnection mSessionBus;
static int mServiceCounter;
};
#endif

@ -0,0 +1,355 @@
#include "systemtrayicon.h"
#include <QAction>
#include <QIcon>
#include <QMenu>
#include <QRect>
#include <QApplication>
#include <QDBusMetaType>
#include <QDBusInterface>
SystemTrayMenu::SystemTrayMenu()
: QPlatformMenu(),
m_tag(0),
m_menu(new QMenu())
{
connect(m_menu.data(), &QMenu::aboutToShow, this, &QPlatformMenu::aboutToShow);
connect(m_menu.data(), &QMenu::aboutToHide, this, &QPlatformMenu::aboutToHide);
}
SystemTrayMenu::~SystemTrayMenu()
{
if (m_menu)
m_menu->deleteLater();
}
QPlatformMenuItem *SystemTrayMenu::createMenuItem() const
{
return new SystemTrayMenuItem();
}
void SystemTrayMenu::insertMenuItem(QPlatformMenuItem *menuItem, QPlatformMenuItem *before)
{
if (SystemTrayMenuItem *ours = qobject_cast<SystemTrayMenuItem*>(menuItem))
{
bool inserted = false;
if (SystemTrayMenuItem *oursBefore = qobject_cast<SystemTrayMenuItem*>(before))
{
for (auto it = m_items.begin(); it != m_items.end(); ++it)
{
if (*it == oursBefore)
{
m_items.insert(it, ours);
if (m_menu)
m_menu->insertAction(oursBefore->action(), ours->action());
inserted = true;
break;
}
}
}
if (!inserted)
{
m_items.append(ours);
if (m_menu)
m_menu->addAction(ours->action());
}
}
}
QPlatformMenuItem *SystemTrayMenu::menuItemAt(int position) const
{
if (position < m_items.size())
return m_items.at(position);
return nullptr;
}
QPlatformMenuItem *SystemTrayMenu::menuItemForTag(quintptr tag) const
{
auto it = std::find_if(m_items.constBegin(), m_items.constEnd(), [tag] (SystemTrayMenuItem *item)
{
return item->tag() == tag;
});
if (it != m_items.constEnd())
return *it;
return nullptr;
}
void SystemTrayMenu::removeMenuItem(QPlatformMenuItem *menuItem)
{
if (SystemTrayMenuItem *ours = qobject_cast<SystemTrayMenuItem*>(menuItem))
{
m_items.removeOne(ours);
if (ours->action() && m_menu)
m_menu->removeAction(ours->action());
}
}
void SystemTrayMenu::setEnabled(bool enabled)
{
if (!m_menu)
return;
m_menu->setEnabled(enabled);
}
void SystemTrayMenu::setIcon(const QIcon &icon)
{
if (!m_menu)
return;
m_menu->setIcon(icon);
}
void SystemTrayMenu::setTag(quintptr tag)
{
m_tag = tag;
}
void SystemTrayMenu::setText(const QString &text)
{
if (!m_menu)
return;
m_menu->setTitle(text);
}
void SystemTrayMenu::setVisible(bool visible)
{
if (!m_menu)
return;
m_menu->setVisible(visible);
}
void SystemTrayMenu::syncMenuItem(QPlatformMenuItem *)
{
// Nothing to do
}
void SystemTrayMenu::syncSeparatorsCollapsible(bool enable)
{
if (!m_menu)
return;
m_menu->setSeparatorsCollapsible(enable);
}
quintptr SystemTrayMenu::tag() const
{
return m_tag;
}
QMenu *SystemTrayMenu::menu() const
{
return m_menu.data();
}
SystemTrayMenuItem::SystemTrayMenuItem()
: QPlatformMenuItem(),
m_tag(0),
m_action(new QAction(this))
{
connect(m_action, &QAction::triggered, this, &QPlatformMenuItem::activated);
connect(m_action, &QAction::hovered, this, &QPlatformMenuItem::hovered);
}
SystemTrayMenuItem::~SystemTrayMenuItem()
{
}
void SystemTrayMenuItem::setCheckable(bool checkable)
{
m_action->setCheckable(checkable);
}
void SystemTrayMenuItem::setChecked(bool isChecked)
{
m_action->setChecked(isChecked);
}
void SystemTrayMenuItem::setEnabled(bool enabled)
{
m_action->setEnabled(enabled);
}
void SystemTrayMenuItem::setFont(const QFont &font)
{
m_action->setFont(font);
}
void SystemTrayMenuItem::setIcon(const QIcon &icon)
{
m_action->setIcon(icon);
}
void SystemTrayMenuItem::setIsSeparator(bool isSeparator)
{
m_action->setSeparator(isSeparator);
}
void SystemTrayMenuItem::setMenu(QPlatformMenu *menu)
{
if (SystemTrayMenu *ourMenu = qobject_cast<SystemTrayMenu *>(menu))
m_action->setMenu(ourMenu->menu());
}
void SystemTrayMenuItem::setRole(QPlatformMenuItem::MenuRole)
{
}
void SystemTrayMenuItem::setShortcut(const QKeySequence &shortcut)
{
m_action->setShortcut(shortcut);
}
void SystemTrayMenuItem::setTag(quintptr tag)
{
m_tag = tag;
}
void SystemTrayMenuItem::setText(const QString &text)
{
m_action->setText(text);
}
void SystemTrayMenuItem::setVisible(bool isVisible)
{
m_action->setVisible(isVisible);
}
void SystemTrayMenuItem::setIconSize(int)
{
}
quintptr SystemTrayMenuItem::tag() const
{
return m_tag;
}
QAction *SystemTrayMenuItem::action() const
{
return m_action;
}
SystemTrayIcon::SystemTrayIcon()
: QPlatformSystemTrayIcon(),
mSni(nullptr)
{
// register types
qDBusRegisterMetaType<ToolTip>();
qDBusRegisterMetaType<IconPixmap>();
qDBusRegisterMetaType<IconPixmapList>();
}
SystemTrayIcon::~SystemTrayIcon()
{
}
void SystemTrayIcon::init()
{
if (!mSni)
{
mSni = new StatusNotifierItem(QString::number(QCoreApplication::applicationPid()), this);
mSni->setTitle(QApplication::applicationDisplayName());
// default menu
QPlatformMenu *menu = createMenu();
menu->setParent(mSni);
QPlatformMenuItem *menuItem = menu->createMenuItem();
menuItem->setParent(menu);
menuItem->setText(tr("Quit"));
menuItem->setIcon(QIcon::fromTheme(QLatin1String("application-exit")));
connect(menuItem, &QPlatformMenuItem::activated, qApp, &QApplication::quit);
menu->insertMenuItem(menuItem, nullptr);
updateMenu(menu);
connect(mSni, &StatusNotifierItem::activateRequested, [this](const QPoint &)
{
Q_EMIT activated(QPlatformSystemTrayIcon::Trigger);
});
connect(mSni, &StatusNotifierItem::secondaryActivateRequested, [this](const QPoint &)
{
Q_EMIT activated(QPlatformSystemTrayIcon::MiddleClick);
});
}
}
void SystemTrayIcon::cleanup()
{
delete mSni;
mSni = nullptr;
}
void SystemTrayIcon::updateIcon(const QIcon &icon)
{
if (!mSni)
return;
if (icon.name().isEmpty())
{
mSni->setIconByPixmap(icon);
mSni->setToolTipIconByPixmap(icon);
}
else
{
mSni->setIconByName(icon.name());
mSni->setToolTipIconByName(icon.name());
}
}
void SystemTrayIcon::updateToolTip(const QString &tooltip)
{
if (!mSni)
return;
mSni->setToolTipTitle(tooltip);
}
void SystemTrayIcon::updateMenu(QPlatformMenu *menu)
{
if (!mSni)
return;
if (SystemTrayMenu *ourMenu = qobject_cast<SystemTrayMenu*>(menu))
mSni->setContextMenu(ourMenu->menu());
}
QPlatformMenu *SystemTrayIcon::createMenu() const
{
return new SystemTrayMenu();
}
QRect SystemTrayIcon::geometry() const
{
// StatusNotifierItem doesn't provide the geometry
return {};
}
void SystemTrayIcon::showMessage(const QString &title, const QString &msg,
const QIcon &icon, MessageIcon, int secs)
{
if (!mSni)
return;
mSni->showMessage(title, msg, icon.name(), secs);
}
bool SystemTrayIcon::isSystemTrayAvailable() const
{
QDBusInterface systrayHost(QLatin1String("org.kde.StatusNotifierWatcher"),
QLatin1String("/StatusNotifierWatcher"),
QLatin1String("org.kde.StatusNotifierWatcher"));
return systrayHost.isValid() && systrayHost.property("IsStatusNotifierHostRegistered").toBool();
}
bool SystemTrayIcon::supportsMessages() const
{
return true;
}

@ -0,0 +1,124 @@
/* BEGIN_COMMON_COPYRIGHT_HEADER
* (c)LGPL2+
*
* LXQt - a lightweight, Qt based, desktop toolset
* https://lxqt.org/
*
* Copyright: 2015 LXQt team
* Authors:
* Paulo Lieuthier <paulolieuthier@gmail.com>
*
* This program or library is free software; you can redistribute it
* and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*
* END_COMMON_COPYRIGHT_HEADER */
#ifndef SYSTEMTRAYICON_H
#define SYSTEMTRAYICON_H
#include <qpa/qplatformmenu.h>
#include <qpa/qplatformsystemtrayicon.h>
#include "statusnotifier/statusnotifieritem.h"
class SystemTrayMenuItem;
class QAction;
class QMenu;
class SystemTrayMenu : public QPlatformMenu
{
Q_OBJECT
public:
SystemTrayMenu();
~SystemTrayMenu() Q_DECL_OVERRIDE;
void insertMenuItem(QPlatformMenuItem *menuItem, QPlatformMenuItem *before) Q_DECL_OVERRIDE;
QPlatformMenuItem *menuItemAt(int position) const Q_DECL_OVERRIDE;
QPlatformMenuItem *menuItemForTag(quintptr tag) const Q_DECL_OVERRIDE;
void removeMenuItem(QPlatformMenuItem *menuItem) Q_DECL_OVERRIDE;
void setEnabled(bool enabled) Q_DECL_OVERRIDE;
void setIcon(const QIcon &icon) Q_DECL_OVERRIDE;
void setTag(quintptr tag) Q_DECL_OVERRIDE;
void setText(const QString &text) Q_DECL_OVERRIDE;
void setVisible(bool visible) Q_DECL_OVERRIDE;
void syncMenuItem(QPlatformMenuItem *menuItem) Q_DECL_OVERRIDE;
void syncSeparatorsCollapsible(bool enable) Q_DECL_OVERRIDE;
quintptr tag() const Q_DECL_OVERRIDE;
QPlatformMenuItem *createMenuItem() const Q_DECL_OVERRIDE;
QMenu *menu() const;
private:
quintptr m_tag;
QPointer<QMenu> m_menu;
QList<SystemTrayMenuItem*> m_items;
};
class SystemTrayMenuItem : public QPlatformMenuItem
{
Q_OBJECT
public:
SystemTrayMenuItem();
~SystemTrayMenuItem() Q_DECL_OVERRIDE;
void setCheckable(bool checkable) Q_DECL_OVERRIDE;
void setChecked(bool isChecked) Q_DECL_OVERRIDE;
void setEnabled(bool enabled) Q_DECL_OVERRIDE;
void setFont(const QFont &font) Q_DECL_OVERRIDE;
void setIcon(const QIcon &icon) Q_DECL_OVERRIDE;
void setIsSeparator(bool isSeparator) Q_DECL_OVERRIDE;
void setMenu(QPlatformMenu *menu) Q_DECL_OVERRIDE;
void setRole(MenuRole role) Q_DECL_OVERRIDE;
void setShortcut(const QKeySequence &shortcut) Q_DECL_OVERRIDE;
void setTag(quintptr tag) Q_DECL_OVERRIDE;
void setText(const QString &text) Q_DECL_OVERRIDE;
void setVisible(bool isVisible) Q_DECL_OVERRIDE;
quintptr tag() const Q_DECL_OVERRIDE;
void setIconSize(int size)
#if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0))
Q_DECL_OVERRIDE
#endif
;
QAction *action() const;
private:
quintptr m_tag;
QAction *m_action;
};
class SystemTrayIcon : public QPlatformSystemTrayIcon
{
public:
SystemTrayIcon();
~SystemTrayIcon() Q_DECL_OVERRIDE;
void init() Q_DECL_OVERRIDE;
void cleanup() Q_DECL_OVERRIDE;
void updateIcon(const QIcon &icon) Q_DECL_OVERRIDE;
void updateToolTip(const QString &tooltip) Q_DECL_OVERRIDE;
void updateMenu(QPlatformMenu *menu) Q_DECL_OVERRIDE;
QRect geometry() const Q_DECL_OVERRIDE;
void showMessage(const QString &title, const QString &msg,
const QIcon &icon, MessageIcon iconType, int secs) Q_DECL_OVERRIDE;
bool isSystemTrayAvailable() const Q_DECL_OVERRIDE;
bool supportsMessages() const Q_DECL_OVERRIDE;
QPlatformMenu *createMenu() const Q_DECL_OVERRIDE;
private:
StatusNotifierItem *mSni;
};
#endif

@ -0,0 +1,135 @@
/* This file is part of the KDE libraries
* Copyright 2015 Martin Gräßlin <mgraesslin@kde.org>
* Copyright 2016 Marco Martin <mart@kde.org>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2 of the License or ( at
* your option ) version 3 or, at the discretion of KDE e.V. ( which shall
* act as a proxy as in section 14 of the GPLv3 ), any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "x11integration.h"
#include <QCoreApplication>
#include <QX11Info>
#include <QPlatformSurfaceEvent>
#include <QGuiApplication>
#include <QWindow>
#include <QWidget>
#include <QVariant>
#include <QRegion>
#include <QDebug>
#include <NETWM>
#include <KWindowEffects>
#include <xcb/xcb.h>
static const char s_schemePropertyName[] = "KDE_COLOR_SCHEME_PATH";
static const QByteArray s_blurBehindPropertyName = QByteArrayLiteral("ENABLE_BLUR_BEHIND_HINT");
static const QByteArray s_blurRegionPropertyName = QByteArrayLiteral("BLUR_REGION");
X11Integration::X11Integration()
: QObject()
{
}
X11Integration::~X11Integration() = default;
void X11Integration::init()
{
QCoreApplication::instance()->installEventFilter(this);
}
bool X11Integration::eventFilter(QObject *watched, QEvent *event)
{
//the drag and drop window should NOT be a tooltip
//https://bugreports.qt.io/browse/QTBUG-52560
if (event->type() == QEvent::Show && watched->inherits("QShapedPixmapWindow")) {
//static cast should be safe there
QWindow *w = static_cast<QWindow *>(watched);
NETWinInfo info(QX11Info::connection(), w->winId(), QX11Info::appRootWindow(), NET::WMWindowType, NET::Properties2());
info.setWindowType(NET::DNDIcon);
// TODO: does this flash the xcb connection?
}
// if (event->type() == QEvent::PlatformSurface) {
// if (QWindow *w = qobject_cast<QWindow*>(watched)) {
// QPlatformSurfaceEvent *pe = static_cast<QPlatformSurfaceEvent*>(event);
// if (!w->flags().testFlag(Qt::ForeignWindow)) {
// if (pe->surfaceEventType() == QPlatformSurfaceEvent::SurfaceCreated) {
// const auto blurBehindProperty = w->property(s_blurBehindPropertyName.constData());
// if (blurBehindProperty.isValid()) {
// KWindowEffects::enableBlurBehind(w->winId(), blurBehindProperty.toBool());
// }
// installDesktopFileName(w);
// }
// }
// }
// }
// if (event->type() == QEvent::ApplicationPaletteChange) {
// const auto topLevelWindows = QGuiApplication::topLevelWindows();
// for (QWindow *w : topLevelWindows) {
// installColorScheme(w);
// }
// }
return false;
}
void X11Integration::installDesktopFileName(QWindow *w)
{
if (!w->isTopLevel()) {
return;
}
QString desktopFileName = QGuiApplication::desktopFileName();
if (desktopFileName.isEmpty()) {
return;
}
// handle apps which set the desktopFileName property with filename suffix,
// due to unclear API dox (https://bugreports.qt.io/browse/QTBUG-75521)
if (desktopFileName.endsWith(QLatin1String(".desktop"))) {
desktopFileName.chop(8);
}
NETWinInfo info(QX11Info::connection(), w->winId(), QX11Info::appRootWindow(), NET::Properties(), NET::Properties2());
info.setDesktopFileName(desktopFileName.toUtf8().constData());
}
void X11Integration::setWindowProperty(QWindow *window, const QByteArray &name, const QByteArray &value)
{
auto *c = QX11Info::connection();
xcb_atom_t atom;
auto it = m_atoms.find(name);
if (it == m_atoms.end()) {
const xcb_intern_atom_cookie_t cookie = xcb_intern_atom(c, false, name.length(), name.constData());
QScopedPointer<xcb_intern_atom_reply_t, QScopedPointerPodDeleter> reply(xcb_intern_atom_reply(c, cookie, nullptr));
if (!reply.isNull()) {
atom = reply->atom;
m_atoms[name] = atom;
} else {
return;
}
} else {
atom = *it;
}
if (value.isEmpty()) {
xcb_delete_property(c, window->winId(), atom);
} else {
xcb_change_property(c, XCB_PROP_MODE_REPLACE, window->winId(), atom, XCB_ATOM_STRING,
8, value.length(), value.constData());
}
}

@ -0,0 +1,47 @@
/* This file is part of the KDE libraries
* Copyright 2015 Martin Gräßlin <mgraesslin@kde.org>
* Copyright 2016 Marco Martin <mart@kde.org>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2 of the License or ( at
* your option ) version 3 or, at the discretion of KDE e.V. ( which shall
* act as a proxy as in section 14 of the GPLv3 ), any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef X11INTEGRATION_H
#define X11INTEGRATION_H
#include <QObject>
#include <QHash>
#include <xcb/xcb.h>
class QWindow;
class X11Integration : public QObject
{
Q_OBJECT
public:
explicit X11Integration();
~X11Integration() override;
void init();
void setWindowProperty(QWindow *window, const QByteArray &name, const QByteArray &value);
bool eventFilter(QObject *watched, QEvent *event) override;
private:
void installDesktopFileName(QWindow *w);
QHash<QByteArray, xcb_atom_t> m_atoms;
};
#endif

@ -0,0 +1,48 @@
cmake_minimum_required(VERSION 3.5)
project(cutefishstyle)
set(TARGET cutefishstyle)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_AUTOMOC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
find_package(ECM REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
set(QT Core Gui Widgets DBus)
find_package(Qt5 REQUIRED ${QT})
find_package(PkgConfig REQUIRED)
find_package(KF5 REQUIRED WindowSystem)
include(ECMQueryQmake)
set (SRCS
blurhelper.cpp
blurhelper.h
pstyleplugin.cpp
pstyleplugin.h
basestyle.h
basestyle.cpp
phantomcolor.h
phantomcolor.cpp
shadowhelper.h
shadowhelper.cpp
tileset.h
tileset.cpp
boxshadowrenderer.h
boxshadowrenderer.cpp
)
add_library(${TARGET} MODULE ${SRCS})
target_link_libraries(${TARGET}
Qt5::GuiPrivate
Qt5::Core
Qt5::Gui
Qt5::Widgets
Qt5::DBus
KF5::WindowSystem
)
query_qmake(CMAKE_INSTALL_QTPLUGINDIR QT_INSTALL_PLUGINS)
install(TARGETS ${TARGET} DESTINATION ${CMAKE_INSTALL_QTPLUGINDIR}/styles/)

File diff suppressed because it is too large Load Diff

@ -0,0 +1,114 @@
/*
* Copyright (C) 2020 Reven Martin
* Copyright (C) 2020 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2019 Andrew Richards
*
* Derived from Phantomstyle and relicensed under the GPLv2 or v3.
* https://github.com/randrew/phantomstyle
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef BASESTYLE_H
#define BASESTYLE_H
#include <QCommonStyle>
class BaseStylePrivate;
class ShadowHelper;
class BlurHelper;
class BaseStyle : public QCommonStyle
{
Q_OBJECT
public:
BaseStyle();
~BaseStyle() override;
enum PhantomPrimitiveElement
{
Phantom_PE_IndicatorTabNew = PE_CustomBase + 1,
Phantom_PE_ScrollBarSliderVertical,
Phantom_PE_WindowFrameColor,
};
static QPalette lightModePalette();
static QPalette darkModePalette();
QPalette standardPalette() const override;
void drawPrimitive(PrimitiveElement elem,
const QStyleOption* option,
QPainter* painter,
const QWidget* widget = nullptr) const override;
void
drawControl(ControlElement ce, const QStyleOption* option, QPainter* painter, const QWidget* widget) const override;
int pixelMetric(PixelMetric metric,
const QStyleOption* option = nullptr,
const QWidget* widget = nullptr) const override;
void drawComplexControl(ComplexControl control,
const QStyleOptionComplex* option,
QPainter* painter,
const QWidget* widget) const override;
QRect subElementRect(SubElement r, const QStyleOption* opt, const QWidget* widget = nullptr) const override;
QSize sizeFromContents(ContentsType type,
const QStyleOption* option,
const QSize& size,
const QWidget* widget) const override;
SubControl hitTestComplexControl(ComplexControl cc,
const QStyleOptionComplex* opt,
const QPoint& pt,
const QWidget* w = nullptr) const override;
QRect subControlRect(ComplexControl cc,
const QStyleOptionComplex* opt,
SubControl sc,
const QWidget* widget) const override;
QPixmap generatedIconPixmap(QIcon::Mode iconMode, const QPixmap& pixmap, const QStyleOption* opt) const override;
int styleHint(StyleHint hint,
const QStyleOption* option = nullptr,
const QWidget* widget = nullptr,
QStyleHintReturn* returnData = nullptr) const override;
QRect itemPixmapRect(const QRect& r, int flags, const QPixmap& pixmap) const override;
void drawItemPixmap(QPainter* painter, const QRect& rect, int alignment, const QPixmap& pixmap) const override;
void drawItemText(QPainter* painter,
const QRect& rect,
int flags,
const QPalette& pal,
bool enabled,
const QString& text,
QPalette::ColorRole textRole = QPalette::NoRole) const override;
using QCommonStyle::polish;
void polish(QApplication* app) override;
void unpolish(QApplication* app) override;
void polish(QWidget *widget) override;
void unpolish(QWidget *widget) override;
bool isDarkMode() const;
protected:
/**
* @return Paths to application stylesheets
*/
virtual QString getAppStyleSheet() const
{
return {};
}
BaseStylePrivate* d;
private:
ShadowHelper *m_shadowHelper;
BlurHelper *m_blurHelper;
};
#endif

@ -0,0 +1,105 @@
//////////////////////////////////////////////////////////////////////////////
// breezeblurhelper.cpp
// handle regions passed to kwin for blurring
// -------------------
//
// Copyright (C) 2018 Alex Nemeth <alex.nemeth329@gmail.com>
//
// Largely rewritten from Oxygen widget style
// Copyright (C) 2007 Thomas Luebking <thomas.luebking@web.de>
// Copyright (c) 2010 Hugo Pereira Da Costa <hugo.pereira@free.fr>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//////////////////////////////////////////////////////////////////////////////
#include "blurhelper.h"
// KF5
#include <KWindowEffects>
// Qt
#include <QWidget>
#include <QVariant>
#include <QEvent>
#include <QPainterPath>
BlurHelper::BlurHelper(QObject *parent)
: QObject(parent)
{
}
void BlurHelper::registerWidget(QWidget *widget)
{
// install event filter
addEventFilter(widget);
// schedule shadow area repaint
update(widget);
}
void BlurHelper::unregisterWidget(QWidget *widget)
{
// remove event filter
widget->removeEventFilter(this);
}
bool BlurHelper::eventFilter(QObject *object, QEvent *event)
{
switch (event->type()) {
case QEvent::Hide:
case QEvent::Show:
case QEvent::Resize: {
// cast to widget and check
QWidget *widget(qobject_cast<QWidget*>(object));
if (!widget)
break;
update(widget);
break;
}
default: break;
}
// never eat events
return false;
}
void BlurHelper::update(QWidget *widget) const
{
/*
directly from bespin code. Supposedly prevent playing with some 'pseudo-widgets'
that have winId matching some other -random- window
*/
if (!(widget->testAttribute(Qt::WA_WState_Created) || widget->internalWinId()))
return;
if (widget->mask().isEmpty()) {
KWindowEffects::enableBlurBehind(widget->winId(), true);
} else {
KWindowEffects::enableBlurBehind(widget->winId(), true, widget->mask());
}
// force update
if (widget->isVisible()) {
widget->update();
}
}

@ -0,0 +1,57 @@
//////////////////////////////////////////////////////////////////////////////
// breezeblurhelper.h
// handle regions passed to kwin for blurring
// -------------------
//
// Copyright (C) 2018 Alex Nemeth <alex.nemeth329@gmail.com>
//
// Largely rewritten from Oxygen widget style
// Copyright (C) 2007 Thomas Luebking <thomas.luebking@web.de>
// Copyright (c) 2010 Hugo Pereira Da Costa <hugo.pereira@free.fr>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//////////////////////////////////////////////////////////////////////////////
#ifndef BLURHELPER_H
#define BLURHELPER_H
#include <QObject>
class BlurHelper : public QObject
{
Q_OBJECT
public:
explicit BlurHelper(QObject *parent = nullptr);
void registerWidget(QWidget *);
void unregisterWidget(QWidget *);
bool eventFilter(QObject *, QEvent *) override;
void update(QWidget *) const;
protected:
void addEventFilter(QObject *object) {
object->removeEventFilter(this);
object->installEventFilter(this);
}
};
#endif // BLURHELPER_H

@ -0,0 +1,348 @@
/*
* Copyright (C) 2018 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
*
* The box blur implementation is based on AlphaBoxBlur from Firefox.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
// own
#include "boxshadowrenderer.h"
// Qt
#include <QPainter>
#include <QtMath>
static inline int calculateBlurRadius(qreal stdDev)
{
// See https://www.w3.org/TR/SVG11/filters.html#feGaussianBlurElement
const qreal gaussianScaleFactor = (3.0 * qSqrt(2.0 * M_PI) / 4.0) * 1.5;
return qMax(2, qFloor(stdDev * gaussianScaleFactor + 0.5));
}
static inline qreal calculateBlurStdDev(int radius)
{
// See https://www.w3.org/TR/css-backgrounds-3/#shadow-blur
return radius * 0.5;
}
static inline QSize calculateBlurExtent(int radius)
{
const int blurRadius = calculateBlurRadius(calculateBlurStdDev(radius));
return QSize(blurRadius, blurRadius);
}
struct BoxLobes
{
int left; ///< how many pixels sample to the left
int right; ///< how many pixels sample to the right
};
/**
* Compute box filter parameters.
*
* @param radius The blur radius.
* @returns Parameters for three box filters.
**/
static QVector<BoxLobes> computeLobes(int radius)
{
const int blurRadius = calculateBlurRadius(calculateBlurStdDev(radius));
const int z = blurRadius / 3;
int major;
int minor;
int final;
switch (blurRadius % 3) {
case 0:
major = z;
minor = z;
final = z;
break;
case 1:
major = z + 1;
minor = z;
final = z;
break;
case 2:
major = z + 1;
minor = z;
final = z + 1;
break;
default:
Q_UNREACHABLE();
}
Q_ASSERT(major + minor + final == blurRadius);
return {
{major, minor},
{minor, major},
{final, final}
};
}
/**
* Process a row with a box filter.
*
* @param src The start of the row.
* @param dst The destination.
* @param width The width of the row, in pixels.
* @param horizontalStride The number of bytes from one alpha value to the
* next alpha value.
* @param verticalStride The number of bytes from one row to the next row.
* @param lobes Params of the box filter.
* @param transposeInput Whether the input is transposed.
* @param transposeOutput Whether the output should be transposed.
**/
static inline void boxBlurRowAlpha(const uint8_t *src, uint8_t *dst, int width, int horizontalStride,
int verticalStride, const BoxLobes &lobes, bool transposeInput,
bool transposeOutput)
{
const int inputStep = transposeInput ? verticalStride : horizontalStride;
const int outputStep = transposeOutput ? verticalStride : horizontalStride;
const int boxSize = lobes.left + 1 + lobes.right;
const int reciprocal = (1 << 24) / boxSize;
uint32_t alphaSum = (boxSize + 1) / 2;
const uint8_t *left = src;
const uint8_t *right = src;
uint8_t *out = dst;
const uint8_t firstValue = src[0];
const uint8_t lastValue = src[(width - 1) * inputStep];
alphaSum += firstValue * lobes.left;
const uint8_t *initEnd = src + (boxSize - lobes.left) * inputStep;
while (right < initEnd) {
alphaSum += *right;
right += inputStep;
}
const uint8_t *leftEnd = src + boxSize * inputStep;
while (right < leftEnd) {
*out = (alphaSum * reciprocal) >> 24;
alphaSum += *right - firstValue;
right += inputStep;
out += outputStep;
}
const uint8_t *centerEnd = src + width * inputStep;
while (right < centerEnd) {
*out = (alphaSum * reciprocal) >> 24;
alphaSum += *right - *left;
left += inputStep;
right += inputStep;
out += outputStep;
}
const uint8_t *rightEnd = dst + width * outputStep;
while (out < rightEnd) {
*out = (alphaSum * reciprocal) >> 24;
alphaSum += lastValue - *left;
left += inputStep;
out += outputStep;
}
}
/**
* Blur the alpha channel of a given image.
*
* @param image The input image.
* @param radius The blur radius.
* @param rect Specifies what part of the image to blur. If nothing is provided, then
* the whole alpha channel of the input image will be blurred.
**/
static inline void boxBlurAlpha(QImage &image, int radius, const QRect &rect = {})
{
if (radius < 2) {
return;
}
const QVector<BoxLobes> lobes = computeLobes(radius);
const QRect blurRect = rect.isNull() ? image.rect() : rect;
const int alphaOffset = QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3;
const int width = blurRect.width();
const int height = blurRect.height();
const int rowStride = image.bytesPerLine();
const int pixelStride = image.depth() >> 3;
const int bufferStride = qMax(width, height) * pixelStride;
QScopedPointer<uint8_t, QScopedPointerArrayDeleter<uint8_t> > buf(new uint8_t[2 * bufferStride]);
uint8_t *buf1 = buf.data();
uint8_t *buf2 = buf1 + bufferStride;
// Blur the image in horizontal direction.
for (int i = 0; i < height; ++i) {
uint8_t *row = image.scanLine(blurRect.y() + i) + blurRect.x() * pixelStride + alphaOffset;
boxBlurRowAlpha(row, buf1, width, pixelStride, rowStride, lobes[0], false, false);
boxBlurRowAlpha(buf1, buf2, width, pixelStride, rowStride, lobes[1], false, false);
boxBlurRowAlpha(buf2, row, width, pixelStride, rowStride, lobes[2], false, false);
}
// Blur the image in vertical direction.
for (int i = 0; i < width; ++i) {
uint8_t *column = image.scanLine(blurRect.y()) + (blurRect.x() + i) * pixelStride + alphaOffset;
boxBlurRowAlpha(column, buf1, height, pixelStride, rowStride, lobes[0], true, false);
boxBlurRowAlpha(buf1, buf2, height, pixelStride, rowStride, lobes[1], false, false);
boxBlurRowAlpha(buf2, column, height, pixelStride, rowStride, lobes[2], false, true);
}
}
static inline void mirrorTopLeftQuadrant(QImage &image)
{
const int width = image.width();
const int height = image.height();
const int centerX = qCeil(width * 0.5);
const int centerY = qCeil(height * 0.5);
const int alphaOffset = QSysInfo::ByteOrder == QSysInfo::BigEndian ? 0 : 3;
const int stride = image.depth() >> 3;
for (int y = 0; y < centerY; ++y) {
uint8_t *in = image.scanLine(y) + alphaOffset;
uint8_t *out = in + (width - 1) * stride;
for (int x = 0; x < centerX; ++x, in += stride, out -= stride) {
*out = *in;
}
}
for (int y = 0; y < centerY; ++y) {
const uint8_t *in = image.scanLine(y) + alphaOffset;
uint8_t *out = image.scanLine(width - y - 1) + alphaOffset;
for (int x = 0; x < width; ++x, in += stride, out += stride) {
*out = *in;
}
}
}
static void renderShadow(QPainter *painter, const QRect &rect, qreal borderRadius, const QPoint &offset, int radius, const QColor &color)
{
const QSize inflation = calculateBlurExtent(radius);
const QSize size = rect.size() + 2 * inflation;
const qreal dpr = painter->device()->devicePixelRatioF();
QImage shadow(size * dpr, QImage::Format_ARGB32_Premultiplied);
shadow.setDevicePixelRatio(dpr);
shadow.fill(Qt::transparent);
QRect boxRect(QPoint(0, 0), rect.size());
boxRect.moveCenter(QRect(QPoint(0, 0), size).center());
const qreal xRadius = 2.0 * borderRadius / boxRect.width();
const qreal yRadius = 2.0 * borderRadius / boxRect.height();
QPainter shadowPainter;
shadowPainter.begin(&shadow);
shadowPainter.setRenderHint(QPainter::Antialiasing);
shadowPainter.setPen(Qt::NoPen);
shadowPainter.setBrush(Qt::black);
shadowPainter.drawRoundedRect(boxRect, xRadius, yRadius);
shadowPainter.end();
// Because the shadow texture is symmetrical, that's enough to blur
// only the top-left quadrant and then mirror it.
const QRect blurRect(0, 0, qCeil(shadow.width() * 0.5), qCeil(shadow.height() * 0.5));
const int scaledRadius = qRound(radius * dpr);
boxBlurAlpha(shadow, scaledRadius, blurRect);
mirrorTopLeftQuadrant(shadow);
// Give the shadow a tint of the desired color.
shadowPainter.begin(&shadow);
shadowPainter.setCompositionMode(QPainter::CompositionMode_SourceIn);
shadowPainter.fillRect(shadow.rect(), color);
shadowPainter.end();
// Actually, present the shadow.
QRect shadowRect = shadow.rect();
shadowRect.setSize(shadowRect.size() / dpr);
shadowRect.moveCenter(rect.center() + offset);
painter->drawImage(shadowRect, shadow);
}
void BoxShadowRenderer::setBoxSize(const QSize &size)
{
m_boxSize = size;
}
void BoxShadowRenderer::setBorderRadius(qreal radius)
{
m_borderRadius = radius;
}
void BoxShadowRenderer::setDevicePixelRatio(qreal dpr)
{
m_dpr = dpr;
}
void BoxShadowRenderer::addShadow(const QPoint &offset, int radius, const QColor &color)
{
Shadow shadow = {};
shadow.offset = offset;
shadow.radius = radius;
shadow.color = color;
m_shadows.append(shadow);
}
QImage BoxShadowRenderer::render() const
{
if (m_shadows.isEmpty()) {
return {};
}
QSize canvasSize;
for (const Shadow &shadow : qAsConst(m_shadows)) {
canvasSize = canvasSize.expandedTo(
calculateMinimumShadowTextureSize(m_boxSize, shadow.radius, shadow.offset));
}
QImage canvas(canvasSize * m_dpr, QImage::Format_ARGB32_Premultiplied);
canvas.setDevicePixelRatio(m_dpr);
canvas.fill(Qt::transparent);
QRect boxRect(QPoint(0, 0), m_boxSize);
boxRect.moveCenter(QRect(QPoint(0, 0), canvasSize).center());
QPainter painter(&canvas);
for (const Shadow &shadow : qAsConst(m_shadows)) {
renderShadow(&painter, boxRect, m_borderRadius, shadow.offset, shadow.radius, shadow.color);
}
painter.end();
return canvas;
}
QSize BoxShadowRenderer::calculateMinimumBoxSize(int radius)
{
const QSize blurExtent = calculateBlurExtent(radius);
return 2 * blurExtent + QSize(1, 1);
}
QSize BoxShadowRenderer::calculateMinimumShadowTextureSize(const QSize &boxSize, int radius, const QPoint &offset)
{
return boxSize + 2 * calculateBlurExtent(radius) + QSize(qAbs(offset.x()), qAbs(offset.y()));
}

@ -0,0 +1,97 @@
/*
* Copyright (C) 2018 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#pragma once
// Qt
#include <QColor>
#include <QImage>
#include <QPoint>
#include <QSize>
class BoxShadowRenderer
{
public:
// Compiler generated constructors & destructor are fine.
/**
* Set the size of the box.
* @param size The size of the box.
**/
void setBoxSize(const QSize &size);
/**
* Set the radius of box' corners.
* @param radius The border radius, in pixels.
**/
void setBorderRadius(qreal radius);
/**
* Set the device pixel ratio of the resulting shadow texture.
* @param dpr The device pixel ratio.
**/
void setDevicePixelRatio(qreal dpr);
/**
* Add a shadow.
* @param offset The offset of the shadow.
* @param radius The blur radius.
* @param color The color of the shadow.
**/
void addShadow(const QPoint &offset, int radius, const QColor &color);
/**
* Render the shadow.
**/
QImage render() const;
/**
* Calculate the minimum size of the box.
*
* This helper computes the minimum size of the box so the shadow behind it has
* full its strength.
*
* @param radius The blur radius of the shadow.
**/
static QSize calculateMinimumBoxSize(int radius);
/**
* Calculate the minimum size of the shadow texture.
*
* This helper computes the minimum size of the resulting texture so the shadow
* is not clipped.
*
* @param boxSize The size of the box.
* @param radius The blur radius.
* @param offset The offset of the shadow.
**/
static QSize calculateMinimumShadowTextureSize(const QSize &boxSize, int radius, const QPoint &offset);
private:
QSize m_boxSize;
qreal m_borderRadius = 0.0;
qreal m_dpr = 1.0;
struct Shadow {
QPoint offset;
int radius;
QColor color;
};
QVector<Shadow> m_shadows;
};

@ -0,0 +1,4 @@
{
"Keys": ["cutefish"]
}

@ -0,0 +1,423 @@
/*
* HSLuv-C: Human-friendly HSL
* <http://github.com/hsluv/hsluv-c>
* <http://www.hsluv.org/>
*
* Copyright (c) 2015 Alexei Boronine (original idea, JavaScript implementation)
* Copyright (c) 2015 Roger Tallada (Obj-C implementation)
* Copyright (c) 2017 Martin Mitas (C implementation, based on Obj-C implementation)
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include "phantomcolor.h"
#include <cfloat>
#include <cmath>
namespace Phantom
{
namespace
{
// Th`ese declarations originate from hsluv.h, from the hsluv-c library. The
// hpluv functions have been removed, as they are unnecessary for Phantom.
/**
* Convert HSLuv to RGB.
*
* @param h Hue. Between 0.0 and 360.0.
* @param s Saturation. Between 0.0 and 100.0.
* @param l Lightness. Between 0.0 and 100.0.
* @param[out] pr Red component. Between 0.0 and 1.0.
* @param[out] pr Green component. Between 0.0 and 1.0.
* @param[out] pr Blue component. Between 0.0 and 1.0.
*/
void hsluv2rgb(double h, double s, double l, double* pr, double* pg, double* pb);
/**
* Convert RGB to HSLuv.
*
* @param r Red component. Between 0.0 and 1.0.
* @param g Green component. Between 0.0 and 1.0.
* @param b Blue component. Between 0.0 and 1.0.
* @param[out] ph Hue. Between 0.0 and 360.0.
* @param[out] ps Saturation. Between 0.0 and 100.0.
* @param[out] pl Lightness. Between 0.0 and 100.0.
*/
void rgb2hsluv(double r, double g, double b, double* ph, double* ps, double* pl);
// Contents below originate from hsluv.c from the hsluv-c library. They have
// been wrapped in a C++ namespace to avoid collisions and to reduce the
// translation unit count, and hsluv's own sRGB conversion code has been
// stripped out (sRGB conversion is now performed in the Phantom color code
// when going to/from the Rgb type.)
//
// If you need to update the hsluv-c code, be mindful of the removed sRGB
// conversions -- you will need to make similar modifications to the upstream
// hsluv-c code. Also note that that the hpluv (pastel) functions have been
// removed, as they are not used in Phantom.
typedef struct Triplet_tag Triplet;
struct Triplet_tag
{
double a;
double b;
double c;
};
/* for RGB */
const Triplet m[3] = {{3.24096994190452134377, -1.53738317757009345794, -0.49861076029300328366},
{-0.96924363628087982613, 1.87596750150772066772, 0.04155505740717561247},
{0.05563007969699360846, -0.20397695888897656435, 1.05697151424287856072}};
/* for XYZ */
const Triplet m_inv[3] = {{0.41239079926595948129, 0.35758433938387796373, 0.18048078840183428751},
{0.21263900587151035754, 0.71516867876775592746, 0.07219231536073371500},
{0.01933081871559185069, 0.11919477979462598791, 0.95053215224966058086}};
const double ref_u = 0.19783000664283680764;
const double ref_v = 0.46831999493879100370;
const double kappa = 903.29629629629629629630;
const double epsilon = 0.00885645167903563082;
typedef struct Bounds_tag Bounds;
struct Bounds_tag
{
double a;
double b;
};
void get_bounds(double l, Bounds bounds[6])
{
double tl = l + 16.0;
double sub1 = (tl * tl * tl) / 1560896.0;
double sub2 = (sub1 > epsilon ? sub1 : (l / kappa));
int channel;
int t;
for (channel = 0; channel < 3; channel++) {
double m1 = m[channel].a;
double m2 = m[channel].b;
double m3 = m[channel].c;
for (t = 0; t < 2; t++) {
double top1 = (284517.0 * m1 - 94839.0 * m3) * sub2;
double top2 = (838422.0 * m3 + 769860.0 * m2 + 731718.0 * m1) * l * sub2 - 769860.0 * t * l;
double bottom = (632260.0 * m3 - 126452.0 * m2) * sub2 + 126452.0 * t;
bounds[channel * 2 + t].a = top1 / bottom;
bounds[channel * 2 + t].b = top2 / bottom;
}
}
}
double ray_length_until_intersect(double theta, const Bounds* line)
{
return line->b / (sin(theta) - line->a * cos(theta));
}
double max_chroma_for_lh(double l, double h)
{
double min_len = DBL_MAX;
double hrad = h * 0.01745329251994329577; /* (2 * pi / 360) */
Bounds bounds[6];
int i;
get_bounds(l, bounds);
for (i = 0; i < 6; i++) {
double len = ray_length_until_intersect(hrad, &bounds[i]);
if (len >= 0 && len < min_len)
min_len = len;
}
return min_len;
}
double dot_product(const Triplet* t1, const Triplet* t2)
{
return (t1->a * t2->a + t1->b * t2->b + t1->c * t2->c);
}
void xyz2rgb(Triplet* in_out)
{
double r = dot_product(&m[0], in_out);
double g = dot_product(&m[1], in_out);
double b = dot_product(&m[2], in_out);
in_out->a = r;
in_out->b = g;
in_out->c = b;
}
void rgb2xyz(Triplet* in_out)
{
Triplet rgbl = {in_out->a, in_out->b, in_out->c};
double x = dot_product(&m_inv[0], &rgbl);
double y = dot_product(&m_inv[1], &rgbl);
double z = dot_product(&m_inv[2], &rgbl);
in_out->a = x;
in_out->b = y;
in_out->c = z;
}
/* http://en.wikipedia.org/wiki/CIELUV
* In these formulas, Yn refers to the reference white point. We are using
* illuminant D65, so Yn (see refY in Maxima file) equals 1. The formula is
* simplified accordingly.
*/
double y2l(double y)
{
if (y <= epsilon) {
return y * kappa;
} else {
return 116.0 * cbrt(y) - 16.0;
}
}
double l2y(double l)
{
if (l <= 8.0) {
return l / kappa;
} else {
double x = (l + 16.0) / 116.0;
return (x * x * x);
}
}
void xyz2luv(Triplet* in_out)
{
double divisor = in_out->a + (15.0 * in_out->b) + (3.0 * in_out->c);
if (divisor <= 0.00000001) {
in_out->a = 0.0;
in_out->b = 0.0;
in_out->c = 0.0;
return;
}
double var_u = (4.0 * in_out->a) / divisor;
double var_v = (9.0 * in_out->b) / divisor;
double l = y2l(in_out->b);
double u = 13.0 * l * (var_u - ref_u);
double v = 13.0 * l * (var_v - ref_v);
in_out->a = l;
if (l < 0.00000001) {
in_out->b = 0.0;
in_out->c = 0.0;
} else {
in_out->b = u;
in_out->c = v;
}
}
void luv2xyz(Triplet* in_out)
{
if (in_out->a <= 0.00000001) {
/* Black will create a divide-by-zero error. */
in_out->a = 0.0;
in_out->b = 0.0;
in_out->c = 0.0;
return;
}
double var_u = in_out->b / (13.0 * in_out->a) + ref_u;
double var_v = in_out->c / (13.0 * in_out->a) + ref_v;
double y = l2y(in_out->a);
double x = -(9.0 * y * var_u) / ((var_u - 4.0) * var_v - var_u * var_v);
double z = (9.0 * y - (15.0 * var_v * y) - (var_v * x)) / (3.0 * var_v);
in_out->a = x;
in_out->b = y;
in_out->c = z;
}
void luv2lch(Triplet* in_out)
{
double l = in_out->a;
double u = in_out->b;
double v = in_out->c;
double h;
double c = sqrt(u * u + v * v);
/* Grays: disambiguate hue */
if (c < 0.00000001) {
h = 0;
} else {
h = atan2(v, u) * 57.29577951308232087680; /* (180 / pi) */
if (h < 0.0)
h += 360.0;
}
in_out->a = l;
in_out->b = c;
in_out->c = h;
}
void lch2luv(Triplet* in_out)
{
double hrad = in_out->c * 0.01745329251994329577; /* (pi / 180.0) */
double u = cos(hrad) * in_out->b;
double v = sin(hrad) * in_out->b;
in_out->b = u;
in_out->c = v;
}
void hsluv2lch(Triplet* in_out)
{
double h = in_out->a;
double s = in_out->b;
double l = in_out->c;
double c;
/* White and black: disambiguate chroma */
if (l > 99.9999999 || l < 0.00000001) {
c = 0.0;
} else {
c = max_chroma_for_lh(l, h) / 100.0 * s;
}
/* Grays: disambiguate hue */
if (s < 0.00000001)
h = 0.0;
in_out->a = l;
in_out->b = c;
in_out->c = h;
}
void lch2hsluv(Triplet* in_out)
{
double l = in_out->a;
double c = in_out->b;
double h = in_out->c;
double s;
/* White and black: disambiguate saturation */
if (l > 99.9999999 || l < 0.00000001) {
s = 0.0;
} else {
s = c / max_chroma_for_lh(l, h) * 100.0;
}
/* Grays: disambiguate hue */
if (c < 0.00000001)
h = 0.0;
in_out->a = h;
in_out->b = s;
in_out->c = l;
}
void hsluv2rgb(double h, double s, double l, double* pr, double* pg, double* pb)
{
Triplet tmp = {h, s, l};
hsluv2lch(&tmp);
lch2luv(&tmp);
luv2xyz(&tmp);
xyz2rgb(&tmp);
*pr = tmp.a;
*pg = tmp.b;
*pb = tmp.c;
}
void rgb2hsluv(double r, double g, double b, double* ph, double* ps, double* pl)
{
Triplet tmp = {r, g, b};
rgb2xyz(&tmp);
xyz2luv(&tmp);
luv2lch(&tmp);
lch2hsluv(&tmp);
*ph = tmp.a;
*ps = tmp.b;
*pl = tmp.c;
}
} // namespace
} // namespace Phantom
// The code below is for Phantom, and is used for the Rgb/Hsl-based interface
// for color operations.
namespace Phantom
{
namespace
{
// Note: these constants might be out of range when qreal is defined as float
// instead of double.
inline qreal linear_of_srgb(qreal x)
{
return x < 0.0404482362771082 ? x / 12.92 : std::pow((x + 0.055) / 1.055, 2.4f);
}
inline qreal srgb_of_linear(qreal x)
{
return x < 0.00313066844250063 ? x * 12.92 : std::pow(x, 1.0 / 2.4) * 1.055 - 0.055;
}
} // namespace
Rgb rgb_of_qcolor(const QColor& color)
{
Rgb a;
a.r = linear_of_srgb(color.red() / 255.0);
a.g = linear_of_srgb(color.green() / 255.0);
a.b = linear_of_srgb(color.blue() / 255.0);
return a;
}
Hsl hsl_of_rgb(qreal r, qreal g, qreal b)
{
double h, s, l;
rgb2hsluv(r, g, b, &h, &s, &l);
s /= 100.0;
l /= 100.0;
return {h, s, l};
}
Rgb rgb_of_hsl(qreal h, qreal s, qreal l)
{
double r, g, b;
hsluv2rgb(h, s * 100.0, l * 100.0, &r, &g, &b);
return {r, g, b};
}
QColor qcolor_of_rgb(qreal r, qreal g, qreal b)
{
int r_ = static_cast<int>(std::lround(srgb_of_linear(r) * 255.0));
int g_ = static_cast<int>(std::lround(srgb_of_linear(g) * 255.0));
int b_ = static_cast<int>(std::lround(srgb_of_linear(b) * 255.0));
return {r_, g_, b_};
}
QColor lerpQColor(const QColor& x, const QColor& y, qreal a)
{
Rgb x_ = rgb_of_qcolor(x);
Rgb y_ = rgb_of_qcolor(y);
Rgb z = Rgb::lerp(x_, y_, a);
return qcolor_of_rgb(z.r, z.g, z.b);
}
Rgb Rgb::lerp(const Rgb& x, const Rgb& y, qreal a)
{
Rgb z;
z.r = (1.0 - a) * x.r + a * y.r;
z.g = (1.0 - a) * x.g + a * y.g;
z.b = (1.0 - a) * x.b + a * y.b;
return z;
}
} // namespace Phantom

@ -0,0 +1,165 @@
/*
* HSLuv-C: Human-friendly HSL
* <http://github.com/hsluv/hsluv-c>
* <http://www.hsluv.org/>
*
* Copyright (c) 2015 Alexei Boronine (original idea, JavaScript implementation)
* Copyright (c) 2015 Roger Tallada (Obj-C implementation)
* Copyright (c) 2017 Martin Mitas (C implementation, based on Obj-C implementation)
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#ifndef PHANTOMCOLOR_H
#define PHANTOMCOLOR_H
#include <QColor>
namespace Phantom
{
struct Rgb;
struct Hsl;
// A color presumed to be in linear space, represented as RGB. Values are in
// the range 0.0 - 1.0. Conversions to and from QColor will assume the QColor
// is in sRGB space, and sRGB conversion will be performed.
struct Rgb
{
qreal r, g, b;
Rgb()
{
}
Rgb(qreal r, qreal g, qreal b)
: r(r)
, g(g)
, b(b)
{
}
inline Hsl toHsl() const;
inline QColor toQColor() const;
static inline Rgb ofHsl(const Hsl&);
static inline Rgb ofQColor(const QColor&);
static Rgb lerp(const Rgb& x, const Rgb& y, qreal a);
};
// A color represented as pseudo-CIE hue, saturation, and lightness. Hue is in
// the range 0.0 - 360.0 (degrees). Lightness and saturation are in the range
// 0.0 - 1.0. Using this and making adjustments to the L value will produce
// more consistent and predictable results than QColor's .darker()/.lighter().
// Note that this is not strictly CIE -- some of the colorspace is distorted so
// that it can represented as a continuous coordinate space. Therefore not all
// adjustments to the parameters will produce perfectly linear results with
// regards to saturation and lightness. But it's still useful, and better than
// QColor's .darker()/.lighter(). Additionally, the L value is more useful for
// performing comparisons between two colors to measure relative and absolute
// brightness.
//
// See the documentation for the hsluv library for more information. (Note that
// for consistency we treat the S and L values in the range 0.0 - 1.0 instead
// of 0.0 - 100.0 like hsluv-c on its own does.)
struct Hsl
{
qreal h, s, l;
Hsl()
{
}
Hsl(qreal h, qreal s, qreal l)
: h(h)
, s(s)
, l(l)
{
}
inline Rgb toRgb() const;
inline QColor toQColor() const;
static inline Hsl ofRgb(const Rgb&);
static inline Hsl ofQColor(const QColor&);
};
Rgb rgb_of_qcolor(const QColor& color);
QColor qcolor_of_rgb(qreal r, qreal g, qreal b);
Hsl hsl_of_rgb(qreal r, qreal g, qreal b);
Rgb rgb_of_hsl(qreal h, qreal s, qreal l);
// Clip a floating point value to the range 0.0 - 1.0.
inline qreal saturate(qreal x)
{
if (x < 0.0)
return 0.0;
if (x > 1.0)
return 1.0;
return x;
}
inline qreal lerp(qreal x, qreal y, qreal a)
{
return (1.0 - a) * x + a * y;
}
// Linearly interpolate two QColors after trasnforming them to linear color
// space, treating the QColor values as if they were in sRGB space. The
// returned QColor is converted back to sRGB space.
QColor lerpQColor(const QColor& x, const QColor& y, qreal a);
Hsl Rgb::toHsl() const
{
return hsl_of_rgb(r, g, b);
}
QColor Rgb::toQColor() const
{
return qcolor_of_rgb(r, g, b);
}
Rgb Rgb::ofHsl(const Hsl& hsl)
{
return rgb_of_hsl(hsl.h, hsl.s, hsl.l);
}
Rgb Rgb::ofQColor(const QColor& color)
{
return rgb_of_qcolor(color);
}
Rgb Hsl::toRgb() const
{
return rgb_of_hsl(h, s, l);
}
QColor Hsl::toQColor() const
{
Rgb rgb = rgb_of_hsl(h, s, l);
return qcolor_of_rgb(rgb.r, rgb.g, rgb.b);
}
Hsl Hsl::ofRgb(const Rgb& rgb)
{
return hsl_of_rgb(rgb.r, rgb.g, rgb.b);
}
Hsl Hsl::ofQColor(const QColor& color)
{
Rgb rgb = rgb_of_qcolor(color);
return hsl_of_rgb(rgb.r, rgb.g, rgb.b);
}
} // namespace Phantom
#endif

@ -0,0 +1,20 @@
#include "pstyleplugin.h"
#include "basestyle.h"
#include <QApplication>
#include <QStyleFactory>
#include <QDebug>
QStringList ProxyStylePlugin::keys() const
{
return {"cutefish"};
}
QStyle *ProxyStylePlugin::create(const QString &key)
{
if (key != QStringLiteral("cutefish")) {
return nullptr;
}
return new BaseStyle;
}

@ -0,0 +1,18 @@
#ifndef PSTYLEPLUGIN_H
#define PSTYLEPLUGIN_H
#include <QStylePlugin>
class ProxyStylePlugin : public QStylePlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QStyleFactoryInterface" FILE "cutefishstyle.json")
public:
ProxyStylePlugin() = default;
QStringList keys() const;
QStyle *create(const QString &key) override;
};
#endif // PSTYLEPLUGIN_H

@ -0,0 +1,414 @@
/*************************************************************************
* Copyright (C) 2014 by Hugo Pereira Da Costa <hugo.pereira@free.fr> *
* Copyright (C) 2018, 2020 by Vlad Zahorodnii <vlad.zahorodnii@kde.org> *
* Copyright (C) 2020, 2020 by Reven Martin <revenmartin@gmail.com> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . *
*************************************************************************/
#include "shadowhelper.h"
#include "boxshadowrenderer.h"
#include <QDockWidget>
#include <QEvent>
#include <QApplication>
#include <QMenu>
#include <QPainter>
#include <QPixmap>
#include <QPlatformSurfaceEvent>
#include <QToolBar>
#include <QTextStream>
#include <KWindowSystem>
const char netWMSkipShadow[] = "_CYBER_NET_WM_SKIP_SHADOW";
const char netWMForceShadow[] = "_CYBER_NET_WM_FORCE_SHADOW";
const char netWMFrameRadius[] = "_CYBER_NET_WM_FRAME_RADIUS";
enum {
ShadowNone,
ShadowSmall,
ShadowMedium,
ShadowLarge,
ShadowVeryLarge
};
const CompositeShadowParams s_shadowParams[] = {
// None
CompositeShadowParams(),
// Small
CompositeShadowParams(
QPoint(0, 3),
ShadowParams(QPoint(0, 0), 16, 0.26),
ShadowParams(QPoint(0, -2), 8, 0.16)),
// Medium
CompositeShadowParams(
QPoint(0, 4),
ShadowParams(QPoint(0, 0), 20, 0.24),
ShadowParams(QPoint(0, -2), 10, 0.14)),
// Large
CompositeShadowParams(
QPoint(0, 5),
ShadowParams(QPoint(0, 0), 24, 0.22),
ShadowParams(QPoint(0, -3), 12, 0.12)),
// Very Large
CompositeShadowParams(
QPoint(0, 6),
ShadowParams(QPoint(0, 0), 32, 0.2),
ShadowParams(QPoint(0, -3), 16, 0.1))
};
ShadowHelper::ShadowHelper(QObject * parent)
: QObject(parent),
m_frameRadius(5)
{
}
ShadowHelper::~ShadowHelper()
{
}
CompositeShadowParams ShadowHelper::lookupShadowParams(int shadowSizeEnum)
{
switch (shadowSizeEnum) {
case ShadowNone:
return s_shadowParams[0];
case ShadowSmall:
return s_shadowParams[1];
case ShadowMedium:
return s_shadowParams[2];
case ShadowLarge:
return s_shadowParams[3];
case ShadowVeryLarge:
return s_shadowParams[4];
default:
// Fallback to the Large size.
return s_shadowParams[3];
}
}
bool ShadowHelper::registerWidget(QWidget *widget, bool force)
{
// make sure widget is not already registered
if (m_widgets.contains(widget))
return false;
// check if widget qualifies
if (!(force || acceptWidget(widget)))
return false;
qreal frameRadius = m_frameRadius;
const auto frameRadiusProperty = widget->property(netWMFrameRadius);
if (frameRadiusProperty.isValid())
frameRadius = frameRadiusProperty.toReal();
installShadows(widget, shadowTiles(frameRadius));
m_widgets.insert(widget);
// install event filter
widget->removeEventFilter(this);
widget->installEventFilter(this);
// connect destroy signal
connect(widget, &QObject::destroyed, this, &ShadowHelper::objectDeleted);
return true;
}
void ShadowHelper::unregisterWidget(QWidget *widget)
{
if (m_widgets.remove(widget)) {
// uninstall the event filter
widget->removeEventFilter(this);
// disconnect all signals
disconnect(widget, nullptr, this, nullptr);
// uninstall the shadow
uninstallShadows(widget);
}
}
bool ShadowHelper::eventFilter(QObject *object, QEvent *event)
{
if (KWindowSystem::isPlatformX11()) {
// check event type
if (event->type() == QEvent::WinIdChange) {
QWidget *widget = static_cast<QWidget *>(object);
qreal frameRadius = m_frameRadius;
const auto frameRadiusProperty = widget->property(netWMFrameRadius);
if (frameRadiusProperty.isValid())
frameRadius = frameRadiusProperty.toReal();
TileSet shadowTileSet = shadowTiles(frameRadius);
installShadows(widget, shadowTileSet);
}
} else {
if (event->type() != QEvent::PlatformSurface)
return false;
QWidget *widget(static_cast<QWidget *>(object));
QPlatformSurfaceEvent* surfaceEvent(static_cast<QPlatformSurfaceEvent*>(event));
switch (surfaceEvent->surfaceEventType()) {
case QPlatformSurfaceEvent::SurfaceCreated:
//installShadows(widget);
break;
case QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed:
// Don't care.
break;
}
}
return false;
}
TileSet ShadowHelper::shadowTiles(const qreal frameRadius)
{
const CompositeShadowParams params = lookupShadowParams(ShadowVeryLarge);
if (params.isNone())
return TileSet();
// } else if (_shadowTiles.isValid()) {
// return _shadowTiles;
// }
auto withOpacity = [](const QColor &color, qreal opacity) -> QColor {
QColor c(color);
c.setAlphaF(opacity);
return c;
};
const QColor color = Qt::black;
// const qreal strength = static_cast<qreal>(255) / 255.0;
const qreal strength = 1.5;
const QSize boxSize = BoxShadowRenderer::calculateMinimumBoxSize(params.shadow1.radius)
.expandedTo(BoxShadowRenderer::calculateMinimumBoxSize(params.shadow2.radius));
const qreal dpr = qApp->devicePixelRatio();
BoxShadowRenderer shadowRenderer;
shadowRenderer.setBorderRadius(frameRadius);
shadowRenderer.setBoxSize(boxSize);
shadowRenderer.setDevicePixelRatio(dpr);
shadowRenderer.addShadow(params.shadow1.offset, params.shadow1.radius,
withOpacity(color, params.shadow1.opacity * strength));
shadowRenderer.addShadow(params.shadow2.offset, params.shadow2.radius,
withOpacity(color, params.shadow2.opacity * strength));
QImage shadowTexture = shadowRenderer.render();
const QRect outerRect(QPoint(0, 0), shadowTexture.size() / dpr);
QRect boxRect(QPoint(0, 0), boxSize);
boxRect.moveCenter(outerRect.center());
// Mask out inner rect.
QPainter painter(&shadowTexture);
painter.setRenderHint(QPainter::Antialiasing);
int Shadow_Overlap = 3;
const QMargins margins = QMargins(
boxRect.left() - outerRect.left() - Shadow_Overlap - params.offset.x(),
boxRect.top() - outerRect.top() - Shadow_Overlap - params.offset.y(),
outerRect.right() - boxRect.right() - Shadow_Overlap + params.offset.x(),
outerRect.bottom() - boxRect.bottom() - Shadow_Overlap + params.offset.y());
painter.setPen(Qt::NoPen);
painter.setBrush(Qt::black);
painter.setCompositionMode(QPainter::CompositionMode_DestinationOut);
painter.drawRoundedRect(
outerRect - margins,
frameRadius,
frameRadius);
// We're done.
painter.end();
const QPoint innerRectTopLeft = outerRect.center();
TileSet tiles = TileSet(
QPixmap::fromImage(shadowTexture),
innerRectTopLeft.x(),
innerRectTopLeft.y(),
1, 1);
return tiles;
}
void ShadowHelper::objectDeleted(QObject *object)
{
QWidget *widget(static_cast<QWidget *>(object));
m_widgets.remove(widget);
m_shadows.remove(widget);
}
bool ShadowHelper::isMenu(QWidget *widget) const
{
return qobject_cast<QMenu*>(widget);
}
bool ShadowHelper::isToolTip(QWidget *widget) const
{
return widget->inherits("QTipLabel") || (widget->windowFlags() & Qt::WindowType_Mask) == Qt::ToolTip;
}
bool ShadowHelper::isDockWidget(QWidget *widget) const
{
return qobject_cast<QDockWidget*>(widget);
}
bool ShadowHelper::isToolBar(QWidget *widget) const
{
return qobject_cast<QToolBar*>(widget);
}
bool ShadowHelper::acceptWidget(QWidget *widget) const
{
// flags
if (widget->property(netWMSkipShadow).toBool())
return false;
if (widget->property(netWMForceShadow).toBool())
return true;
// menus
if (isMenu(widget))
return true;
// combobox dropdown lists
if (widget->inherits("QComboBoxPrivateContainer"))
return true;
// tooltips
if (isToolTip(widget) && !widget->inherits("Plasma::ToolTip"))
return true;
// detached widgets
if (isDockWidget(widget) || isToolBar(widget))
return true;
// reject
return false;
}
KWindowShadowTile::Ptr ShadowHelper::createTile(const QPixmap& source)
{
KWindowShadowTile::Ptr tile = KWindowShadowTile::Ptr::create();
tile->setImage(source.toImage());
return tile;
}
void ShadowHelper::installShadows(QWidget *widget, TileSet shadowTiles)
{
if (!widget)
return;
// only toplevel widgets can cast drop-shadows
if (!widget->isWindow())
return;
// widget must have valid native window
if (!widget->testAttribute(Qt::WA_WState_Created))
return;
// create platform shadow tiles
QVector<KWindowShadowTile::Ptr> tiles = {
createTile(shadowTiles.pixmap(1)),
createTile(shadowTiles.pixmap(2)),
createTile(shadowTiles.pixmap(5)),
createTile(shadowTiles.pixmap(8)),
createTile(shadowTiles.pixmap(7)),
createTile(shadowTiles.pixmap(6)),
createTile(shadowTiles.pixmap(3)),
createTile(shadowTiles.pixmap(0))
};
if (tiles.count() != numTiles)
return;
// find a shadow associated with the widget
KWindowShadow*& shadow = m_shadows[ widget ];
if (!shadow)
shadow = new KWindowShadow(widget);
if (shadow->isCreated())
shadow->destroy();
shadow->setTopTile(tiles[ 0 ]);
shadow->setTopRightTile(tiles[ 1 ]);
shadow->setRightTile(tiles[ 2 ]);
shadow->setBottomRightTile(tiles[ 3 ]);
shadow->setBottomTile(tiles[ 4 ]);
shadow->setBottomLeftTile(tiles[ 5 ]);
shadow->setLeftTile(tiles[ 6 ]);
shadow->setTopLeftTile(tiles[ 7 ]);
shadow->setPadding(shadowMargins(widget, shadowTiles));
shadow->setWindow(widget->windowHandle());
shadow->create();
}
QMargins ShadowHelper::shadowMargins(QWidget *widget, TileSet shadowTiles) const
{
const CompositeShadowParams params = lookupShadowParams(ShadowVeryLarge);
if (params.isNone())
return QMargins();
const QSize boxSize = BoxShadowRenderer::calculateMinimumBoxSize(params.shadow1.radius)
.expandedTo(BoxShadowRenderer::calculateMinimumBoxSize(params.shadow2.radius));
const QSize shadowSize = BoxShadowRenderer::calculateMinimumShadowTextureSize(boxSize, params.shadow1.radius, params.shadow1.offset)
.expandedTo(BoxShadowRenderer::calculateMinimumShadowTextureSize(boxSize, params.shadow2.radius, params.shadow2.offset));
const QRect shadowRect(QPoint(0, 0), shadowSize);
QRect boxRect(QPoint(0, 0), boxSize);
boxRect.moveCenter(shadowRect.center());
int Shadow_Overlap = 3;
QMargins margins(
boxRect.left() - shadowRect.left() - Shadow_Overlap - params.offset.x(),
boxRect.top() - shadowRect.top() - Shadow_Overlap - params.offset.y(),
shadowRect.right() - boxRect.right() - Shadow_Overlap + params.offset.x(),
shadowRect.bottom() - boxRect.bottom() - Shadow_Overlap + params.offset.y());
if (widget->inherits("QBalloonTip")) {
// Balloon tip needs special margins to deal with the arrow.
int top = widget->contentsMargins().top();
int bottom = widget->contentsMargins().bottom();
// Need to decrement default size further due to extra hard coded round corner.
margins -= 1;
// Arrow can be either to the top or the bottom. Adjust margins accordingly.
const int diff = qAbs(top - bottom);
if (top > bottom) {
margins.setTop(margins.top() - diff);
} else {
margins.setBottom(margins.bottom() - diff);
}
}
margins *= shadowTiles.pixmap(0).devicePixelRatio();
return margins;
}
void ShadowHelper::uninstallShadows(QWidget *widget)
{
delete m_shadows.take(widget);
}

@ -0,0 +1,148 @@
/*************************************************************************
* Copyright (C) 2014 by Hugo Pereira Da Costa <hugo.pereira@free.fr> *
* Copyright (C) 2020 by Vlad Zahorodnii <vlad.zahorodnii@kde.org> *
* Copyright (C) 2020, 2020 by Reven Martin <revenmartin@gmail.com> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . *
*************************************************************************/
#ifndef SHADOWHELPER_H
#define SHADOWHELPER_H
#include "tileset.h"
#include <KWindowShadow>
#include <QObject>
#include <QPointer>
#include <QMap>
#include <QMargins>
#include <QSet>
struct ShadowParams
{
ShadowParams() = default;
ShadowParams(const QPoint &offset, int radius, qreal opacity):
offset(offset),
radius(radius),
opacity(opacity)
{}
QPoint offset;
int radius = 0;
qreal opacity = 0;
};
struct CompositeShadowParams
{
CompositeShadowParams() = default;
CompositeShadowParams(
const QPoint &offset,
const ShadowParams &shadow1,
const ShadowParams &shadow2)
: offset(offset)
, shadow1(shadow1)
, shadow2(shadow2) {}
bool isNone() const
{ return qMax(shadow1.radius, shadow2.radius) == 0; }
QPoint offset;
ShadowParams shadow1;
ShadowParams shadow2;
};
//* handle shadow pixmaps passed to window manager via X property
class ShadowHelper: public QObject
{
Q_OBJECT
public:
//* constructor
ShadowHelper(QObject *);
//* destructor
~ShadowHelper() override;
//* shadow params from size enum
static CompositeShadowParams lookupShadowParams(int shadowSizeEnum);
//* register widget
bool registerWidget(QWidget *, bool force = false);
//* unregister widget
void unregisterWidget(QWidget *);
//* event filter
bool eventFilter(QObject *, QEvent *) override;
void setFrameRadius(qreal radius) { m_frameRadius = radius; }
//* shadow tiles
/** is public because it is also needed for mdi windows */
// TileSet shadowTiles();
TileSet shadowTiles(const qreal frameRadius);
protected Q_SLOTS:
//* unregister widget
void objectDeleted(QObject *);
protected:
//* true if widget is a menu
bool isMenu(QWidget *) const;
//* true if widget is a tooltip
bool isToolTip(QWidget *) const;
//* dock widget
bool isDockWidget(QWidget *) const;
//* toolbar
bool isToolBar(QWidget *) const;
//* accept widget
bool acceptWidget(QWidget *) const;
// create shadow tile from pixmap
KWindowShadowTile::Ptr createTile(const QPixmap &);
//* installs shadow on given widget in a platform independent way
// void installShadows( QWidget * );
void installShadows(QWidget *widget, TileSet shadowTiles);
//* uninstalls shadow on given widget in a platform independent way
void uninstallShadows(QWidget *);
//* gets the shadow margins for the given widget
QMargins shadowMargins(QWidget*, TileSet) const;
private:
//* registered widgets
QSet<QWidget *> m_widgets;
//* managed shadows
QMap<QWidget *, KWindowShadow *> m_shadows;
qreal m_frameRadius;
//* number of tiles
enum { numTiles = 8 };
};
#endif

@ -0,0 +1,183 @@
/*************************************************************************
* Copyright (C) 2014 by Hugo Pereira Da Costa <hugo.pereira@free.fr> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . *
*************************************************************************/
#include "tileset.h"
#include <QPainter>
//___________________________________________________________
inline bool bits(TileSet::Tiles flags, TileSet::Tiles testFlags)
{ return (flags & testFlags) == testFlags; }
//______________________________________________________________________________________
inline qreal devicePixelRatio( const QPixmap& pixmap )
{
return pixmap.devicePixelRatio();
}
//______________________________________________________________________________________
inline void setDevicePixelRatio( QPixmap& pixmap, qreal value )
{
return pixmap.setDevicePixelRatio( value );
}
//______________________________________________________________
void TileSet::initPixmap( PixmapList& pixmaps, const QPixmap &source, int width, int height, const QRect &rect)
{
QSize size( width, height );
if( !( size.isValid() && rect.isValid() ) )
{
pixmaps.append( QPixmap() );
} else if( size != rect.size() ) {
const qreal dpiRatio( devicePixelRatio( source ) );
const QRect scaledRect( rect.topLeft()*dpiRatio, rect.size()*dpiRatio );
const QSize scaledSize( size*dpiRatio );
const QPixmap tile( source.copy(scaledRect) );
QPixmap pixmap( scaledSize );
pixmap.fill(Qt::transparent);
QPainter painter(&pixmap);
painter.drawTiledPixmap(0, 0, scaledSize.width(), scaledSize.height(), tile);
setDevicePixelRatio( pixmap, dpiRatio );
pixmaps.append( pixmap );
} else {
const qreal dpiRatio( devicePixelRatio( source ) );
const QRect scaledRect( rect.topLeft()*dpiRatio, rect.size()*dpiRatio );
QPixmap pixmap( source.copy( scaledRect ) );
setDevicePixelRatio( pixmap, dpiRatio );
pixmaps.append( pixmap );
}
}
//______________________________________________________________
TileSet::TileSet():
_w1(0),
_h1(0),
_w3(0),
_h3(0)
{ _pixmaps.reserve(9); }
//______________________________________________________________
TileSet::TileSet(const QPixmap &source, int w1, int h1, int w2, int h2 ):
_w1(w1),
_h1(h1),
_w3(0),
_h3(0)
{
_pixmaps.reserve(9);
if( source.isNull() ) return;
_w3 = source.width()/devicePixelRatio( source ) - (w1 + w2);
_h3 = source.height()/devicePixelRatio( source ) - (h1 + h2);
int w = w2;
int h = h2;
// initialise pixmap array
initPixmap( _pixmaps, source, _w1, _h1, QRect(0, 0, _w1, _h1) );
initPixmap( _pixmaps, source, w, _h1, QRect(_w1, 0, w2, _h1) );
initPixmap( _pixmaps, source, _w3, _h1, QRect(_w1+w2, 0, _w3, _h1) );
initPixmap( _pixmaps, source, _w1, h, QRect(0, _h1, _w1, h2) );
initPixmap( _pixmaps, source, w, h, QRect(_w1, _h1, w2, h2) );
initPixmap( _pixmaps, source, _w3, h, QRect(_w1+w2, _h1, _w3, h2) );
initPixmap( _pixmaps, source, _w1, _h3, QRect(0, _h1+h2, _w1, _h3) );
initPixmap( _pixmaps, source, w, _h3, QRect(_w1, _h1+h2, w2, _h3) );
initPixmap( _pixmaps, source, _w3, _h3, QRect(_w1+w2, _h1+h2, _w3, _h3) );
}
//___________________________________________________________
void TileSet::render(const QRect &constRect, QPainter *painter, Tiles tiles) const
{
const bool oldHint( painter->testRenderHint( QPainter::SmoothPixmapTransform ) );
painter->setRenderHint( QPainter::SmoothPixmapTransform, true );
// check initialization
if( _pixmaps.size() < 9 ) return;
// copy source rect
QRect rect( constRect );
// get rect dimensions
int x0, y0, w, h;
rect.getRect(&x0, &y0, &w, &h);
// calculate pixmaps widths
int wLeft(0);
int wRight(0);
if( _w1+_w3 > 0 )
{
qreal wRatio( qreal( _w1 )/qreal( _w1 + _w3 ) );
wLeft = (tiles&Right) ? qMin( _w1, int(w*wRatio) ):_w1;
wRight = (tiles&Left) ? qMin( _w3, int(w*(1.0-wRatio)) ):_w3;
}
// calculate pixmap heights
int hTop(0);
int hBottom(0);
if( _h1+_h3 > 0 )
{
qreal hRatio( qreal( _h1 )/qreal( _h1 + _h3 ) );
hTop = (tiles&Bottom) ? qMin( _h1, int(h*hRatio) ):_h1;
hBottom = (tiles&Top) ? qMin( _h3, int(h*(1.0-hRatio)) ):_h3;
}
// calculate corner locations
w -= wLeft + wRight;
h -= hTop + hBottom;
const int x1 = x0 + wLeft;
const int x2 = x1 + w;
const int y1 = y0 + hTop;
const int y2 = y1 + h;
const int w2 = _pixmaps.at(7).width()/devicePixelRatio( _pixmaps.at(7) );
const int h2 = _pixmaps.at(5).height()/devicePixelRatio( _pixmaps.at(5) );
// corner
if( bits( tiles, Top|Left) ) painter->drawPixmap(x0, y0, _pixmaps.at(0), 0, 0, wLeft*devicePixelRatio( _pixmaps.at(0) ), hTop*devicePixelRatio( _pixmaps.at(0) ));
if( bits( tiles, Top|Right) ) painter->drawPixmap(x2, y0, _pixmaps.at(2), (_w3-wRight)*devicePixelRatio( _pixmaps.at(2) ), 0, wRight*devicePixelRatio( _pixmaps.at(2) ), hTop*devicePixelRatio( _pixmaps.at(2) ) );
if( bits( tiles, Bottom|Left) ) painter->drawPixmap(x0, y2, _pixmaps.at(6), 0, (_h3-hBottom)*devicePixelRatio( _pixmaps.at(6) ), wLeft*devicePixelRatio( _pixmaps.at(6) ), hBottom*devicePixelRatio( _pixmaps.at(6) ));
if( bits( tiles, Bottom|Right) ) painter->drawPixmap(x2, y2, _pixmaps.at(8), (_w3-wRight)*devicePixelRatio( _pixmaps.at(8) ), (_h3-hBottom)*devicePixelRatio( _pixmaps.at(8) ), wRight*devicePixelRatio( _pixmaps.at(8) ), hBottom*devicePixelRatio( _pixmaps.at(8) ) );
// top and bottom
if( w > 0 )
{
if( tiles&Top ) painter->drawPixmap(x1, y0, w, hTop, _pixmaps.at(1), 0, 0, w2*devicePixelRatio( _pixmaps.at(1) ), hTop*devicePixelRatio( _pixmaps.at(1) ) );
if( tiles&Bottom ) painter->drawPixmap(x1, y2, w, hBottom, _pixmaps.at(7), 0, (_h3-hBottom)*devicePixelRatio( _pixmaps.at(7) ), w2*devicePixelRatio( _pixmaps.at(7) ), hBottom*devicePixelRatio( _pixmaps.at(7) ) );
}
// left and right
if( h > 0 )
{
if( tiles&Left ) painter->drawPixmap(x0, y1, wLeft, h, _pixmaps.at(3), 0, 0, wLeft*devicePixelRatio( _pixmaps.at(3) ), h2*devicePixelRatio( _pixmaps.at(3) ) );
if( tiles&Right ) painter->drawPixmap(x2, y1, wRight, h, _pixmaps.at(5), (_w3-wRight)*devicePixelRatio( _pixmaps.at(5) ), 0, wRight*devicePixelRatio( _pixmaps.at(5) ), h2*devicePixelRatio( _pixmaps.at(5) ) );
}
// center
if( (tiles&Center) && h > 0 && w > 0 ) painter->drawPixmap(x1, y1, w, h, _pixmaps.at(4));
// restore
painter->setRenderHint( QPainter::SmoothPixmapTransform, oldHint );
}

@ -0,0 +1,121 @@
#ifndef TILESET_H
#define TILESET_H
/*************************************************************************
* Copyright (C) 2014 by Hugo Pereira Da Costa <hugo.pereira@free.fr> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . *
*************************************************************************/
#include <QPixmap>
#include <QRect>
#include <QVector>
//* handles proper scaling of pixmap to match widget rect.
/**
tilesets are collections of stretchable pixmaps corresponding to a given widget corners, sides, and center.
corner pixmaps are never stretched. center pixmaps are
*/
class TileSet
{
public:
/**
Create a TileSet from a pixmap. The size of the bottom/right chunks is
whatever is left over from the other chunks, whose size is specified
in the required parameters.
@param w1 width of the left chunks
@param h1 height of the top chunks
@param w2 width of the not-left-or-right chunks
@param h2 height of the not-top-or-bottom chunks
*/
TileSet(const QPixmap&, int w1, int h1, int w2, int h2 );
//* empty constructor
TileSet();
//* destructor
virtual ~TileSet()
{}
/**
Flags specifying what sides to draw in ::render. Corners are drawn when
the sides forming that corner are drawn, e.g. Top|Left draws the
top-center, center-left, and top-left chunks. The center-center chunk is
only drawn when Center is requested.
*/
enum Tile {
Top = 0x1,
Left = 0x2,
Bottom = 0x4,
Right = 0x8,
Center = 0x10,
TopLeft = Top|Left,
TopRight = Top|Right,
BottomLeft = Bottom|Left,
BottomRight = Bottom|Right,
Ring = Top|Left|Bottom|Right,
Horizontal = Left|Right|Center,
Vertical = Top|Bottom|Center,
Full = Ring|Center
};
Q_DECLARE_FLAGS(Tiles, Tile)
/**
Fills the specified rect with tiled chunks. Corners are never tiled,
edges are tiled in one direction, and the center chunk is tiled in both
directions. Partial tiles are used as needed so that the entire rect is
perfectly filled. Filling is performed as if all chunks are being drawn.
*/
void render(const QRect&, QPainter*, Tiles = Ring) const;
//* return size associated to this tileset
QSize size() const
{ return QSize( _w1 + _w3, _h1 + _h3 ); }
//* is valid
bool isValid() const
{ return _pixmaps.size() == 9; }
//* returns pixmap for given index
QPixmap pixmap( int index ) const
{ return _pixmaps[index]; }
protected:
//* shortcut to pixmap list
using PixmapList = QVector<QPixmap>;
//* initialize pixmap
void initPixmap( PixmapList&, const QPixmap&, int w, int h, const QRect& );
private:
//* pixmap arry
PixmapList _pixmaps;
// dimensions
int _w1;
int _h1;
int _w3;
int _h3;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(TileSet::Tiles)
#endif //TILESET_H
Loading…
Cancel
Save