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