refactor(qt-plugins): remove X11 integrations

main
reionwong 3 weeks ago
parent dccded7c80
commit 688d4d44d9

@ -4,7 +4,7 @@ Unify Qt application style of CutefishOS.
## Dependencies
`sudo pacman -S gcc extra-cmake-modules qt5-base qt5-tools qt5-x11extras libqtxdg libdbusmenu-qt5 libxcb`
`sudo pacman -S gcc extra-cmake-modules qt6-base qt6-tools libqtxdg`
## Build

4
debian/control vendored

@ -5,12 +5,8 @@ Maintainer: CutefishOS <support@cutefishos.com>
Build-Depends: cmake,
debhelper (>= 9),
extra-cmake-modules,
libkf6windowsystem-dev,
libqt6xdg-dev,
pkg-config,
libxcb-shape0-dev,
libxcb-icccm4-dev,
libxcb1-dev,
qt6-base-dev,
qt6-base-private-dev,
qt6-declarative-dev

@ -10,7 +10,6 @@ find_package(Qt6Gui CONFIG QUIET COMPONENTS Private)
if(NOT TARGET Qt6::GuiPrivate)
find_package(Qt6 REQUIRED COMPONENTS GuiPrivate)
endif()
find_package(KF6WindowSystem REQUIRED)
find_package(PkgConfig REQUIRED)
pkg_check_modules(Qt6XdgIconLoader REQUIRED IMPORTED_TARGET Qt6XdgIconLoader)
find_path(QT6XDGICONLOADER_PRIVATE_INCLUDE_DIR
@ -20,9 +19,6 @@ find_path(QT6XDGICONLOADER_PRIVATE_INCLUDE_DIR
REQUIRED
)
# xcb-ewmh was never used by this plugin and is no longer pulled in by Qt.
pkg_check_modules(XCB REQUIRED IMPORTED_TARGET xcb x11)
set (SRCS
main.cpp
platformtheme.h
@ -31,8 +27,6 @@ set (SRCS
hintsettings.cpp
systemtrayicon.h
systemtrayicon.cpp
x11integration.h
x11integration.cpp
statusnotifier/dbustypes.h
statusnotifier/dbustypes.cpp
statusnotifier/statusnotifieritem.h
@ -63,8 +57,6 @@ target_link_libraries(cutefishplatformtheme PRIVATE
Qt6::DBus
PkgConfig::Qt6XdgIconLoader
KF6::WindowSystem
PkgConfig::XCB
)
target_include_directories(cutefishplatformtheme PRIVATE "${QT6XDGICONLOADER_PRIVATE_INCLUDE_DIR}")

@ -1,5 +1,4 @@
#include "platformtheme.h"
#include "x11integration.h"
#include <QApplication>
#include <QFont>
@ -17,8 +16,6 @@
#include <private/qwindow_p.h>
#include <private/qguiapplication_p.h>
#include <KWindowSystem>
extern void updateXdgIconSystemTheme();
void onDarkModeChanged()
@ -40,11 +37,6 @@ PlatformTheme::PlatformTheme()
{
// 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);

@ -16,8 +16,6 @@
class QIconEngine;
class QWindow;
class X11Integration;
class PlatformTheme : public QObject, public QGenericUnixTheme
{
Q_OBJECT
@ -47,7 +45,6 @@ private:
private:
HintsSettings *m_hints;
QScopedPointer<X11Integration> m_x11Integration;
};
#endif // PLATFORMTHEME_H

@ -1,136 +0,0 @@
/* 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 "x11utils.h"
#include <QCoreApplication>
#include <QPlatformSurfaceEvent>
#include <QGuiApplication>
#include <QtGui/qguiapplication_platform.h>
#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(qGuiApp->nativeInterface<QNativeInterface::QX11Application>()->connection(), w->winId(), Cutefish::X11::rootWindow(), 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(qGuiApp->nativeInterface<QNativeInterface::QX11Application>()->connection(), w->winId(), Cutefish::X11::rootWindow(), NET::Properties(), NET::Properties2());
info.setDesktopFileName(desktopFileName.toUtf8().constData());
}
void X11Integration::setWindowProperty(QWindow *window, const QByteArray &name, const QByteArray &value)
{
auto *c = qGuiApp->nativeInterface<QNativeInterface::QX11Application>()->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());
}
}

@ -1,47 +0,0 @@
/* 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

@ -16,23 +16,14 @@ if(NOT TARGET Qt6::GuiPrivate)
find_package(Qt6 REQUIRED COMPONENTS GuiPrivate)
endif()
find_package(PkgConfig REQUIRED)
find_package(KF6WindowSystem REQUIRED)
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})
@ -42,7 +33,6 @@ target_link_libraries(${TARGET}
Qt6::Gui
Qt6::Widgets
Qt6::DBus
KF6::WindowSystem
)
find_program(QT_PATHS_EXECUTABLE NAMES qtpaths6 REQUIRED)

@ -22,8 +22,6 @@
#include "basestyle.h"
#include "phantomcolor.h"
#include "shadowhelper.h"
#include "blurhelper.h"
#include <QAbstractItemView>
#include <QApplication>
@ -1405,13 +1403,9 @@ BaseStylePrivate::BaseStylePrivate()
}
BaseStyle::BaseStyle()
: d(new BaseStylePrivate),
m_shadowHelper(new ShadowHelper(this)),
m_blurHelper(new BlurHelper(this))
: d(new BaseStylePrivate)
{
setObjectName(QLatin1String("Phantom"));
m_shadowHelper->setFrameRadius(Phantom::DefaultFrame_Radius);
}
BaseStyle::~BaseStyle()
@ -1578,11 +1572,6 @@ void BaseStyle::drawPrimitive(PrimitiveElement elem,
break;
}
case PE_PanelTipLabel: {
// force registration of widget
if (widget && widget->window()) {
m_shadowHelper->registerWidget(widget->window(), true);
}
const auto &palette = option->palette;
const auto &background = palette.color(QPalette::ToolTipBase);
const qreal radius = Phantom::DefaultFrame_Radius;
@ -2168,16 +2157,6 @@ void BaseStyle::drawPrimitive(PrimitiveElement elem,
QRectF frameRect = strokedRect(option->rect, 1);
painter->drawRoundedRect(frameRect, Phantom::DefaultFrame_Radius, Phantom::DefaultFrame_Radius);
// blur
if (widget && widget->window()) {
// QPainterPath path;
// path.addRoundedRect(option->rect, Phantom::DefaultFrame_Radius, Phantom::DefaultFrame_Radius);
// const_cast<QWidget *>(widget)->setMask(path.toFillPolygon().toPolygon());
// m_blurHelper->update(const_cast<QWidget *>(widget));
m_blurHelper->enableBlurBehind(const_cast<QWidget *>(widget), true, Phantom::DefaultFrame_Radius);
}
painter->restore();
break;
}
@ -4483,14 +4462,12 @@ void BaseStyle::polish(QWidget *widget)
if (qobject_cast<QMenu *>(widget)) {
widget->setAttribute(Qt::WA_TranslucentBackground, true);
// m_blurHelper->registerWidget(widget);
}
if (widget->inherits("QTipLabel") || widget->inherits("QComboBoxPrivateContainer")) {
widget->setAttribute(Qt::WA_TranslucentBackground, true);
}
m_shadowHelper->registerWidget(widget);
}
void BaseStyle::unpolish(QWidget *widget)
@ -4512,14 +4489,12 @@ void BaseStyle::unpolish(QWidget *widget)
if (qobject_cast<QMenu *>(widget)) {
widget->setAttribute(Qt::WA_TranslucentBackground, false);
// m_blurHelper->unregisterWidget(widget);
}
if (widget->inherits("QTipLabel")) {
widget->setAttribute(Qt::WA_TranslucentBackground, false);
}
m_shadowHelper->unregisterWidget(widget);
}
bool BaseStyle::isDarkMode() const

@ -26,8 +26,6 @@
#include <QCommonStyle>
class BaseStylePrivate;
class ShadowHelper;
class BlurHelper;
class BaseStyle : public QCommonStyle
{
Q_OBJECT
@ -106,9 +104,6 @@ protected:
BaseStylePrivate* d;
private:
ShadowHelper *m_shadowHelper;
BlurHelper *m_blurHelper;
};
#endif

@ -1,170 +0,0 @@
//////////////////////////////////////////////////////////////////////////////
// breezeblurhelper.cpp
// handle regions passed to kwin for blurring
// -------------------
//
// Copyright (C) 2021 Reion Wong <reionwong@gmail.com>
// 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"
#include "x11utils.h"
// KDE Frameworks
#include <KWindowEffects>
#include <KWindowSystem>
// Qt
#include <QWidget>
#include <QWindow>
#include <QScreen>
#include <QRect>
#include <QVariant>
#include <QEvent>
#include <QPainterPath>
#include <QApplication>
#include <QtGui/qguiapplication_platform.h>
// XCB
#include <xcb/xcb.h>
#include <xcb/shape.h>
#include <xcb/xcb_icccm.h>
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
{
// KWindowEffects is an X11/EWMH integration. On Wayland there is no X11
// native interface to pass the blur region to, and calling it can crash
// while a QMenu is being painted.
if (!KWindowSystem::isPlatformX11())
return;
/*
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;
QWindow *window = widget->windowHandle();
if (!window)
return;
if (widget->mask().isEmpty()) {
KWindowEffects::enableBlurBehind(window, true);
} else {
KWindowEffects::enableBlurBehind(window, true, widget->mask());
}
// force update
if (widget->isVisible()) {
widget->update();
}
}
void BlurHelper::enableBlurBehind(QWidget *widget, bool enable, qreal windowRadius)
{
if (!widget || !KWindowSystem::isPlatformX11())
return;
const auto native = qGuiApp->nativeInterface<QNativeInterface::QX11Application>();
if (!native)
return;
xcb_connection_t *c = native->connection();
if (!c)
return;
const QByteArray effectName = QByteArrayLiteral("_KDE_NET_WM_BLUR_BEHIND_REGION");
xcb_intern_atom_cookie_t atomCookie = xcb_intern_atom_unchecked(c, false, effectName.length(), effectName.constData());
QScopedPointer<xcb_intern_atom_reply_t, QScopedPointerPodDeleter> atom(xcb_intern_atom_reply(c, atomCookie, nullptr));
if (!atom)
return;
if (enable) {
qreal devicePixelRatio = qApp->devicePixelRatio();
QPainterPath path;
path.addRoundedRect(QRectF(QPoint(0, 0), widget->size() * devicePixelRatio),
windowRadius * devicePixelRatio,
windowRadius * devicePixelRatio);
QVector<uint32_t> data;
for (const QPolygonF &polygon : path.toFillPolygons()) {
QRegion region = polygon.toPolygon();
for (auto i = region.begin(); i != region.end(); ++i) {
data << i->x() << i->y() << i->width() << i->height();
}
}
xcb_change_property(c, XCB_PROP_MODE_REPLACE, widget->winId(),
atom->atom, XCB_ATOM_CARDINAL,
32, data.size(), data.constData());
} else {
xcb_delete_property(c, widget->winId(), atom->atom);
}
}

@ -1,59 +0,0 @@
//////////////////////////////////////////////////////////////////////////////
// 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;
void enableBlurBehind(QWidget *widget, bool enable = true, qreal windowRadius = 0.0);
protected:
void addEventFilter(QObject *object) {
object->removeEventFilter(this);
object->installEventFilter(this);
}
};
#endif // BLURHELPER_H

@ -1,348 +0,0 @@
/*
* 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()));
}

@ -1,97 +0,0 @@
/*
* 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;
};

@ -1,414 +0,0 @@
/*************************************************************************
* 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[] = "_CUTEFISH_NET_WM_SKIP_SHADOW";
const char netWMForceShadow[] = "_CUTEFISH_NET_WM_FORCE_SHADOW";
const char netWMFrameRadius[] = "_CUTEFISH_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(12)
{
}
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 = 4;
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);
}

@ -1,148 +0,0 @@
/*************************************************************************
* 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

@ -1,183 +0,0 @@
/*************************************************************************
* 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 );
}

@ -1,121 +0,0 @@
#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

@ -1,21 +0,0 @@
#pragma once
#include <QGuiApplication>
#include <QtGui/qguiapplication_platform.h>
#include <xcb/xcb.h>
namespace Cutefish::X11
{
inline xcb_window_t rootWindow()
{
const auto native = qGuiApp ? qGuiApp->nativeInterface<QNativeInterface::QX11Application>() : nullptr;
const auto c = native ? native->connection() : nullptr;
if (!c)
return XCB_WINDOW_NONE;
const auto *setup = xcb_get_setup(c);
const auto iterator = xcb_setup_roots_iterator(setup);
return iterator.data ? iterator.data->root : XCB_WINDOW_NONE;
}
}
Loading…
Cancel
Save