[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.
This commit is contained in:
Martin Gräßlin 2013-10-09 07:26:12 +02:00
parent 612709d9d3
commit 1674824e79
5 changed files with 249 additions and 7 deletions

View file

@ -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();

View file

@ -33,6 +33,7 @@
#include "configdialog.h"
#include "decorationmodel.h"
#include "auroraetheme.h"
#include "preview.h"
// Qt
#include <QTimer>
#include <QtDBus/QtDBus>
@ -83,6 +84,7 @@ KWinDecorationModule::KWinDecorationModule(QWidget* parent, const QVariantList &
, m_listView(new QQuickView())
{
qmlRegisterType<Aurorae::AuroraeTheme>("org.kde.kwin.aurorae", 0, 1, "AuroraeTheme");
qmlRegisterType<PreviewItem>("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"));

View file

@ -1,6 +1,7 @@
/*
*
* Copyright (c) 2003 Lubos Lunak <l.lunak@kde.org>
* Copyright (c) 2013 Martin Gräßlin <mgraesslin@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
@ -25,6 +26,7 @@
#include <kglobal.h>
#include <QLabel>
#include <QStyle>
#include <QApplication>
#include <QPainter>
#include <QMouseEvent>
#include <QVector>
@ -32,6 +34,179 @@
#include <kdecorationfactory.h>
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 &region, 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)

View file

@ -1,6 +1,7 @@
/*
*
* Copyright (c) 2003 Lubos Lunak <l.lunak@kde.org>
* Copyright (c) 2013 Martin Gräßlin <mgraesslin@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
@ -21,6 +22,7 @@
#define KWINDECORATION_PREVIEW_H
#include <QWidget>
#include <QQuickPaintedItem>
#include <kdecoration.h>
#include <kdecorationbridge.h>
#include <kdecoration_plugins_p.h>
@ -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<KDecorationPlugins> m_plugins;
QScopedPointer<KDecorationPreviewBridge> m_activeBridge;
QScopedPointer<KDecorationPreviewBridge> 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;
};

View file

@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
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" : ""