Plasma-framework

plasmoiditem.cpp
1/*
2 SPDX-FileCopyrightText: 2008-2013 Aaron Seigo <aseigo@kde.org>
3 SPDX-FileCopyrightText: 2010-2013 Marco Martin <mart@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "plasmoiditem.h"
9#include "appletcontext_p.h"
10#include "sharedqmlengine.h"
11
12#include <QAction>
13#include <QActionGroup>
14#include <QDir>
15#include <QFile>
16#include <QIcon>
17#include <QTimer>
18
19#include <KConfigLoader>
20#include <KLocalizedString>
21#include <QDebug>
22
23#include <Plasma/ContainmentActions>
24#include <Plasma/Corona>
25#include <Plasma/Plasma>
26#include <Plasma/PluginLoader>
27
28#include "containmentitem.h"
29#include "wallpaperitem.h"
30
31#include <KConfigPropertyMap>
32
33PlasmoidItem::PlasmoidItem(QQuickItem *parent)
34 : AppletQuickItem(parent)
35 , m_toolTipTextFormat(0)
36 , m_toolTipItem(nullptr)
37 , m_hideOnDeactivate(true)
38 , m_oldKeyboardShortcut(0)
39 , m_positionBeforeRemoval(QPointF(-1, -1))
40{
41 qmlRegisterAnonymousType<QAction>("org.kde.plasma.plasmoid", 1);
42}
43
44PlasmoidItem::~PlasmoidItem()
45{
46}
47
48void PlasmoidItem::init()
49{
50 AppletQuickItem::init();
51
52 auto *applet = PlasmoidItem::applet();
53 if (!applet) {
54 // This can happen only if the client QML code declares a PlasmoidItem somewhere else than the root object
55 return;
56 }
57
59
60 connect(applet, &Plasma::Applet::titleChanged, this, [this]() {
61 if (m_toolTipMainText.isNull()) {
62 Q_EMIT toolTipMainTextChanged();
63 }
64 });
65
66 if (applet->containment()) {
67 connect(applet->containment(), &Plasma::Containment::screenChanged, this, &PlasmoidItem::screenChanged);
68
69 // Screen change implies geo change for good measure.
70 connect(applet->containment(), &Plasma::Containment::screenChanged, this, &PlasmoidItem::screenGeometryChanged);
71
72 connect(applet->containment()->corona(), &Plasma::Corona::screenGeometryChanged, this, [this](int id) {
73 if (id == AppletQuickItem::applet()->containment()->screen()) {
74 Q_EMIT screenGeometryChanged();
75 }
76 });
77
78 connect(applet->containment()->corona(), &Plasma::Corona::availableScreenRegionChanged, this, &ContainmentItem::availableScreenRegionChanged);
79 connect(applet->containment()->corona(), &Plasma::Corona::availableScreenRectChanged, this, &ContainmentItem::availableScreenRectChanged);
80 }
81
82 connect(this, &PlasmoidItem::expandedChanged, [=, this](bool expanded) {
83 // if both compactRepresentationItem and fullRepresentationItem exist,
84 // the applet is in a popup
85 if (expanded) {
86 /* clang-format off */
87 if (compactRepresentationItem()
88 && fullRepresentationItem()
89 && fullRepresentationItem()->window()
90 && compactRepresentationItem()->window()
91 && fullRepresentationItem()->window() != compactRepresentationItem()->window()
92 && fullRepresentationItem()->parentItem()) {
93 /* clang-format on */
94 fullRepresentationItem()->parentItem()->installEventFilter(this);
95 } else if (fullRepresentationItem() && fullRepresentationItem()->parentItem()) {
96 fullRepresentationItem()->parentItem()->removeEventFilter(this);
97 }
98 }
99 });
100
101 geometryChange(QRectF(), QRectF(x(), y(), width(), height()));
102
103 connect(applet, &Plasma::Applet::activated, this, [=, this]() {
104 // in case the applet doesn't want to get shrunk on reactivation,
105 // we always expand it again (only in order to conform with legacy behaviour)
106 bool activate = !(isExpanded() && isActivationTogglesExpanded());
107
108 setExpanded(activate);
109 });
110
111 connect(applet, &Plasma::Applet::destroyedChanged, this, &PlasmoidItem::destroyedChanged);
112
113 auto args = applet->startupArguments();
114
115 if (args.count() == 1) {
116 Q_EMIT externalData(QString(), args.first());
117 } else if (!args.isEmpty()) {
118 Q_EMIT externalData(QString(), args);
119 }
120}
121
122void PlasmoidItem::destroyedChanged(bool destroyed)
123{
124 // if an item loses its scene before losing the focus, will never
125 // be able to gain focus again
126 if (destroyed && window() && window()->activeFocusItem()) {
128 QQuickItem *candidate = focus;
129 bool isAncestor = false;
130
131 // search if the current focus item is a child or grandchild of the applet
132 while (candidate) {
133 if (candidate == this) {
134 isAncestor = true;
135 break;
136 }
137 candidate = candidate->parentItem();
138 }
139
140 if (isAncestor) {
141 // Found? remove focus for the whole hierarchy
142 candidate = focus;
143
144 while (candidate && candidate != this) {
145 candidate->setFocus(false);
146 candidate = candidate->parentItem();
147 }
148 }
149 }
150
152}
153
155{
156 if (m_toolTipMainText.isNull()) {
157 return applet()->title();
158 } else {
159 return m_toolTipMainText;
160 }
161}
162
163void PlasmoidItem::setToolTipMainText(const QString &text)
164{
165 // Here we are abusing the difference between a null and an empty string.
166 // by default is null so falls back to the name
167 // the fist time it gets set, an empty non null one is set, and won't fallback anymore
168 if (!m_toolTipMainText.isNull() && m_toolTipMainText == text) {
169 return;
170 }
171
172 if (text.isEmpty()) {
173 m_toolTipMainText = QStringLiteral(""); // this "" makes it non-null
174 } else {
175 m_toolTipMainText = text;
176 }
177
178 Q_EMIT toolTipMainTextChanged();
179}
180
182{
183 if (m_toolTipSubText.isNull() && applet()->pluginMetaData().isValid()) {
184 return applet()->pluginMetaData().description();
185 } else {
186 return m_toolTipSubText;
187 }
188}
189
190void PlasmoidItem::setToolTipSubText(const QString &text)
191{
192 // Also there the difference between null and empty gets exploited
193 if (!m_toolTipSubText.isNull() && m_toolTipSubText == text) {
194 return;
195 }
196
197 if (text.isEmpty()) {
198 m_toolTipSubText = QStringLiteral(""); // this "" makes it non-null
199 } else {
200 m_toolTipSubText = text;
201 }
202
203 Q_EMIT toolTipSubTextChanged();
204}
205
207{
208 return m_toolTipTextFormat;
209}
210
211void PlasmoidItem::setToolTipTextFormat(int format)
212{
213 if (m_toolTipTextFormat == format) {
214 return;
215 }
216
217 m_toolTipTextFormat = format;
218 Q_EMIT toolTipTextFormatChanged();
219}
220
222{
223 return m_toolTipItem.data();
224}
225
226void PlasmoidItem::setToolTipItem(QQuickItem *toolTipItem)
227{
228 if (m_toolTipItem.data() == toolTipItem) {
229 return;
230 }
231
232 m_toolTipItem = toolTipItem;
233 connect(m_toolTipItem.data(), &QObject::destroyed, this, &PlasmoidItem::toolTipItemChanged);
234
235 Q_EMIT toolTipItemChanged();
236}
237
238int PlasmoidItem::screen() const
239{
240 if (Plasma::Containment *c = applet()->containment()) {
241 return c->screen();
242 }
243
244 return -1;
245}
246
247void PlasmoidItem::setHideOnWindowDeactivate(bool hide)
248{
249 if (m_hideOnDeactivate != hide) {
250 m_hideOnDeactivate = hide;
251 Q_EMIT hideOnWindowDeactivateChanged();
252 }
253}
254
256{
257 return m_hideOnDeactivate;
258}
259
261{
262 if (!applet() || !applet()->containment() || !applet()->containment()->corona() || applet()->containment()->screen() < 0) {
263 return QRect();
264 }
265
266 return applet()->containment()->corona()->screenGeometry(applet()->containment()->screen());
267}
268
269QVariantList PlasmoidItem::availableScreenRegion() const
270{
271 QVariantList regVal;
272
273 if (!applet()->containment() || !applet()->containment()->corona()) {
274 return regVal;
275 }
276
277 QRegion reg = QRect(0, 0, width(), height());
278 int screenId = screen();
279 if (screenId > -1) {
280 reg = applet()->containment()->corona()->availableScreenRegion(screenId);
281 }
282
283 auto it = reg.begin();
284 const auto itEnd = reg.end();
285 for (; it != itEnd; ++it) {
286 QRect rect = *it;
287 // make it relative
288 QRect geometry = applet()->containment()->corona()->screenGeometry(screenId);
289 rect.moveTo(rect.topLeft() - geometry.topLeft());
290 regVal << QVariant::fromValue(QRectF(rect));
291 }
292 return regVal;
293}
294
296{
297 if (!applet()->containment() || !applet()->containment()->corona()) {
298 return QRect();
299 }
300
301 QRect rect(0, 0, width(), height());
302
303 int screenId = screen();
304
305 // If corona returned an invalid screenId, try to use lastScreen value if it is valid
306 if (screenId == -1 && applet()->containment()->lastScreen() > -1) {
307 screenId = applet()->containment()->lastScreen();
308 // Is this a screen not actually valid?
309 if (screenId >= applet()->containment()->corona()->numScreens()) {
310 screenId = -1;
311 }
312 }
313
314 if (screenId > -1) {
315 rect = applet()->containment()->corona()->availableScreenRect(screenId);
316 // make it relative
317 QRect geometry = applet()->containment()->corona()->screenGeometry(screenId);
318 rect.moveTo(rect.topLeft() - geometry.topLeft());
319 }
320
321 return rect;
322}
323
324bool PlasmoidItem::event(QEvent *event)
325{
326 // QAction keyboard shortcuts cannot work with QML2 (and probably newver will
327 // since in Qt qtquick and qwidgets cannot depend from each other in any way)
328 // so do a simple keyboard shortcut matching here
329 if (event->type() == QEvent::KeyPress) {
330 QKeyEvent *ke = static_cast<QKeyEvent *>(event);
331 QKeySequence seq(ke->key() | ke->modifiers());
332
333 QList<QAction *> actions = applet()->internalActions();
334 actions.append(applet()->contextualActions());
335 // find the wallpaper action if we are a containment
336 ContainmentItem *ci = qobject_cast<ContainmentItem *>(this);
337 if (ci) {
338 WallpaperItem *wi = ci->wallpaperItem();
339 if (wi) {
340 actions << wi->contextualActions();
341 }
342 }
343
344 // add any actions of the corona
345 if (applet()->containment() && applet()->containment()->corona()) {
346 actions << applet()->containment()->corona()->actions();
347 }
348
349 bool keySequenceUsed = false;
350 for (auto a : std::as_const(actions)) {
351 if (a->shortcut().isEmpty()) {
352 continue;
353 }
354
355 if (!a->isEnabled()) {
356 continue;
357 }
358
359 // this will happen on a normal, non emacs shortcut
360 if (seq.matches(a->shortcut()) == QKeySequence::ExactMatch) {
361 event->accept();
362 a->trigger();
363 m_oldKeyboardShortcut = 0;
364 return true;
365
366 // first part of an emacs style shortcut?
367 } else if (seq.matches(a->shortcut()) == QKeySequence::PartialMatch) {
368 keySequenceUsed = true;
369 m_oldKeyboardShortcut = ke->key() | ke->modifiers();
370
371 // no match at all, but it can be the second part of an emacs style shortcut
372 } else {
373 QKeySequence seq(m_oldKeyboardShortcut, ke->key() | ke->modifiers());
374
375 if (seq.matches(a->shortcut()) == QKeySequence::ExactMatch) {
376 event->accept();
377 a->trigger();
378
379 return true;
380 }
381 }
382 }
383
384 if (!keySequenceUsed) {
385 m_oldKeyboardShortcut = 0;
386 }
387 }
388
389 return AppletQuickItem::event(event);
390}
391
396
397bool PlasmoidItem::eventFilter(QObject *watched, QEvent *event)
398{
399 if (event->type() == QEvent::MouseButtonPress) {
400 QMouseEvent *e = static_cast<QMouseEvent *>(event);
401
402 // pass it up to the applet
403 // well, actually we have to pass it to the *containment*
404 // because all the code for showing an applet's contextmenu is actually in Containment.
405 Plasma::Containment *c = applet()->containment();
406 if (c) {
408 Plasma::ContainmentActions *plugin = c->containmentActions().value(trigger);
409 if (!plugin) {
410 return false;
411 }
412
413 ContainmentItem *ci = qobject_cast<ContainmentItem *>(AppletQuickItem::itemForApplet(c));
414
415 if (!ci) {
416 return false;
417 }
418
419 // the plugin can be a single action or a context menu
420 // Don't have an action list? execute as single action
421 // and set the event position as action data
422 if (plugin->contextualActions().length() == 1) {
423 // but first check whether we are not a popup
424 // we don't want to randomly create applets without confirmation
425 if (static_cast<QQuickItem *>(watched)->window() != ci->window()) {
426 return true;
427 }
428
429 QAction *action = plugin->contextualActions().at(0);
430 action->setData(e->globalPosition().toPoint());
431 action->trigger();
432 return true;
433 }
434
435 QMenu *desktopMenu = new QMenu;
436 if (desktopMenu->winId()) {
437 desktopMenu->windowHandle()->setTransientParent(window());
438 }
440 ci->addAppletActions(desktopMenu, applet(), event);
441
442 if (!desktopMenu->isEmpty()) {
444 desktopMenu->popup(e->globalPosition().toPoint());
445 return true;
446 }
447
448 delete desktopMenu;
449 return false;
450 }
451 }
452
453 return AppletQuickItem::eventFilter(watched, event);
454}
455
456#include "moc_plasmoiditem.cpp"
This class is exposed to containments QML as the attached property Plasmoid.
QString description() const
void titleChanged(const QString &title)
Emitted when the title has changed.
void activated()
Emitted when activation is requested due to, for example, a global keyboard shortcut.
void destroyedChanged(bool destroyed)
Emitted when the applet has been scheduled for destruction or the destruction has been undone.
QList< QAction * > internalActions() const
Definition applet.cpp:673
QString title
User friendly title for the plasmoid: it's the localized applet name by default.
Definition applet.h:74
Plasma::Containment * containment
The Containment managing this applet.
Definition applet.h:189
void contextualActionsAboutToShow()
Emitted just before the contextual actions are about to show For instance just before the context men...
KPluginMetaData pluginMetaData() const
Definition applet.cpp:394
The base ContainmentActions class.
virtual QList< QAction * > contextualActions()
Implement this to provide a list of actions that can be added to another menu for example,...
static QString eventToString(QEvent *event)
Turns a mouse or wheel event into a string suitable for a ContainmentActions.
The base class for plugins that provide backgrounds and applet grouping containers.
Definition containment.h:47
Plasma::Corona * corona
The corona for this contaiment.
Definition containment.h:59
QHash< QString, ContainmentActions * > & containmentActions()
void screenChanged(int newScreen)
This signal indicates that a containment has been associated (or dissociated) with a physical screen.
virtual QRect screenGeometry(int id) const =0
Returns the geometry of a given screen.
virtual QRegion availableScreenRegion(int id) const
Returns the available region for a given screen.
Definition corona.cpp:267
QList< QAction * > actions() const
Definition corona.cpp:427
void availableScreenRegionChanged(int id)
This signal indicates that a change in available screen geometry occurred.
virtual QRect availableScreenRect(int id) const
Returns the available rect for a given screen.
Definition corona.cpp:272
void screenGeometryChanged(int id)
This signal indicates that a change in geometry for the screen occurred.
void availableScreenRectChanged(int id)
This signal indicates that a change in available screen geometry occurred.
void contextualActionsAboutToShow()
Emitted just before the contextual actions are about to show For instance just before the context men...
Q_INVOKABLE void prepareContextualActions()
Should be called before retrieving any action to ensure contents are up to date.
QRect screenGeometry
Provides access to the geometry of the applet is in.
QQuickItem * toolTipItem
This allows to set fully custom QML item as the tooltip.
QVariantList availableScreenRegion
The available region of this screen, panels excluded.
QString toolTipMainText
The QML root object defined in the applet main.qml will be direct child of an PlasmoidItem instance.
bool hideOnWindowDeactivate
Whether the dialog should be hidden when the dialog loses focus.
QRect availableScreenRect
screen area free of panels: the coordinates are relative to the containment, it's independent from th...
QString toolTipSubText
Description for the plasmoid tooltip or other means of quick information: it comes from the pluginifo...
int toolTipTextFormat
how to handle the text format of the tooltip subtext:
This class is exposed to wallpapers as the WallpaperItem root qml item.
QQmlListProperty< QAction > contextualActions
Actions to be added in the desktop context menu.
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
KGUIADDONS_EXPORT QWindow * window(QObject *job)
bool isValid(QStringView ifopt)
void setData(const QVariant &data)
void trigger()
int key() const const
Qt::KeyboardModifiers modifiers() const const
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
qsizetype length() const const
bool isEmpty() const const
void popup(const QPoint &p, QAction *atAction)
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void destroyed(QObject *obj)
T * data() const const
QPoint toPoint() const const
QQuickItem * parentItem() const const
void setVisible(bool)
QQuickWindow * window() const const
void moveTo(const QPoint &position)
QPoint topLeft() const const
const_iterator begin() const const
const_iterator end() const const
QPointF globalPosition() const const
QString first(qsizetype n) const const
bool isEmpty() const const
bool isNull() const const
WA_DeleteOnClose
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QVariant fromValue(T &&value)
void setAttribute(Qt::WidgetAttribute attribute, bool on)
WId winId() const const
QWindow * windowHandle() const const
void setTransientParent(QWindow *parent)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri May 17 2024 11:54:11 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.