Fluid 1.3.0
Material Design for QtQuick and Qml
Loading...
Searching...
No Matches
tooltipattached.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
2// SPDX-License-Identifier: MPL-2.0
3
4#include <cstring>
5
6#include <QtCore/qmetaobject.h>
7#include <QtCore/qvariant.h>
8#include <QtQml/qqmlcomponent.h>
9#include <QtQml/qqmlengine.h>
10#include <QtQml/qqmlinfo.h>
11#include <QtQuick/qquickitem.h>
12
13#include "tooltipattached.h"
14
15namespace Fluid {
16
17// The public-Qt attached-provider and per-engine manager design follows QmlMaterial:
18// https://github.com/hypengw/QmlMaterial/blob/main/src/control/tool_tip.cpp
19
20namespace {
21
22constexpr auto managerPropertyName = "_fluid_plain_tooltip_manager";
23constexpr auto plainToolTipUrl = "qrc:/qt/qml/Fluid/qml/components/PlainToolTip.qml";
24
25void resetProperty(QObject *object, const char *name)
26{
27 const int index = object->metaObject()->indexOfProperty(name);
28 if (index >= 0) {
29 const QMetaProperty property = object->metaObject()->property(index);
30 if (property.isResettable())
31 property.reset(object);
32 }
33}
34
35} // namespace
36
37class PlainToolTipManager : public QObject
38{
39 Q_OBJECT
40
41public:
42 explicit PlainToolTipManager(QQmlEngine *engine)
43 : QObject(engine)
44 , m_engine(engine)
45 {
46 }
47
48 static PlainToolTipManager *instance(QQmlEngine *engine, bool create)
49 {
50 if (!engine)
51 return nullptr;
52
53 const QVariant value = engine->property(managerPropertyName);
54 if (auto *manager = qobject_cast<PlainToolTipManager *>(value.value<QObject *>()))
55 return manager;
56 if (!create)
57 return nullptr;
58
59 auto *manager = new PlainToolTipManager(engine);
60 engine->setProperty(managerPropertyName, QVariant::fromValue<QObject *>(manager));
61 return manager;
62 }
63
64 QObject *toolTip(bool create)
65 {
66 if (m_toolTip || !create)
67 return m_toolTip;
68
69 QQmlComponent component(m_engine, QUrl(QString::fromLatin1(plainToolTipUrl)));
70 if (!component.isReady()) {
71 reportErrors(component);
72 return nullptr;
73 }
74
75 QObject *toolTip = component.create();
76 if (!toolTip) {
77 reportErrors(component);
78 return nullptr;
79 }
80
81 toolTip->setParent(this);
82 m_toolTip = toolTip;
83 connect(toolTip, SIGNAL(visibleChanged()), this, SLOT(toolTipVisibleChanged()));
84 return toolTip;
85 }
86
87 bool isVisible(const PlainToolTipAttached *attached) const
88 {
89 return attached && attached == m_current && m_toolTip
90 && m_toolTip->property("visible").toBool();
91 }
92
93 void show(PlainToolTipAttached *attached, const QString &text, int timeout)
94 {
95 QQuickItem *owner = attached ? attached->target() : nullptr;
96 QObject *toolTip = owner ? this->toolTip(true) : nullptr;
97 if (!owner || !toolTip)
98 return;
99
100 if (m_current && m_current != attached)
101 hide(m_current);
102
103 m_current = attached;
104 setOwner(owner);
105 resetProperty(toolTip, "width");
106 resetProperty(toolTip, "height");
107 toolTip->setProperty("parent", QVariant::fromValue(owner));
108 toolTip->setProperty("text", text);
109 toolTip->setProperty("delay", attached->delay());
110 toolTip->setProperty("timeout", attached->timeout());
111 m_requestedText = text;
112 m_requestedTimeout = timeout;
113 m_showRequested = true;
114
115 if (owner->window())
116 invokeShow();
117 }
118
120 {
121 if (!attached || attached != m_current || !m_toolTip)
122 return;
123
124 m_showRequested = false;
125 if (!QMetaObject::invokeMethod(m_toolTip, "hide"))
126 qmlWarning(attached->target()) << "Failed to invoke PlainToolTip.hide()";
127 }
128
130 {
131 if (!attached || attached != m_current)
132 return;
133
134 m_showRequested = false;
135 if (m_toolTip)
136 QMetaObject::invokeMethod(m_toolTip, "hide");
137 m_current.clear();
138 setOwner(nullptr);
139 }
140
141 void sync(PlainToolTipAttached *attached, const char *property, const QVariant &value)
142 {
143 if (!attached || attached != m_current || !m_toolTip
144 || (!m_showRequested && !isVisible(attached))) {
145 return;
146 }
147
148 m_toolTip->setProperty(property, value);
149 if (std::strcmp(property, "text") == 0)
150 m_requestedText = value.toString();
151 else if (std::strcmp(property, "timeout") == 0)
152 m_requestedTimeout = -1;
153 else if (std::strcmp(property, "delay") == 0 && m_showRequested
154 && !m_toolTip->property("visible").toBool() && m_owner && m_owner->window())
155 invokeShow();
156 }
157
158private Q_SLOTS:
159 void toolTipVisibleChanged()
160 {
161 if (m_toolTip && !m_toolTip->property("visible").toBool())
162 m_showRequested = false;
163 if (m_current)
164 m_current->notifyVisibleChanged();
165 }
166
167private:
168 void setOwner(QQuickItem *owner)
169 {
170 if (m_owner == owner)
171 return;
172 if (m_owner)
173 disconnect(m_owner, nullptr, this, nullptr);
174
175 m_owner = owner;
176 if (m_owner) {
177 connect(m_owner, &QQuickItem::windowChanged, this, [this](QQuickWindow *window) {
178 if (window && m_showRequested)
179 invokeShow();
180 });
181 }
182 }
183
184 void invokeShow()
185 {
186 if (!m_toolTip || !m_owner || !m_showRequested)
187 return;
188
189 if (!QMetaObject::invokeMethod(m_toolTip, "show", Q_ARG(QString, m_requestedText),
190 Q_ARG(int, m_requestedTimeout))) {
191 m_showRequested = false;
192 qmlWarning(m_owner) << "Failed to invoke PlainToolTip.show()";
193 }
194 }
195
196 void reportErrors(const QQmlComponent &component) const
197 {
198 const auto errors = component.errors();
199 if (errors.isEmpty()) {
200 qmlWarning(m_engine) << "Failed to create PlainToolTip";
201 return;
202 }
203 for (const auto &error : errors)
204 qmlWarning(m_engine) << error.toString();
205 }
206
207 QPointer<QQmlEngine> m_engine;
208 QPointer<QObject> m_toolTip;
209 QPointer<PlainToolTipAttached> m_current;
210 QPointer<QQuickItem> m_owner;
211 QString m_requestedText;
212 int m_requestedTimeout = -1;
213 bool m_showRequested = false;
214};
215
217 : QObject(parent)
218{
219 if (parent && !qobject_cast<QQuickItem *>(parent))
220 qmlWarning(parent) << "ToolTip attached properties require a QQuickItem target";
221}
222
224{
225 if (PlainToolTipManager *toolTipManager = manager(false))
226 toolTipManager->release(this);
227}
228
230{
231 return m_text;
232}
233
234void PlainToolTipAttached::setText(const QString &text)
235{
236 if (m_text == text)
237 return;
238 m_text = text;
239 if (PlainToolTipManager *toolTipManager = manager(false))
240 toolTipManager->sync(this, "text", text);
241 Q_EMIT textChanged();
242}
243
245{
246 return m_delay;
247}
248
250{
251 if (m_delay == delay)
252 return;
253 m_delay = delay;
254 if (PlainToolTipManager *toolTipManager = manager(false))
255 toolTipManager->sync(this, "delay", delay);
256 Q_EMIT delayChanged();
257}
258
260{
261 return m_timeout;
262}
263
265{
266 if (m_timeout == timeout)
267 return;
268 m_timeout = timeout;
269 if (PlainToolTipManager *toolTipManager = manager(false))
270 toolTipManager->sync(this, "timeout", timeout);
271 Q_EMIT timeoutChanged();
272}
273
275{
276 PlainToolTipManager *toolTipManager = manager(false);
277 return toolTipManager && toolTipManager->isVisible(this);
278}
279
281{
282 if (visible)
283 show(m_text);
284 else
285 hide();
286}
287
289{
290 PlainToolTipManager *toolTipManager = manager(true);
291 return toolTipManager ? toolTipManager->toolTip(true) : nullptr;
292}
293
294void PlainToolTipAttached::show(const QString &text, int timeout)
295{
296 PlainToolTipManager *toolTipManager = manager(true);
297 if (toolTipManager)
298 toolTipManager->show(this, text, timeout);
299}
300
302{
303 if (PlainToolTipManager *toolTipManager = manager(false))
304 toolTipManager->hide(this);
305}
306
307PlainToolTipManager *PlainToolTipAttached::manager(bool create) const
308{
309 return PlainToolTipManager::instance(qmlEngine(parent()), create);
310}
311
312QQuickItem *PlainToolTipAttached::target() const
313{
314 return qobject_cast<QQuickItem *>(parent());
315}
316
317void PlainToolTipAttached::notifyVisibleChanged()
318{
319 Q_EMIT visibleChanged();
320}
321
323 : QObject(parent)
324{
325}
326
328{
329 return new PlainToolTipAttached(object);
330}
331
332} // namespace Fluid
333
334#include "tooltipattached.moc"
static PlainToolTipAttached * qmlAttachedProperties(QObject *object)
AttachedToolTip(QObject *parent=nullptr)
PlainToolTipAttached(QObject *parent=nullptr)
void setText(const QString &text)
Q_INVOKABLE void show(const QString &text, int timeout=-1)
static PlainToolTipManager * instance(QQmlEngine *engine, bool create)
void hide(PlainToolTipAttached *attached)
PlainToolTipManager(QQmlEngine *engine)
void release(PlainToolTipAttached *attached)
void show(PlainToolTipAttached *attached, const QString &text, int timeout)
bool isVisible(const PlainToolTipAttached *attached) const
void sync(PlainToolTipAttached *attached, const char *property, const QVariant &value)
QObject * toolTip(bool create)