1 /*
2     SPDX-FileCopyrightText: 2011 Marco Martin <mart@kde.org>
3     SPDX-FileCopyrightText: 2011 Artur Duque de Souza <asouza@kde.org>
4     SPDX-FileCopyrightText: 2013 Sebastian Kügler <sebas@kde.org>
5 
6     SPDX-License-Identifier: GPL-2.0-or-later
7 */
8 
9 #include "tooltip.h"
10 #include "tooltipdialog.h"
11 
12 #include <QDebug>
13 #include <QQmlEngine>
14 
15 #include "framesvgitem.h"
16 #include <KDirWatch>
17 #include <KWindowEffects>
18 
19 ToolTipDialog *ToolTip::s_dialog = nullptr;
20 int ToolTip::s_dialogUsers = 0;
21 
ToolTip(QQuickItem * parent)22 ToolTip::ToolTip(QQuickItem *parent)
23     : QQuickItem(parent)
24     , m_tooltipsEnabledGlobally(false)
25     , m_containsMouse(false)
26     , m_location(Plasma::Types::Floating)
27     , m_textFormat(Qt::AutoText)
28     , m_active(true)
29     , m_interactive(false)
30     , m_timeout(4000)
31     , m_usingDialog(false)
32 {
33     setAcceptHoverEvents(true);
34     setFiltersChildMouseEvents(true);
35 
36     m_showTimer = new QTimer(this);
37     m_showTimer->setSingleShot(true);
38     connect(m_showTimer, &QTimer::timeout, this, &ToolTip::showToolTip);
39 
40     loadSettings();
41 
42     const QString configFile = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QStringLiteral("/plasmarc");
43     KDirWatch::self()->addFile(configFile);
44     QObject::connect(KDirWatch::self(), &KDirWatch::created, this, &ToolTip::settingsChanged);
45     QObject::connect(KDirWatch::self(), &KDirWatch::dirty, this, &ToolTip::settingsChanged);
46 }
47 
~ToolTip()48 ToolTip::~ToolTip()
49 {
50     if (s_dialog && s_dialog->owner() == this) {
51         s_dialog->setVisible(false);
52     }
53 
54     if (m_usingDialog) {
55         --s_dialogUsers;
56     }
57 
58     if (s_dialogUsers == 0) {
59         delete s_dialog;
60         s_dialog = nullptr;
61     }
62 }
63 
settingsChanged(const QString & file)64 void ToolTip::settingsChanged(const QString &file)
65 {
66     if (!file.endsWith(QLatin1String("plasmarc"))) {
67         return;
68     }
69 
70     KSharedConfig::openConfig(QStringLiteral("plasmarc"))->reparseConfiguration();
71     loadSettings();
72 }
73 
loadSettings()74 void ToolTip::loadSettings()
75 {
76     KConfigGroup cfg = KConfigGroup(KSharedConfig::openConfig(QStringLiteral("plasmarc")), "PlasmaToolTips");
77     m_interval = cfg.readEntry("Delay", 700);
78     m_tooltipsEnabledGlobally = (m_interval > 0);
79 }
80 
mainItem() const81 QQuickItem *ToolTip::mainItem() const
82 {
83     return m_mainItem.data();
84 }
85 
tooltipDialogInstance()86 ToolTipDialog *ToolTip::tooltipDialogInstance()
87 {
88     if (!s_dialog) {
89         s_dialog = new ToolTipDialog;
90         s_dialogUsers = 1;
91     }
92 
93     if (!m_usingDialog) {
94         s_dialogUsers++;
95         m_usingDialog = true;
96     }
97 
98     return s_dialog;
99 }
100 
setMainItem(QQuickItem * mainItem)101 void ToolTip::setMainItem(QQuickItem *mainItem)
102 {
103     if (m_mainItem.data() != mainItem) {
104         m_mainItem = mainItem;
105 
106         Q_EMIT mainItemChanged();
107 
108         if (!isValid() && s_dialog && s_dialog->owner() == this) {
109             s_dialog->setVisible(false);
110         }
111     }
112 }
113 
showToolTip()114 void ToolTip::showToolTip()
115 {
116     if (!m_active) {
117         return;
118     }
119 
120     Q_EMIT aboutToShow();
121 
122     ToolTipDialog *dlg = tooltipDialogInstance();
123 
124     if (!mainItem()) {
125         setMainItem(dlg->loadDefaultItem());
126     }
127 
128     // Unset the dialog's old contents before reparenting the dialog.
129     dlg->setMainItem(nullptr);
130 
131     Plasma::Types::Location location = m_location;
132     if (m_location == Plasma::Types::Floating) {
133         QQuickItem *p = parentItem();
134         while (p) {
135             if (p->property("location").isValid()) {
136                 location = (Plasma::Types::Location)p->property("location").toInt();
137                 break;
138             }
139             p = p->parentItem();
140         }
141     }
142 
143     if (mainItem()) {
144         mainItem()->setProperty("toolTip", QVariant::fromValue(this));
145         mainItem()->setVisible(true);
146     }
147 
148     connect(dlg, &ToolTipDialog::visibleChanged, this, &ToolTip::toolTipVisibleChanged, Qt::UniqueConnection);
149 
150     dlg->setHideTimeout(m_timeout);
151     dlg->setOwner(this);
152     dlg->setLocation(location);
153     dlg->setVisualParent(this);
154     dlg->setMainItem(mainItem());
155     dlg->setInteractive(m_interactive);
156     dlg->setVisible(true);
157 }
158 
mainText() const159 QString ToolTip::mainText() const
160 {
161     return m_mainText;
162 }
163 
setMainText(const QString & mainText)164 void ToolTip::setMainText(const QString &mainText)
165 {
166     if (mainText == m_mainText) {
167         return;
168     }
169 
170     m_mainText = mainText;
171     Q_EMIT mainTextChanged();
172 
173     if (!isValid() && s_dialog && s_dialog->owner() == this) {
174         s_dialog->setVisible(false);
175     }
176 }
177 
subText() const178 QString ToolTip::subText() const
179 {
180     return m_subText;
181 }
182 
setSubText(const QString & subText)183 void ToolTip::setSubText(const QString &subText)
184 {
185     if (subText == m_subText) {
186         return;
187     }
188 
189     m_subText = subText;
190     Q_EMIT subTextChanged();
191 
192     if (!isValid() && s_dialog && s_dialog->owner() == this) {
193         s_dialog->setVisible(false);
194     }
195 }
196 
textFormat() const197 int ToolTip::textFormat() const
198 {
199     return m_textFormat;
200 }
201 
setTextFormat(int format)202 void ToolTip::setTextFormat(int format)
203 {
204     if (m_textFormat == format) {
205         return;
206     }
207 
208     m_textFormat = format;
209     Q_EMIT textFormatChanged();
210 }
211 
location() const212 Plasma::Types::Location ToolTip::location() const
213 {
214     return m_location;
215 }
216 
setLocation(Plasma::Types::Location location)217 void ToolTip::setLocation(Plasma::Types::Location location)
218 {
219     if (m_location == location) {
220         return;
221     }
222     m_location = location;
223     Q_EMIT locationChanged();
224 }
225 
setActive(bool active)226 void ToolTip::setActive(bool active)
227 {
228     if (m_active == active) {
229         return;
230     }
231 
232     m_active = active;
233     if (!active) {
234         tooltipDialogInstance()->dismiss();
235     }
236     Q_EMIT activeChanged();
237 }
238 
setInteractive(bool interactive)239 void ToolTip::setInteractive(bool interactive)
240 {
241     if (m_interactive == interactive) {
242         return;
243     }
244 
245     m_interactive = interactive;
246 
247     Q_EMIT interactiveChanged();
248 }
249 
setTimeout(int timeout)250 void ToolTip::setTimeout(int timeout)
251 {
252     m_timeout = timeout;
253 }
254 
hideToolTip()255 void ToolTip::hideToolTip()
256 {
257     m_showTimer->stop();
258     tooltipDialogInstance()->dismiss();
259 }
260 
hideImmediately()261 void ToolTip::hideImmediately()
262 {
263     m_showTimer->stop();
264     tooltipDialogInstance()->setVisible(false);
265 }
266 
icon() const267 QVariant ToolTip::icon() const
268 {
269     if (m_icon.isValid()) {
270         return m_icon;
271     } else {
272         return QString();
273     }
274 }
275 
setIcon(const QVariant & icon)276 void ToolTip::setIcon(const QVariant &icon)
277 {
278     if (icon == m_icon) {
279         return;
280     }
281 
282     m_icon = icon;
283     Q_EMIT iconChanged();
284 }
285 
image() const286 QVariant ToolTip::image() const
287 {
288     if (m_image.isValid()) {
289         return m_image;
290     } else {
291         return QString();
292     }
293 }
294 
setImage(const QVariant & image)295 void ToolTip::setImage(const QVariant &image)
296 {
297     if (image == m_image) {
298         return;
299     }
300 
301     m_image = image;
302     Q_EMIT imageChanged();
303 }
304 
containsMouse() const305 bool ToolTip::containsMouse() const
306 {
307     return m_containsMouse;
308 }
309 
setContainsMouse(bool contains)310 void ToolTip::setContainsMouse(bool contains)
311 {
312     if (m_containsMouse != contains) {
313         m_containsMouse = contains;
314         Q_EMIT containsMouseChanged();
315     }
316     if (!contains) {
317         tooltipDialogInstance()->dismiss();
318     }
319 }
320 
hoverEnterEvent(QHoverEvent * event)321 void ToolTip::hoverEnterEvent(QHoverEvent *event)
322 {
323     Q_UNUSED(event)
324     setContainsMouse(true);
325 
326     if (!m_tooltipsEnabledGlobally) {
327         return;
328     }
329 
330     if (!isValid()) {
331         return;
332     }
333 
334     if (tooltipDialogInstance()->isVisible()) {
335         // We signal the tooltipmanager that we're "potentially interested,
336         // and ask to keep it open for a bit, so other items get the chance
337         // to update the content before the tooltip hides -- this avoids
338         // flickering
339         // It need to be considered only when other items can deal with tooltip area
340         if (m_active) {
341             tooltipDialogInstance()->keepalive();
342             // FIXME: showToolTip needs to be renamed in sync or something like that
343             showToolTip();
344         }
345     } else {
346         m_showTimer->start(m_interval);
347     }
348 }
349 
hoverLeaveEvent(QHoverEvent * event)350 void ToolTip::hoverLeaveEvent(QHoverEvent *event)
351 {
352     Q_UNUSED(event)
353     setContainsMouse(false);
354     m_showTimer->stop();
355 }
356 
childMouseEventFilter(QQuickItem * item,QEvent * event)357 bool ToolTip::childMouseEventFilter(QQuickItem *item, QEvent *event)
358 {
359     if (event->type() == QEvent::MouseButtonPress) {
360         hideToolTip();
361     }
362     return QQuickItem::childMouseEventFilter(item, event);
363 }
364 
isValid() const365 bool ToolTip::isValid() const
366 {
367     return m_mainItem || !mainText().isEmpty() || !subText().isEmpty();
368 }
369