]> cloud.milkyroute.net Git - dolphin.git/blob - src/ktooltip.cpp
- Fix crash found while investigating https://bugs.kde.org/show_bug.cgi?id=170927
[dolphin.git] / src / ktooltip.cpp
1 /***************************************************************************
2 * Copyright (C) 2008 by Fredrik Höglund <fredrik@kde.org> *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
18 ***************************************************************************/
19
20 #include "ktooltip.h"
21
22 #include <QApplication>
23 #include <QMap>
24 #include <QPixmap>
25 #include <QPainter>
26 #include <QVariant>
27 #include <QIcon>
28 #include <QWidget>
29 #include <QToolTip>
30 #include <QDebug>
31
32 #ifdef Q_WS_X11
33 # include <QX11Info>
34 # include <X11/Xlib.h>
35 # include <X11/extensions/Xrender.h>
36 # include <X11/extensions/shape.h>
37 #endif
38
39 #include "ktooltip_p.h"
40
41
42 // compile with XShape older than 1.0
43 #ifndef ShapeInput
44 const int ShapeInput = 2;
45 #endif
46
47
48 class KToolTipItemPrivate
49 {
50 public:
51 QMap<int, QVariant> map;
52 int type;
53 };
54
55 KToolTipItem::KToolTipItem(const QString &text, int type)
56 : d(new KToolTipItemPrivate)
57 {
58 d->map[Qt::DisplayRole] = text;
59 d->type = type;
60 }
61
62 KToolTipItem::KToolTipItem(const QIcon &icon, const QString &text, int type)
63 : d(new KToolTipItemPrivate)
64 {
65 d->map[Qt::DecorationRole] = icon;
66 d->map[Qt::DisplayRole] = text;
67 d->type = type;
68 }
69
70 KToolTipItem::~KToolTipItem()
71 {
72 delete d;
73 }
74
75 int KToolTipItem::type() const
76 {
77 return d->type;
78 }
79
80 QString KToolTipItem::text() const
81 {
82 return data(Qt::DisplayRole).toString();
83 }
84
85 QIcon KToolTipItem::icon() const
86 {
87 return qvariant_cast<QIcon>(data(Qt::DecorationRole));
88 }
89
90 QVariant KToolTipItem::data(int role) const
91 {
92 return d->map.value(role);
93 }
94
95 void KToolTipItem::setData(int role, const QVariant &data)
96 {
97 d->map[role] = data;
98 KToolTipManager::instance()->update();
99 }
100
101
102
103 // ----------------------------------------------------------------------------
104
105
106 KStyleOptionToolTip::KStyleOptionToolTip()
107 : fontMetrics(QApplication::font())
108 {
109 }
110
111
112 // ----------------------------------------------------------------------------
113
114
115
116 KToolTipDelegate::KToolTipDelegate() : QObject()
117 {
118 }
119
120 KToolTipDelegate::~KToolTipDelegate()
121 {
122 }
123
124 QSize KToolTipDelegate::sizeHint(const KStyleOptionToolTip *option, const KToolTipItem *item) const
125 {
126 QSize size;
127 size.rwidth() = option->fontMetrics.width(item->text());
128 size.rheight() = option->fontMetrics.lineSpacing();
129
130 QIcon icon = item->icon();
131 if (!icon.isNull()) {
132 const QSize iconSize = icon.actualSize(option->decorationSize);
133 size.rwidth() += iconSize.width() + 4;
134 size.rheight() = qMax(size.height(), iconSize.height());
135 }
136
137 return size + QSize(20, 20);
138
139 }
140
141 void KToolTipDelegate::paint(QPainter *painter, const KStyleOptionToolTip *option,
142 const KToolTipItem *item) const
143 {
144 bool haveAlpha = haveAlphaChannel();
145 painter->setRenderHint(QPainter::Antialiasing);
146
147 QPainterPath path;
148 if (haveAlpha)
149 path.addRoundRect(option->rect.adjusted(0, 0, -1, -1), 25);
150 else
151 path.addRect(option->rect.adjusted(0, 0, -1, -1));
152
153 #if QT_VERSION >= 0x040400
154 QColor color = option->palette.color(QPalette::ToolTipBase);
155 #else
156 QColor color = option->palette.color(QPalette::Base);
157 #endif
158 QColor from = color.lighter(105);
159 QColor to = color.darker(120);
160
161 QLinearGradient gradient(0, 0, 0, 1);
162 gradient.setCoordinateMode(QGradient::ObjectBoundingMode);
163 gradient.setColorAt(0, from);
164 gradient.setColorAt(1, to);
165
166 painter->translate(.5, .5);
167 painter->setPen(QPen(Qt::black, 1));
168 painter->setBrush(gradient);
169 painter->drawPath(path);
170 painter->translate(-.5, -.5);
171
172 if (haveAlpha) {
173 QLinearGradient mask(0, 0, 0, 1);
174 gradient.setCoordinateMode(QGradient::ObjectBoundingMode);
175 gradient.setColorAt(0, QColor(0, 0, 0, 192));
176 gradient.setColorAt(1, QColor(0, 0, 0, 72));
177 painter->setCompositionMode(QPainter::CompositionMode_DestinationIn);
178 painter->fillRect(option->rect, gradient);
179 painter->setCompositionMode(QPainter::CompositionMode_SourceOver);
180 }
181
182 QRect textRect = option->rect.adjusted(10, 10, -10, -10);
183
184 QIcon icon = item->icon();
185 if (!icon.isNull()) {
186 const QSize iconSize = icon.actualSize(option->decorationSize);
187 painter->drawPixmap(textRect.topLeft(), icon.pixmap(iconSize));
188 textRect.adjust(iconSize.width() + 4, 0, 0, 0);
189 }
190 painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, item->text());
191 }
192
193 QRegion KToolTipDelegate::inputShape(const KStyleOptionToolTip *option) const
194 {
195 return QRegion(option->rect);
196 }
197
198 QRegion KToolTipDelegate::shapeMask(const KStyleOptionToolTip *option) const
199 {
200 return QRegion(option->rect);
201 }
202
203 bool KToolTipDelegate::haveAlphaChannel() const
204 {
205 return KToolTipManager::instance()->haveAlphaChannel();
206 }
207
208
209
210 // ----------------------------------------------------------------------------
211
212
213
214 class KAbstractToolTipLabel
215 {
216 public:
217 KAbstractToolTipLabel() {}
218 virtual ~KAbstractToolTipLabel() {}
219
220 virtual void showTip(const QPoint &pos, const KToolTipItem *item) = 0;
221 virtual void moveTip(const QPoint &pos) = 0;
222 virtual void hideTip() = 0;
223
224 protected:
225 KStyleOptionToolTip styleOption() const;
226 KToolTipDelegate *delegate() const;
227 };
228
229 KStyleOptionToolTip KAbstractToolTipLabel::styleOption() const
230 {
231 KStyleOptionToolTip option;
232 KToolTipManager::instance()->initStyleOption(&option);
233 return option;
234 }
235
236 KToolTipDelegate *KAbstractToolTipLabel::delegate() const
237 {
238 return KToolTipManager::instance()->delegate();
239 }
240
241
242 // ----------------------------------------------------------------------------
243
244
245
246 class QWidgetLabel : public QWidget, public KAbstractToolTipLabel
247 {
248 public:
249 QWidgetLabel() : QWidget(0, Qt::ToolTip) {}
250 void showTip(const QPoint &pos, const KToolTipItem *item);
251 void moveTip(const QPoint &pos);
252 void hideTip();
253
254 private:
255 void paintEvent(QPaintEvent*);
256 QSize sizeHint() const;
257
258 private:
259 const KToolTipItem *currentItem;
260 };
261
262 void QWidgetLabel::showTip(const QPoint &pos, const KToolTipItem *item)
263 {
264 currentItem = item;
265 move(pos);
266 show();
267 }
268
269 void QWidgetLabel::hideTip()
270 {
271 hide();
272 currentItem = 0;
273 }
274
275 void QWidgetLabel::moveTip(const QPoint &pos)
276 {
277 move(pos);
278 }
279
280 void QWidgetLabel::paintEvent(QPaintEvent*)
281 {
282 KStyleOptionToolTip option = styleOption();
283 option.rect = rect();
284
285 setMask(delegate()->shapeMask(&option));
286
287 QPainter p(this);
288 p.setFont(option.font);
289 p.setPen(QPen(option.palette.brush(QPalette::Text), 0));
290 delegate()->paint(&p, &option, currentItem);
291 }
292
293 QSize QWidgetLabel::sizeHint() const
294 {
295 if (!currentItem)
296 return QSize();
297
298 KStyleOptionToolTip option = styleOption();
299 return delegate()->sizeHint(&option, currentItem);
300 }
301
302
303
304 // ----------------------------------------------------------------------------
305
306
307
308 #ifdef Q_WS_X11
309
310 // X11 specific label that displays the tip in an ARGB window.
311 class ArgbLabel : public KAbstractToolTipLabel
312 {
313 public:
314 ArgbLabel(Visual *visual, int depth);
315 ~ArgbLabel();
316
317 void showTip(const QPoint &pos, const KToolTipItem *item);
318 void moveTip(const QPoint &pos);
319 void hideTip();
320
321 private:
322 Window window;
323 Colormap colormap;
324 Picture picture;
325 bool mapped;
326 };
327
328 ArgbLabel::ArgbLabel(Visual *visual, int depth)
329 {
330 Display *dpy = QX11Info::display();
331 Window root = QX11Info::appRootWindow();
332 colormap = XCreateColormap(dpy, QX11Info::appRootWindow(), visual, AllocNone);
333
334 XSetWindowAttributes attr;
335 attr.border_pixel = 0;
336 attr.background_pixel = 0;
337 attr.colormap = colormap;
338 attr.override_redirect = True;
339
340 window = XCreateWindow(dpy, root, 0, 0, 1, 1, 0, depth, InputOutput, visual,
341 CWBorderPixel | CWBackPixel | CWColormap |
342 CWOverrideRedirect, &attr);
343
344 // ### TODO: Set the WM hints so KWin can identify this window as a
345 // tooltip.
346
347 XRenderPictFormat *format = XRenderFindVisualFormat(dpy, visual);
348 picture = XRenderCreatePicture(dpy, window, format, 0, 0);
349
350 mapped = false;
351 }
352
353 ArgbLabel::~ArgbLabel()
354 {
355 Display *dpy = QX11Info::display();
356 XRenderFreePicture(dpy, picture);
357 XDestroyWindow(dpy, window);
358 XFreeColormap(dpy, colormap);
359 }
360
361 void ArgbLabel::showTip(const QPoint &pos, const KToolTipItem *item)
362 {
363 Display *dpy = QX11Info::display();
364 KStyleOptionToolTip option = styleOption();
365 const QSize size = delegate()->sizeHint(&option, item);
366 option.rect = QRect(QPoint(), size);
367
368 QPixmap pixmap(size);
369 pixmap.fill(Qt::transparent);
370
371 QPainter p(&pixmap);
372 p.setFont(option.font);
373 p.setPen(QPen(option.palette.brush(QPalette::Text), 0));
374 delegate()->paint(&p, &option, item);
375
376 // Resize, position and show the window.
377 XMoveResizeWindow(dpy, window, pos.x(), pos.y(), size.width(), size.height());
378
379 if (KToolTipManager::instance()->haveAlphaChannel()) {
380 const QRegion region = delegate()->inputShape(&option);
381 XShapeCombineRegion(dpy, window, ShapeInput, 0, 0, region.handle(), ShapeSet);
382 } else {
383 const QRegion region = delegate()->shapeMask(&option);
384 XShapeCombineRegion(dpy, window, ShapeBounding, 0, 0, region.handle(), ShapeSet);
385 }
386
387 XMapWindow(dpy, window);
388
389 // Blit the pixmap with the tip contents to the window.
390 // Since the window is override-redirect and an ARGB32 window,
391 // which always has an offscreen pixmap, there's no need to
392 // wait for an Expose event, or to process those.
393 XRenderComposite(dpy, PictOpSrc, pixmap.x11PictureHandle(), None,
394 picture, 0, 0, 0, 0, 0, 0, size.width(), size.height());
395
396 mapped = true;
397 }
398
399 void ArgbLabel::moveTip(const QPoint &pos)
400 {
401 if (mapped)
402 XMoveWindow(QX11Info::display(), window, pos.x(), pos.y());
403 }
404
405 void ArgbLabel::hideTip()
406 {
407 if (mapped) {
408 Display *dpy = QX11Info::display();
409 XUnmapWindow(dpy, window);
410 mapped = false;
411 }
412 }
413
414 #endif // Q_WS_X11
415
416
417
418
419 // ----------------------------------------------------------------------------
420
421
422
423
424 KToolTipManager *KToolTipManager::s_instance = 0;
425
426 KToolTipManager::KToolTipManager()
427 : label(0), currentItem(0), m_delegate(0)
428 {
429 #ifdef Q_WS_X11
430 Display *dpy = QX11Info::display();
431 int screen = DefaultScreen(dpy);
432 int depth = DefaultDepth(dpy, screen);
433 Visual *visual = DefaultVisual(dpy, screen);
434 net_wm_cm_s0 = XInternAtom(dpy, "_NET_WM_CM_S0", False);
435 haveArgbVisual = false;
436
437 int nvi;
438 XVisualInfo templ;
439 templ.screen = screen;
440 templ.depth = 32;
441 templ.c_class = TrueColor;
442 XVisualInfo *xvi = XGetVisualInfo(dpy, VisualScreenMask | VisualDepthMask |
443 VisualClassMask, &templ, &nvi);
444
445 for (int i = 0; i < nvi; ++i)
446 {
447 XRenderPictFormat *format = XRenderFindVisualFormat(dpy, xvi[i].visual);
448 if (format->type == PictTypeDirect && format->direct.alphaMask)
449 {
450 visual = xvi[i].visual;
451 depth = xvi[i].depth;
452 haveArgbVisual = true;
453 break;
454 }
455 }
456
457 if (haveArgbVisual)
458 label = new ArgbLabel(visual, depth);
459 else
460 #endif
461 label = new QWidgetLabel();
462 }
463
464 KToolTipManager::~KToolTipManager()
465 {
466 delete label;
467 delete currentItem;
468 }
469
470 void KToolTipManager::showTip(const QPoint &pos, KToolTipItem *item)
471 {
472 hideTip();
473 label->showTip(pos, item);
474 currentItem = item;
475 m_tooltipPos = pos;
476 }
477
478 void KToolTipManager::hideTip()
479 {
480 label->hideTip();
481 delete currentItem;
482 currentItem = 0;
483 }
484
485 void KToolTipManager::initStyleOption(KStyleOptionToolTip *option) const
486 {
487 option->direction = QApplication::layoutDirection();
488 option->fontMetrics = QFontMetrics(QToolTip::font());
489 option->activeCorner = KStyleOptionToolTip::TopLeftCorner;
490 option->palette = QToolTip::palette();
491 option->font = QToolTip::font();
492 option->rect = QRect();
493 option->state = QStyle::State_None;
494 option->decorationSize = QSize(32, 32);
495 }
496
497 bool KToolTipManager::haveAlphaChannel() const
498 {
499 #ifdef Q_WS_X11
500 // ### This is a synchronous call - ideally we'd use a selection
501 // watcher to avoid it.
502 return haveArgbVisual &&
503 XGetSelectionOwner(QX11Info::display(), net_wm_cm_s0) != None;
504 #else
505 return false;
506 #endif
507 }
508
509 void KToolTipManager::setDelegate(KToolTipDelegate *delegate)
510 {
511 m_delegate = delegate;
512 }
513
514 void KToolTipManager::update()
515 {
516 if (currentItem == 0)
517 return;
518 label->showTip(m_tooltipPos, currentItem);
519 }
520
521 KToolTipDelegate *KToolTipManager::delegate() const
522 {
523 return m_delegate;
524 }
525
526
527
528 // ----------------------------------------------------------------------------
529
530
531
532 namespace KToolTip
533 {
534 void showText(const QPoint &pos, const QString &text, QWidget *widget, const QRect &rect)
535 {
536 Q_UNUSED(widget)
537 Q_UNUSED(rect)
538 KToolTipItem *item = new KToolTipItem(text);
539 KToolTipManager::instance()->showTip(pos, item);
540 }
541
542 void showText(const QPoint &pos, const QString &text, QWidget *widget)
543 {
544 showText(pos, text, widget, QRect());
545 }
546
547 void showTip(const QPoint &pos, KToolTipItem *item)
548 {
549 KToolTipManager::instance()->showTip(pos, item);
550 }
551
552 void hideTip()
553 {
554 KToolTipManager::instance()->hideTip();
555 }
556
557 void setToolTipDelegate(KToolTipDelegate *delegate)
558 {
559 KToolTipManager::instance()->setDelegate(delegate);
560 }
561 }
562