From 1674824e7927904b2fbcd853b9d362b5e9c11738 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Gr=C3=A4=C3=9Flin?= Date: Wed, 9 Oct 2013 07:26:12 +0200 Subject: [PATCH] [kcmdeco] Introduce a new PreviewItem for rendering QWidget based decos in QML Using a QQuickPaintedItem for the rendering. The item gets the library name from the model and loads the decoration with its own decoration plugin. Thus each preview has its own plugin which eliminates the need to constantly recreate the decoration as it is done with the preview. Having a QQuickItem gives new possibilities. The item accepts hover events and forwards them as enter and leave events to the widgets inside the decoration. By that the mouse interaction of e.g. Oxygen is still functional. If the decoration uses the new update approach the bridge is forwarding the updates to the item and triggering a repaint so we even have animations in the preview although the widget is never shown. --- kcmkwin/kwindecoration/decorationmodel.cpp | 1 + kcmkwin/kwindecoration/kwindecoration.cpp | 2 + kcmkwin/kwindecoration/preview.cpp | 196 ++++++++++++++++++++- kcmkwin/kwindecoration/preview.h | 46 +++++ kcmkwin/kwindecoration/qml/main.qml | 11 +- 5 files changed, 249 insertions(+), 7 deletions(-) diff --git a/kcmkwin/kwindecoration/decorationmodel.cpp b/kcmkwin/kwindecoration/decorationmodel.cpp index 55f76b05be..1064668cec 100644 --- a/kcmkwin/kwindecoration/decorationmodel.cpp +++ b/kcmkwin/kwindecoration/decorationmodel.cpp @@ -70,6 +70,7 @@ DecorationModel::DecorationModel(KSharedConfigPtr config, QObject* parent) roleNames[QmlMainScriptRole] = "mainScript"; roleNames[BorderSizeRole] = "borderSize"; roleNames[ButtonSizeRole] = "buttonSize"; + roleNames[LibraryNameRole] = "library"; setRoleNames(roleNames); m_config = KSharedConfig::openConfig("auroraerc"); findDecorations(); diff --git a/kcmkwin/kwindecoration/kwindecoration.cpp b/kcmkwin/kwindecoration/kwindecoration.cpp index 4c289daf55..f26f740cef 100644 --- a/kcmkwin/kwindecoration/kwindecoration.cpp +++ b/kcmkwin/kwindecoration/kwindecoration.cpp @@ -33,6 +33,7 @@ #include "configdialog.h" #include "decorationmodel.h" #include "auroraetheme.h" +#include "preview.h" // Qt #include #include @@ -83,6 +84,7 @@ KWinDecorationModule::KWinDecorationModule(QWidget* parent, const QVariantList & , m_listView(new QQuickView()) { qmlRegisterType("org.kde.kwin.aurorae", 0, 1, "AuroraeTheme"); + qmlRegisterType("org.kde.kwin.kcmdecoration", 0, 1, "PreviewItem"); m_ui = new KWinDecorationForm(this); m_ui->configureDecorationButton->setIcon(QIcon::fromTheme("configure")); m_ui->configureButtonsButton->setIcon(QIcon::fromTheme("configure")); diff --git a/kcmkwin/kwindecoration/preview.cpp b/kcmkwin/kwindecoration/preview.cpp index 887982266e..ea4645d9d5 100644 --- a/kcmkwin/kwindecoration/preview.cpp +++ b/kcmkwin/kwindecoration/preview.cpp @@ -1,6 +1,7 @@ /* * * Copyright (c) 2003 Lubos Lunak + * Copyright (c) 2013 Martin Gräßlin * * 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 @@ -25,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -32,6 +34,179 @@ #include +static const int SMALL_OFFSET = 10; +static const int LARGE_OFFSET = 40; + +PreviewItem::PreviewItem(QQuickItem *parent) + : QQuickPaintedItem(parent) + , m_plugins(new KDecorationPreviewPlugins(KSharedConfig::openConfig(QStringLiteral("kwinrc")))) + , m_activeBridge(new KDecorationPreviewBridge(this, true)) + , m_inactiveBridge(new KDecorationPreviewBridge(this, false)) + , m_activeDecoration(nullptr) + , m_inactiveDecoration(nullptr) + , m_activeEntered(nullptr) + , m_inactiveEntered(nullptr) +{ + setFlag(ItemHasContents, true); + setAcceptHoverEvents(true); + connect(this, &PreviewItem::libraryChanged, this, &PreviewItem::recreateDecorations); +} + +PreviewItem::~PreviewItem() +{ + delete m_activeDecoration; + delete m_inactiveDecoration; +} + +void PreviewItem::setLibraryName(const QString &library) +{ + if (library == m_libraryName || library.isEmpty()) { + return; + } + m_libraryName = library; + emit libraryChanged(); +} + +void PreviewItem::recreateDecorations() +{ + const bool loaded = m_plugins->loadPlugin(m_libraryName); + if (!loaded) { + return; + } + m_plugins->destroyPreviousPlugin(); + delete m_activeDecoration; + delete m_inactiveDecoration; + m_activeDecoration = m_plugins->createDecoration(m_activeBridge.data()); + m_inactiveDecoration = m_plugins->createDecoration(m_inactiveBridge.data()); + + if (m_activeDecoration) { + m_activeDecoration->init(); + if (m_activeDecoration->widget()) { + m_activeDecoration->widget()->installEventFilter(this); + } + } + if (m_inactiveDecoration) { + m_inactiveDecoration->init(); + if (m_inactiveDecoration->widget()) { + m_inactiveDecoration->widget()->installEventFilter(this); + } + } + updatePreview(); +} + +void PreviewItem::geometryChanged(const QRectF& newGeometry, const QRectF& oldGeometry) +{ + QQuickPaintedItem::geometryChanged(newGeometry, oldGeometry); + updatePreview(); +} + +void PreviewItem::updatePreview() +{ + if (width() == 0 && height() == 0) { + return; + } + if (!m_activeDecoration && !m_inactiveDecoration) { + return; + } + const QSize size(width() - 50, height() - 50); + updateSize(size, m_activeDecoration, m_activeBuffer); + updateSize(size, m_inactiveDecoration, m_inactiveBuffer); + + render(&m_activeBuffer, m_activeDecoration); + render(&m_inactiveBuffer, m_inactiveDecoration); +} + +void PreviewItem::updateSize(const QSize &baseSize, KDecoration *decoration, QImage &buffer) +{ + if (!decoration) { + return; + } + int left, right, top, bottom; + left = right = top = bottom = 0; + decoration->padding(left, right, top, bottom); + const QSize size = baseSize + QSize(left + right, top + bottom); + if (decoration->geometry().size() != size) { + decoration->resize(size); + } + if (buffer.isNull() || buffer.size() != size) { + buffer = QImage(size, QImage::Format_ARGB32_Premultiplied); + } +} + +void PreviewItem::render(QImage* image, KDecoration* decoration) +{ + image->fill(Qt::transparent); + decoration->render(image, QRegion()); +} + +void PreviewItem::paint(QPainter *painter) +{ + int paddingLeft, paddingRigth, paddingTop, paddingBottom; + paddingLeft = paddingRigth = paddingTop = paddingBottom = 0; + if (m_inactiveDecoration) { + m_inactiveDecoration->padding(paddingLeft, paddingRigth, paddingTop, paddingBottom); + } + + painter->drawImage(LARGE_OFFSET - paddingLeft, SMALL_OFFSET - paddingTop, m_inactiveBuffer); + + paddingLeft = paddingRigth = paddingTop = paddingBottom = 0; + if (m_activeDecoration) { + m_activeDecoration->padding(paddingLeft, paddingRigth, paddingTop, paddingBottom); + } + painter->drawImage(SMALL_OFFSET - paddingLeft, LARGE_OFFSET - paddingTop, m_activeBuffer); +} + +void PreviewItem::hoverMoveEvent(QHoverEvent* event) +{ + QQuickItem::hoverMoveEvent(event); + const int w = width() - LARGE_OFFSET - SMALL_OFFSET; + const int h = height() - LARGE_OFFSET - SMALL_OFFSET; + forwardMoveEvent(event, QRect(SMALL_OFFSET, LARGE_OFFSET, w, h), m_activeDecoration, &m_activeEntered); + forwardMoveEvent(event, QRect(LARGE_OFFSET, SMALL_OFFSET, w, h), m_inactiveDecoration, &m_inactiveEntered); +} + +void PreviewItem::forwardMoveEvent(QHoverEvent *event, const QRect &geo, KDecoration *deco, QWidget **entered) +{ + auto leaveEvent = [](QWidget **widget) { + if (!(*widget)) { + return; + } + // send old one a leave event + QEvent leave(QEvent::Leave); + QApplication::sendEvent(*widget, &leave); + *widget = nullptr; + }; + if (geo.contains(event->pos()) && deco && deco->widget()) { + int paddingLeft, paddingRigth, paddingTop, paddingBottom; + paddingLeft = paddingRigth = paddingTop = paddingBottom = 0; + deco->padding(paddingLeft, paddingRigth, paddingTop, paddingBottom); + const QPoint widgetPos = event->pos() - geo.topLeft() + QPoint(paddingLeft, paddingTop); + if (QWidget *widget = deco->widget()->childAt(widgetPos)) { + if (widget != *entered) { + leaveEvent(entered); + // send enter event + *entered = widget; + QEnterEvent enter(widgetPos - widget->geometry().topLeft(), widgetPos, event->pos()); + QApplication::sendEvent(*entered, &enter); + } + } else { + leaveEvent(entered); + } + } else { + leaveEvent(entered); + } +} + +void PreviewItem::updateDecoration(KDecorationPreviewBridge *bridge) +{ + if (bridge == m_activeBridge.data()) { + render(&m_activeBuffer, m_activeDecoration); + } else if (bridge == m_inactiveBridge.data()) { + render(&m_inactiveBuffer, m_inactiveDecoration); + } + update(); +} + KDecorationPreview::KDecorationPreview(QWidget* parent) : QWidget(parent) { @@ -180,7 +355,14 @@ void KDecorationPreview::setMask(const QRegion ®ion, bool active) } KDecorationPreviewBridge::KDecorationPreviewBridge(KDecorationPreview* p, bool a) - : preview(p), active(a) + : preview(p), active(a), m_previewItem(nullptr) +{ +} + +KDecorationPreviewBridge::KDecorationPreviewBridge(PreviewItem *p, bool a) + : preview(nullptr) + , active(a) + , m_previewItem(p) { } @@ -311,8 +493,8 @@ void KDecorationPreviewBridge::performWindowOperation(WindowOperation) void KDecorationPreviewBridge::setMask(const QRegion& reg, int mode) { + Q_UNUSED(reg) Q_UNUSED(mode) - preview->setMask(reg, active); } bool KDecorationPreviewBridge::isPreview() const @@ -322,7 +504,10 @@ bool KDecorationPreviewBridge::isPreview() const QRect KDecorationPreviewBridge::geometry() const { - return preview->windowGeometry(active); + if (preview) { + return preview->windowGeometry(active); + } + return QRect(); } QRect KDecorationPreviewBridge::iconGeometry() const @@ -456,7 +641,10 @@ void KDecorationPreviewBridge::showWindowMenu(const QPoint &, long) void KDecorationPreviewBridge::update(const QRegion&) { - + if (m_previewItem) { + // call update + m_previewItem->updateDecoration(this); + } } KDecoration::WindowOperation KDecorationPreviewBridge::buttonToWindowOperation(Qt::MouseButtons) diff --git a/kcmkwin/kwindecoration/preview.h b/kcmkwin/kwindecoration/preview.h index d38895864f..74e9c4e01f 100644 --- a/kcmkwin/kwindecoration/preview.h +++ b/kcmkwin/kwindecoration/preview.h @@ -1,6 +1,7 @@ /* * * Copyright (c) 2003 Lubos Lunak + * Copyright (c) 2013 Martin Gräßlin * * 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 @@ -21,6 +22,7 @@ #define KWINDECORATION_PREVIEW_H #include +#include #include #include #include @@ -29,6 +31,48 @@ class KDecorationPreviewBridge; class KDecorationPreviewOptions; class QMouseEvent; +class PreviewItem : public QQuickPaintedItem +{ + Q_OBJECT + Q_PROPERTY(QString library READ libraryName WRITE setLibraryName NOTIFY libraryChanged) +public: + PreviewItem(QQuickItem *parent = nullptr); + virtual ~PreviewItem(); + virtual void paint(QPainter *painter); + + void setLibraryName(const QString &library); + const QString &libraryName() const { + return m_libraryName; + } + void updateDecoration(KDecorationPreviewBridge *bridge); + +Q_SIGNALS: + void libraryChanged(); + +protected: + virtual void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry); + virtual void hoverMoveEvent(QHoverEvent *event); + +private Q_SLOTS: + void recreateDecorations(); + +private: + void updatePreview(); + void updateSize(const QSize &size, KDecoration *decoration, QImage &buffer); + void render(QImage *image, KDecoration *decoration); + void forwardMoveEvent(QHoverEvent *event, const QRect &geo, KDecoration *deco, QWidget **entered); + QScopedPointer m_plugins; + QScopedPointer m_activeBridge; + QScopedPointer m_inactiveBridge; + KDecoration *m_activeDecoration; + KDecoration *m_inactiveDecoration; + QWidget *m_activeEntered; + QWidget *m_inactiveEntered; + QString m_libraryName; + QImage m_activeBuffer; + QImage m_inactiveBuffer; +}; + class KDecorationPreview : public QWidget { @@ -64,6 +108,7 @@ class KDecorationPreviewBridge { public: KDecorationPreviewBridge(KDecorationPreview* preview, bool active); + KDecorationPreviewBridge(PreviewItem* preview, bool active); virtual bool isActive() const override; virtual bool isCloseable() const override; virtual bool isMaximizable() const override; @@ -131,6 +176,7 @@ public: private: KDecorationPreview* preview; + PreviewItem *m_previewItem; bool active; }; diff --git a/kcmkwin/kwindecoration/qml/main.qml b/kcmkwin/kwindecoration/qml/main.qml index 95c281ac1d..d209bdd79a 100644 --- a/kcmkwin/kwindecoration/qml/main.qml +++ b/kcmkwin/kwindecoration/qml/main.qml @@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ import QtQuick 2.0 -import org.kde.qtextracomponents 2.0 as QtExtra +import org.kde.kwin.kcmdecoration 0.1 ListView { id: listView @@ -35,10 +35,15 @@ ListView { objectName: "decorationItem" width: listView.width height: 150 - QtExtra.QPixmapItem { - pixmap: preview + PreviewItem { + id: preview anchors.fill: parent visible: type == 0 + Component.onCompleted: { + if (type == 0) { + preview.library = model.library; + } + } } Loader { source: type == 1 ? "AuroraePreview.qml" : ""