Libplasma

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 connect(applet->containment()->corona(), &Plasma::Corona::screenGeometryChanged, this, [this](int id) {
70 if (id == AppletQuickItem::applet()->containment()->screen()) {
71 Q_EMIT screenGeometryChanged();
72 }
73 });
74
75 connect(applet->containment()->corona(), &Plasma::Corona::availableScreenRegionChanged, this, &ContainmentItem::availableScreenRegionChanged);
76 connect(applet->containment()->corona(), &Plasma::Corona::availableScreenRectChanged, this, &ContainmentItem::availableScreenRectChanged);
77 }
78
79 connect(this, &PlasmoidItem::expandedChanged, [=, this](bool expanded) {
80 // if both compactRepresentationItem and fullRepresentationItem exist,
81 // the applet is in a popup
82 if (expanded) {
83 /* clang-format off */
84 if (compactRepresentationItem()
85 && fullRepresentationItem()
86 && fullRepresentationItem()->window()
87 && compactRepresentationItem()->window()
88 && fullRepresentationItem()->window() != compactRepresentationItem()->window()
89 && fullRepresentationItem()->parentItem()) {
90 /* clang-format on */
91 fullRepresentationItem()->parentItem()->installEventFilter(this);
92 } else if (fullRepresentationItem() && fullRepresentationItem()->parentItem()) {
93 fullRepresentationItem()->parentItem()->removeEventFilter(this);
94 }
95 }
96 });
97
98 geometryChange(QRectF(), QRectF(x(), y(), width(), height()));
99
100 connect(applet, &Plasma::Applet::activated, this, [=, this]() {
101 // in case the applet doesn't want to get shrunk on reactivation,
102 // we always expand it again (only in order to conform with legacy behaviour)
103 bool activate = !(isExpanded() && isActivationTogglesExpanded());
104
105 setExpanded(activate);
106 });
107
108 connect(applet, &Plasma::Applet::destroyedChanged, this, &PlasmoidItem::destroyedChanged);
109
110 auto args = applet->startupArguments();
111
112 if (args.count() == 1) {
113 Q_EMIT externalData(QString(), args.first());
114 } else if (!args.isEmpty()) {
115 Q_EMIT externalData(QString(), args);
116 }
117}
118
119void PlasmoidItem::destroyedChanged(bool destroyed)
120{
121 // if an item loses its scene before losing the focus, will never
122 // be able to gain focus again
123 if (destroyed && window() && window()->activeFocusItem()) {
125 QQuickItem *candidate = focus;
126 bool isAncestor = false;
127
128 // search if the current focus item is a child or grandchild of the applet
129 while (candidate) {
130 if (candidate == this) {
131 isAncestor = true;
132 break;
133 }
134 candidate = candidate->parentItem();
135 }
136
137 if (isAncestor) {
138 // Found? remove focus for the whole hierarchy
139 candidate = focus;
140
141 while (candidate && candidate != this) {
142 candidate->setFocus(false);
143 candidate = candidate->parentItem();
144 }
145 }
146 }
147
149}
150
152{
153 if (m_toolTipMainText.isNull()) {
154 return applet()->title();
155 } else {
156 return m_toolTipMainText;
157 }
158}
159
160void PlasmoidItem::setToolTipMainText(const QString &text)
161{
162 // Here we are abusing the difference between a null and an empty string.
163 // by default is null so falls back to the name
164 // the fist time it gets set, an empty non null one is set, and won't fallback anymore
165 if (!m_toolTipMainText.isNull() && m_toolTipMainText == text) {
166 return;
167 }
168
169 if (text.isEmpty()) {
170 m_toolTipMainText = QStringLiteral(""); // this "" makes it non-null
171 } else {
172 m_toolTipMainText = text;
173 }
174
175 Q_EMIT toolTipMainTextChanged();
176}
177
179{
180 if (m_toolTipSubText.isNull() && applet()->pluginMetaData().isValid()) {
181 return applet()->pluginMetaData().description();
182 } else {
183 return m_toolTipSubText;
184 }
185}
186
187void PlasmoidItem::setToolTipSubText(const QString &text)
188{
189 // Also there the difference between null and empty gets exploited
190 if (!m_toolTipSubText.isNull() && m_toolTipSubText == text) {
191 return;
192 }
193
194 if (text.isEmpty()) {
195 m_toolTipSubText = QStringLiteral(""); // this "" makes it non-null
196 } else {
197 m_toolTipSubText = text;
198 }
199
200 Q_EMIT toolTipSubTextChanged();
201}
202
204{
205 return m_toolTipTextFormat;
206}
207
208void PlasmoidItem::setToolTipTextFormat(int format)
209{
210 if (m_toolTipTextFormat == format) {
211 return;
212 }
213
214 m_toolTipTextFormat = format;
215 Q_EMIT toolTipTextFormatChanged();
216}
217
219{
220 return m_toolTipItem.data();
221}
222
223void PlasmoidItem::setToolTipItem(QQuickItem *toolTipItem)
224{
225 if (m_toolTipItem.data() == toolTipItem) {
226 return;
227 }
228
229 m_toolTipItem = toolTipItem;
230 connect(m_toolTipItem.data(), &QObject::destroyed, this, &PlasmoidItem::toolTipItemChanged);
231
232 Q_EMIT toolTipItemChanged();
233}
234
235int PlasmoidItem::screen() const
236{
237 if (Plasma::Containment *c = applet()->containment()) {
238 return c->screen();
239 }
240
241 return -1;
242}
243
244void PlasmoidItem::setHideOnWindowDeactivate(bool hide)
245{
246 if (m_hideOnDeactivate != hide) {
247 m_hideOnDeactivate = hide;
248 Q_EMIT hideOnWindowDeactivateChanged();
249 }
250}
251
253{
254 return m_hideOnDeactivate;
255}
256
258{
259 if (!applet() || !applet()->containment() || !applet()->containment()->corona() || applet()->containment()->screen() < 0) {
260 return QRect();
261 }
262
263 return applet()->containment()->corona()->screenGeometry(applet()->containment()->screen());
264}
265
266QVariantList PlasmoidItem::availableScreenRegion() const
267{
268 QVariantList regVal;
269
270 if (!applet()->containment() || !applet()->containment()->corona()) {
271 return regVal;
272 }
273
274 QRegion reg = QRect(0, 0, width(), height());
275 int screenId = screen();
276 if (screenId > -1) {
277 reg = applet()->containment()->corona()->availableScreenRegion(screenId);
278 }
279
280 auto it = reg.begin();
281 const auto itEnd = reg.end();
282 for (; it != itEnd; ++it) {
283 QRect rect = *it;
284 // make it relative
285 QRect geometry = applet()->containment()->corona()->screenGeometry(screenId);
286 rect.moveTo(rect.topLeft() - geometry.topLeft());
287 regVal << QVariant::fromValue(QRectF(rect));
288 }
289 return regVal;
290}
291
293{
294 if (!applet()->containment() || !applet()->containment()->corona()) {
295 return QRect();
296 }
297
298 QRect rect(0, 0, width(), height());
299
300 int screenId = screen();
301
302 // If corona returned an invalid screenId, try to use lastScreen value if it is valid
303 if (screenId == -1 && applet()->containment()->lastScreen() > -1) {
304 screenId = applet()->containment()->lastScreen();
305 // Is this a screen not actually valid?
306 if (screenId >= applet()->containment()->corona()->numScreens()) {
307 screenId = -1;
308 }
309 }
310
311 if (screenId > -1) {
312 rect = applet()->containment()->corona()->availableScreenRect(screenId);
313 // make it relative
314 QRect geometry = applet()->containment()->corona()->screenGeometry(screenId);
315 rect.moveTo(rect.topLeft() - geometry.topLeft());
316 }
317
318 return rect;
319}
320
321bool PlasmoidItem::event(QEvent *event)
322{
323 // QAction keyboard shortcuts cannot work with QML2 (and probably newver will
324 // since in Qt qtquick and qwidgets cannot depend from each other in any way)
325 // so do a simple keyboard shortcut matching here
326 if (event->type() == QEvent::KeyPress) {
327 QKeyEvent *ke = static_cast<QKeyEvent *>(event);
328 QKeySequence seq(ke->key() | ke->modifiers());
329
330 QList<QAction *> actions = applet()->internalActions();
331 actions.append(applet()->contextualActions());
332 // find the wallpaper action if we are a containment
334 if (ci) {
335 WallpaperItem *wi = ci->wallpaperItem();
336 if (wi) {
337 actions << wi->contextualActions();
338 }
339 }
340
341 // add any actions of the corona
342 if (applet()->containment() && applet()->containment()->corona()) {
343 actions << applet()->containment()->corona()->actions();
344 }
345
346 bool keySequenceUsed = false;
347 for (auto a : std::as_const(actions)) {
348 if (a->shortcut().isEmpty()) {
349 continue;
350 }
351
352 if (!a->isEnabled()) {
353 continue;
354 }
355
356 // this will happen on a normal, non emacs shortcut
357 if (seq.matches(a->shortcut()) == QKeySequence::ExactMatch) {
358 event->accept();
359 a->trigger();
360 m_oldKeyboardShortcut = 0;
361 return true;
362
363 // first part of an emacs style shortcut?
364 } else if (seq.matches(a->shortcut()) == QKeySequence::PartialMatch) {
365 keySequenceUsed = true;
366 m_oldKeyboardShortcut = ke->key() | ke->modifiers();
367
368 // no match at all, but it can be the second part of an emacs style shortcut
369 } else {
370 QKeySequence seq(m_oldKeyboardShortcut, ke->key() | ke->modifiers());
371
372 if (seq.matches(a->shortcut()) == QKeySequence::ExactMatch) {
373 event->accept();
374 a->trigger();
375
376 return true;
377 }
378 }
379 }
380
381 if (!keySequenceUsed) {
382 m_oldKeyboardShortcut = 0;
383 }
384 }
385
386 return AppletQuickItem::event(event);
387}
388
393
394bool PlasmoidItem::eventFilter(QObject *watched, QEvent *event)
395{
396 if (event->type() == QEvent::MouseButtonPress) {
397 QMouseEvent *e = static_cast<QMouseEvent *>(event);
398
399 // pass it up to the applet
400 // well, actually we have to pass it to the *containment*
401 // because all the code for showing an applet's contextmenu is actually in Containment.
402 Plasma::Containment *c = applet()->containment();
403 if (c) {
405 Plasma::ContainmentActions *plugin = c->containmentActions().value(trigger);
406 if (!plugin) {
407 return false;
408 }
409
410 ContainmentItem *ci = qobject_cast<ContainmentItem *>(AppletQuickItem::itemForApplet(c));
411
412 if (!ci) {
413 return false;
414 }
415
416 // the plugin can be a single action or a context menu
417 // Don't have an action list? execute as single action
418 // and set the event position as action data
419 if (plugin->contextualActions().length() == 1) {
420 // but first check whether we are not a popup
421 // we don't want to randomly create applets without confirmation
422 if (static_cast<QQuickItem *>(watched)->window() != ci->window()) {
423 return true;
424 }
425
426 QAction *action = plugin->contextualActions().at(0);
427 action->setData(e->globalPosition().toPoint());
428 action->trigger();
429 return true;
430 }
431
432 QMenu *desktopMenu = new QMenu;
433 if (desktopMenu->winId()) {
434 desktopMenu->windowHandle()->setTransientParent(window());
435 }
437 ci->addAppletActions(desktopMenu, applet(), event);
438
439 if (!desktopMenu->isEmpty()) {
441 desktopMenu->popup(e->globalPosition().toPoint());
442 return true;
443 }
444
445 delete desktopMenu;
446 return false;
447 }
448 }
449
450 return AppletQuickItem::eventFilter(watched, event);
451}
452
453#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:681
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:391
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)
QWidget * 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 qobject_cast(QObject *object)
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 Nov 22 2024 12:06:24 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.