aurorae.cpp 19.2 KB
Newer Older
1
/********************************************************************
2
Copyright (C) 2009, 2010, 2012 Martin Gräßlin <mgraesslin@kde.org>
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

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, see <http://www.gnu.org/licenses/>.
*********************************************************************/

#include "aurorae.h"
19
#include "auroraetheme.h"
20
#include "config-kwin.h"
21

22
#include <QApplication>
23
#include <QtDeclarative/QDeclarativeComponent>
24
#include <QtDeclarative/QDeclarativeContext>
Martin Flöser's avatar
Martin Flöser committed
25
#include <QtDeclarative/QDeclarativeEngine>
26
27
#include <QtDeclarative/QDeclarativeItem>
#include <QtGui/QGraphicsView>
28
29
30

#include <KConfig>
#include <KConfigGroup>
31
32
33
#include <KDebug>
#include <KPluginInfo>
#include <KServiceTypeTrader>
Martin Flöser's avatar
Martin Flöser committed
34
#include <KStandardDirs>
35
#include <Plasma/FrameSvg>
36
37
38
39
40
41
42

namespace Aurorae
{

AuroraeFactory::AuroraeFactory()
        : QObject()
        , KDecorationFactoryUnstable()
43
        , m_theme(new AuroraeTheme(this))
44
45
        , m_engine(new QDeclarativeEngine(this))
        , m_component(new QDeclarativeComponent(m_engine, this))
46
        , m_engineType(AuroraeEngine)
47
48
49
50
51
52
{
    init();
}

void AuroraeFactory::init()
{
53
54
    qRegisterMetaType<uint>("Qt::MouseButtons");

55
56
    KConfig conf("auroraerc");
    KConfigGroup group(&conf, "Engine");
57
58
59
60
    if (!group.hasKey("EngineType") && !group.hasKey("ThemeName")) {
        // neither engine type and theme name are configured, use the only available theme
        initQML(group);
    } else if (group.hasKey("EngineType")) {
61
62
63
64
65
66
67
68
69
70
71
72
        const QString engineType = group.readEntry("EngineType", "aurorae").toLower();
        if (engineType == "qml") {
            initQML(group);
        } else {
            // fallback to classic Aurorae Themes
            initAurorae(conf, group);
        }
    } else {
        // fallback to classic Aurorae Themes
        initAurorae(conf, group);
    }
}
73

74
75
76
void AuroraeFactory::initAurorae(KConfig &conf, KConfigGroup &group)
{
    m_engineType = AuroraeEngine;
77
78
79
80
81
82
    const QString themeName = group.readEntry("ThemeName");
    if (themeName.isEmpty()) {
        // no theme configured, fall back to Plastik QML theme
        initQML(group);
        return;
    }
83
    KConfig config("aurorae/themes/" + themeName + '/' + themeName + "rc", KConfig::FullConfig, "data");
84
    KConfigGroup themeGroup(&conf, themeName);
85
    m_theme->loadTheme(themeName, config);
86
87
    m_theme->setBorderSize((KDecorationDefines::BorderSize)themeGroup.readEntry<int>("BorderSize", KDecorationDefines::BorderNormal));
    m_theme->setButtonSize((KDecorationDefines::BorderSize)themeGroup.readEntry<int>("ButtonSize", KDecorationDefines::BorderNormal));
Thomas Lübking's avatar
Thomas Lübking committed
88
    m_theme->setTabDragMimeType(tabDragMimeType());
89
90
91
92
93
94
    // setup the QML engine
    foreach (const QString &importPath, KGlobal::dirs()->findDirs("module", "imports")) {
        m_engine->addImportPath(importPath);
    }
    m_component->loadUrl(QUrl(KStandardDirs::locate("data", "kwin/aurorae/aurorae.qml")));
    m_engine->rootContext()->setContextProperty("auroraeTheme", m_theme);
95
    m_themeName = themeName;
96
97
}

98
99
100
void AuroraeFactory::initQML(const KConfigGroup &group)
{
    // try finding the QML package
101
    const QString themeName = group.readEntry("ThemeName", "kwin4_decoration_qml_plastik");
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
    kDebug(1212) << "Trying to load QML Decoration " << themeName;
    const QString internalname = themeName.toLower();

    QString constraint = QString("[X-KDE-PluginInfo-Name] == '%1'").arg(internalname);
    KService::List offers = KServiceTypeTrader::self()->query("KWin/Decoration", constraint);
    if (offers.isEmpty()) {
        kError(1212) << "Couldn't find QML Decoration " << themeName << endl;
        // TODO: what to do in error case?
        return;
    }
    KService::Ptr service = offers.first();
    KPluginInfo plugininfo(service);
    const QString pluginName = service->property("X-KDE-PluginInfo-Name").toString();
    const QString scriptName = service->property("X-Plasma-MainScript").toString();
    const QString file = KStandardDirs::locate("data", QLatin1String(KWIN_NAME) + "/decorations/" + pluginName + "/contents/" + scriptName);
    if (file.isNull()) {
        kDebug(1212) << "Could not find script file for " << pluginName;
        // TODO: what to do in error case?
        return;
    }
    m_engineType = QMLEngine;
    // setup the QML engine
    foreach (const QString &importPath, KGlobal::dirs()->findDirs("module", "imports")) {
        m_engine->addImportPath(importPath);
    }
    m_component->loadUrl(QUrl::fromLocalFile(file));
128
    m_themeName = themeName;
129
130
}

131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
AuroraeFactory::~AuroraeFactory()
{
    s_instance = NULL;
}

AuroraeFactory *AuroraeFactory::instance()
{
    if (!s_instance) {
        s_instance = new AuroraeFactory;
    }

    return s_instance;
}

bool AuroraeFactory::reset(unsigned long changed)
{
147
148
149
    if (changed & SettingButtons) {
        emit buttonsChanged();
    }
150
151
152
    if (changed & SettingFont){
        emit titleFontChanged();
    }
153
154
155
    if (changed & SettingCompositing) {
        return false;
    }
156
157
158
159
160
161
    const KConfig conf("auroraerc");
    const KConfigGroup group(&conf, "Engine");
    const QString themeName = group.readEntry("ThemeName", "example-deco");
    const KConfig config("aurorae/themes/" + themeName + '/' + themeName + "rc", KConfig::FullConfig, "data");
    const KConfigGroup themeGroup(&conf, themeName);
    if (themeName != m_theme->themeName()) {
162
163
164
165
166
167
168
169
170
171
172
        delete m_engine;
        m_engine = new QDeclarativeEngine(this);
        delete m_component;
        m_component = new QDeclarativeComponent(m_engine, this);
        init();
        // recreate all decorations
        return true;
    }
    if (m_engineType == AuroraeEngine) {
        m_theme->setBorderSize((KDecorationDefines::BorderSize)themeGroup.readEntry<int>("BorderSize", KDecorationDefines::BorderNormal));
        m_theme->setButtonSize((KDecorationDefines::BorderSize)themeGroup.readEntry<int>("ButtonSize", KDecorationDefines::BorderNormal));
173
    }
174
    emit configChanged();
175
176
177
178
179
180
181
182
    return false; // need hard reset
}

bool AuroraeFactory::supports(Ability ability) const
{
    switch (ability) {
    case AbilityAnnounceButtons:
    case AbilityUsesAlphaChannel:
183
    case AbilityAnnounceAlphaChannel:
184
185
    case AbilityButtonMenu:
    case AbilityButtonSpacer:
186
    case AbilityExtendIntoClientArea:
187
188
189
190
191
192
193
194
195
    case AbilityButtonMinimize:
    case AbilityButtonMaximize:
    case AbilityButtonClose:
    case AbilityButtonAboveOthers:
    case AbilityButtonBelowOthers:
    case AbilityButtonShade:
    case AbilityButtonOnAllDesktops:
    case AbilityButtonHelp:
    case AbilityProvidesShadow:
196
        return true; // TODO: correct value from theme
Thomas Lübking's avatar
Thomas Lübking committed
197
    case AbilityTabbing:
198
        return false;
199
200
    case AbilityUsesBlurBehind:
        return true;
201
202
203
204
205
206
207
208
    default:
        return false;
    }
}

KDecoration *AuroraeFactory::createDecoration(KDecorationBridge *bridge)
{
    AuroraeClient *client = new AuroraeClient(bridge, this);
209
    return client;
210
211
}

212
213
214
215
216
217
218
QList< KDecorationDefines::BorderSize > AuroraeFactory::borderSizes() const
{
    return QList< BorderSize >() << BorderTiny << BorderNormal <<
        BorderLarge << BorderVeryLarge <<  BorderHuge <<
        BorderVeryHuge << BorderOversized;
}

219
220
221
222
223
224
QDeclarativeItem *AuroraeFactory::createQmlDecoration(Aurorae::AuroraeClient *client)
{
    QDeclarativeContext *context = new QDeclarativeContext(m_engine->rootContext(), this);
    context->setContextProperty("decoration", client);
    return qobject_cast< QDeclarativeItem* >(m_component->create(context));
}
225
226
227
228

AuroraeFactory *AuroraeFactory::s_instance = NULL;

/*******************************************************
229
* Client
230
*******************************************************/
231
232
AuroraeClient::AuroraeClient(KDecorationBridge *bridge, KDecorationFactory *factory)
    : KDecorationUnstable(bridge, factory)
233
234
235
    , m_view(NULL)
    , m_scene(new QGraphicsScene(this))
    , m_item(AuroraeFactory::instance()->createQmlDecoration(this))
Martin Flöser's avatar
Martin Flöser committed
236
237
238
{
    connect(this, SIGNAL(keepAboveChanged(bool)), SIGNAL(keepAboveChangedWrapper()));
    connect(this, SIGNAL(keepBelowChanged(bool)), SIGNAL(keepBelowChangedWrapper()));
239
    connect(AuroraeFactory::instance(), SIGNAL(buttonsChanged()), SIGNAL(buttonsChanged()));
240
    connect(AuroraeFactory::instance(), SIGNAL(configChanged()), SIGNAL(configChanged()));
241
    connect(AuroraeFactory::instance(), SIGNAL(titleFontChanged()), SIGNAL(fontChanged()));
242
    connect(m_item, SIGNAL(alphaChanged()), SLOT(slotAlphaChanged()));
243
244
}

245
246
AuroraeClient::~AuroraeClient()
{
247
248
249
250
    m_item->setParent(NULL);
    m_item->deleteLater();
    m_scene->setParent(NULL);
    m_scene->deleteLater();
251
252
253
254
    m_view->setParent(NULL);
    m_view->deleteLater();
}

255
void AuroraeClient::init()
256
{
257
    m_scene->setItemIndexMethod(QGraphicsScene::NoIndex);
258
259
260
261
262
263
264
265
    // HACK: we need to add the GraphicsView as a child widget to a normal widget
    // the GraphicsView eats the mouse release event and by that kwin core starts to move
    // the decoration each time the decoration is clicked
    // therefore we use two widgets and inject an own mouse release event to the parent widget
    // when the graphics view eats a mouse event
    createMainWidget();
    widget()->setAttribute(Qt::WA_TranslucentBackground);
    widget()->setAttribute(Qt::WA_NoSystemBackground);
266
    widget()->installEventFilter(this);
267
    m_view = new QGraphicsView(m_scene, widget());
268
    m_view->setAttribute(Qt::WA_TranslucentBackground);
269
    m_view->setWindowFlags(Qt::X11BypassWindowManagerHint);
270
271
272
273
274
    m_view->setFrameShape(QFrame::NoFrame);
    m_view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    m_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    m_view->setOptimizationFlags(QGraphicsView::DontSavePainterState);
    m_view->setViewportUpdateMode(QGraphicsView::BoundingRectViewportUpdate);
275
276
277
278
279
280
    QPalette pal = m_view->palette();
    pal.setColor(m_view->backgroundRole(), Qt::transparent);
    m_view->setPalette(pal);
    QPalette pal2 = widget()->palette();
    pal2.setColor(widget()->backgroundRole(), Qt::transparent);
    widget()->setPalette(pal2);
281
    m_scene->addItem(m_item);
282
    slotAlphaChanged();
Martin Flöser's avatar
Martin Flöser committed
283

284
    AuroraeFactory::instance()->theme()->setCompositingActive(compositingActive());
285
286
}

287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
bool AuroraeClient::eventFilter(QObject *object, QEvent *event)
{
    // we need to filter the wheel events on the decoration
    // QML does not yet provide a way to accept wheel events, this will change with Qt 5
    // TODO: remove in KDE5
    // see BUG: 304248
    if (object != widget() || event->type() != QEvent::Wheel) {
        return KDecorationUnstable::eventFilter(object, event);
    }
    QWheelEvent *wheel = static_cast<QWheelEvent*>(event);
    if (mousePosition(wheel->pos()) == PositionCenter) {
        titlebarMouseWheelOperation(wheel->delta());
        return true;
    }
    return false;
}

304
void AuroraeClient::activeChange()
305
{
306
    emit activeChanged();
307
308
}

309
void AuroraeClient::captionChange()
310
{
311
    emit captionChanged();
312
313
}

314
void AuroraeClient::iconChange()
315
{
316
    emit iconChanged();
317
318
}

319
void AuroraeClient::desktopChange()
320
{
321
    emit desktopChanged();
322
323
}

324
void AuroraeClient::maximizeChange()
325
{
326
    if (!options()->moveResizeMaximizedWindows()) {
Martin Flöser's avatar
Martin Flöser committed
327
        emit maximizeChanged();
328
329
330
    }
}

331
void AuroraeClient::resize(const QSize &s)
332
{
333
334
335
336
337
    if (m_item) {
        m_item->setWidth(s.width());
        m_item->setHeight(s.height());
    }
    m_scene->setSceneRect(QRectF(QPoint(0, 0), s));
338
339
    m_view->resize(s);
    widget()->resize(s);
340
341
}

342
void AuroraeClient::shadeChange()
343
{
344
    emit shadeChanged();
345
346
}

347
void AuroraeClient::borders(int &left, int &right, int &top, int &bottom) const
348
{
349
    if (!m_item) {
350
351
352
        left = right = top = bottom = 0;
        return;
    }
353
    const bool maximized = maximizeMode() == MaximizeFull && !options()->moveResizeMaximizedWindows();
Martin Flöser's avatar
Martin Flöser committed
354
    if (maximized) {
355
356
357
358
        left = m_item->property("borderLeftMaximized").toInt();
        right = m_item->property("borderRightMaximized").toInt();
        top = m_item->property("borderTopMaximized").toInt();
        bottom = m_item->property("borderBottomMaximized").toInt();
Martin Flöser's avatar
Martin Flöser committed
359
    } else {
360
361
362
363
        left = m_item->property("borderLeft").toInt();
        right = m_item->property("borderRight").toInt();
        top = m_item->property("borderTop").toInt();
        bottom = m_item->property("borderBottom").toInt();
Martin Flöser's avatar
Martin Flöser committed
364
    }
365
366
}

367
void AuroraeClient::padding(int &left, int &right, int &top, int &bottom) const
368
{
369
    if (!m_item) {
370
371
372
        left = right = top = bottom = 0;
        return;
    }
373
374
    if (maximizeMode() == MaximizeFull && !options()->moveResizeMaximizedWindows()) {
        left = right = top = bottom = 0;
375
376
        return;
    }
377
378
379
380
    left = m_item->property("paddingLeft").toInt();
    right = m_item->property("paddingRight").toInt();
    top = m_item->property("paddingTop").toInt();
    bottom = m_item->property("paddingBottom").toInt();
381
382
}

383
QSize AuroraeClient::minimumSize() const
384
{
385
    return widget()->minimumSize();
386
387
}

388
KDecorationDefines::Position AuroraeClient::mousePosition(const QPoint &point) const
389
{
390
391
392
393
    // based on the code from deKorator
    int pos = PositionCenter;
    if (isShade()) {
        return Position(pos);
394
    }
395

396
397
398
399
    int borderLeft, borderTop, borderRight, borderBottom;
    borders(borderLeft, borderRight, borderTop, borderBottom);
    int paddingLeft, paddingTop, paddingRight, paddingBottom;
    padding(paddingLeft, paddingRight, paddingTop, paddingBottom);
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
    const bool maximized = maximizeMode() == MaximizeFull && !options()->moveResizeMaximizedWindows();
    int titleEdgeLeft, titleEdgeRight, titleEdgeTop, titleEdgeBottom;
    AuroraeFactory::instance()->theme()->titleEdges(titleEdgeLeft, titleEdgeTop, titleEdgeRight, titleEdgeBottom, maximized);
    switch (AuroraeFactory::instance()->theme()->decorationPosition()) {
    case DecorationTop:
        borderTop = titleEdgeTop;
        break;
    case DecorationLeft:
        borderLeft = titleEdgeLeft;
        break;
    case DecorationRight:
        borderRight = titleEdgeRight;
        break;
    case DecorationBottom:
        borderBottom = titleEdgeBottom;
        break;
    default:
        break; // nothing
    }
419
    if (point.x() >= (m_view->width() -  borderRight - paddingRight)) {
420
421
422
423
        pos |= PositionRight;
    } else if (point.x() <= borderLeft + paddingLeft) {
        pos |= PositionLeft;
    }
424

425
    if (point.y() >= m_view->height() - borderBottom - paddingBottom) {
426
        pos |= PositionBottom;
427
    } else if (point.y() <= borderTop + paddingTop ) {
428
429
        pos |= PositionTop;
    }
430

431
    return Position(pos);
432
433
}

434
void AuroraeClient::reset(long unsigned int changed)
435
{
436
    KDecoration::reset(changed);
437
438
}

439
void AuroraeClient::menuClicked()
440
{
441
    showWindowMenu(QCursor::pos());
442
443
}

444
void AuroraeClient::toggleShade()
445
{
446
    setShade(!isShade());
447
448
}

449
void AuroraeClient::toggleKeepAbove()
450
{
451
    setKeepAbove(!keepAbove());
452
453
}

454
void AuroraeClient::toggleKeepBelow()
455
{
456
    setKeepBelow(!keepBelow());
457
458
}

459
460
461
462
463
bool AuroraeClient::isMaximized() const
{
    return maximizeMode()==KDecorationDefines::MaximizeFull && !options()->moveResizeMaximizedWindows();
}

464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
void AuroraeClient::titlePressed(int button, int buttons)
{
    titlePressed(static_cast<Qt::MouseButton>(button), static_cast<Qt::MouseButtons>(buttons));
}

void AuroraeClient::titleReleased(int button, int buttons)
{
    titleReleased(static_cast<Qt::MouseButton>(button), static_cast<Qt::MouseButtons>(buttons));
}

void AuroraeClient::titleMouseMoved(int button, int buttons)
{
    titleMouseMoved(static_cast<Qt::MouseButton>(button), static_cast<Qt::MouseButtons>(buttons));
}

479
void AuroraeClient::titlePressed(Qt::MouseButton button, Qt::MouseButtons buttons)
480
{
481
482
483
484
485
    QMouseEvent *event = new QMouseEvent(QEvent::MouseButtonPress, widget()->mapFromGlobal(QCursor::pos()),
                                         QCursor::pos(), button, buttons, Qt::NoModifier);
    processMousePressEvent(event);
    delete event;
    event = 0;
486
487
}

488
void AuroraeClient::titleReleased(Qt::MouseButton button, Qt::MouseButtons buttons)
489
{
490
491
492
493
494
    QMouseEvent *event = new QMouseEvent(QEvent::MouseButtonRelease, widget()->mapFromGlobal(QCursor::pos()),
                                         QCursor::pos(), button, buttons, Qt::NoModifier);
    QApplication::sendEvent(widget(), event);
    delete event;
    event = 0;
495
496
}

497
void AuroraeClient::titleMouseMoved(Qt::MouseButton button, Qt::MouseButtons buttons)
498
{
499
500
501
502
503
    QMouseEvent *event = new QMouseEvent(QEvent::MouseMove, widget()->mapFromGlobal(QCursor::pos()),
                                         QCursor::pos(), button, buttons, Qt::NoModifier);
    QApplication::sendEvent(widget(), event);
    delete event;
    event = 0;
504
505
}

506
507
508
509
510
void AuroraeClient::themeChanged()
{
    m_scene->clear();
    m_item = AuroraeFactory::instance()->createQmlDecoration(this);
    if (m_item) {
511
512
        m_item->setWidth(m_scene->sceneRect().width());
        m_item->setHeight(m_scene->sceneRect().height());
513
514
    }
    m_scene->addItem(m_item);
515
516
    connect(m_item, SIGNAL(alphaChanged()), SLOT(slotAlphaChanged()));
    slotAlphaChanged();
517
518
}

519
520
521
522
523
int AuroraeClient::doubleClickInterval() const
{
    return QApplication::doubleClickInterval();
}

524
525
526
527
528
529
530
531
532
533
void AuroraeClient::closeWindow()
{
    QMetaObject::invokeMethod(qobject_cast< KDecorationUnstable* >(this), "doCloseWindow", Qt::QueuedConnection);
}

void AuroraeClient::doCloseWindow()
{
    KDecorationUnstable::closeWindow();
}

534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
void AuroraeClient::maximize(int button)
{
    // a maximized window does not need to have a window decoration
    // in that case we need to delay handling by one cycle
    // BUG: 304870
    QMetaObject::invokeMethod(qobject_cast< KDecorationUnstable* >(this),
                              "doMaximzie",
                              Qt::QueuedConnection,
                              Q_ARG(int, button));
}

void AuroraeClient::doMaximzie(int button)
{
    KDecorationUnstable::maximize(static_cast<Qt::MouseButton>(button));
}

void AuroraeClient::titlebarDblClickOperation()
{
    // the double click operation can result in a window being maximized
    // see maximize
    QMetaObject::invokeMethod(qobject_cast< KDecorationUnstable* >(this), "doTitlebarDblClickOperation", Qt::QueuedConnection);
}

void AuroraeClient::doTitlebarDblClickOperation()
{
    KDecorationUnstable::titlebarDblClickOperation();
}

562
563
564
565
566
567
QVariant AuroraeClient::readConfig(const QString &key, const QVariant &defaultValue)
{
    KSharedConfigPtr config = KSharedConfig::openConfig("auroraerc");
    return config->group(AuroraeFactory::instance()->currentThemeName()).readEntry(key, defaultValue);
}

568
569
570
571
572
573
574
575
576
577
578
void AuroraeClient::slotAlphaChanged()
{
    QVariant alphaProperty = m_item->property("alpha");
    if (alphaProperty.isValid() && alphaProperty.canConvert<bool>()) {
        setAlphaEnabled(alphaProperty.toBool());
    } else {
        // by default all Aurorae themes use the alpha channel
        setAlphaEnabled(true);
    }
}

579
580
581
582
583
584
585
} // namespace Aurorae

extern "C"
{
    KDE_EXPORT KDecorationFactory *create_factory() {
        return Aurorae::AuroraeFactory::instance();
    }
586
587
588
    KWIN_EXPORT int decoration_version() {
        return KWIN_DECORATION_API_VERSION;
    }
589
590
591
592
}


#include "aurorae.moc"