mirror of https://github.com/cutefishos/qt-plugins
init
commit
bcabaa6149
@ -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 ¶mList) 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…
Reference in New Issue