1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtWidgets module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qwhatsthis.h"
41#include "qpointer.h"
42#include "qapplication.h"
43#include <private/qguiapplication_p.h>
44#include "qdesktopwidget.h"
45#include <private/qdesktopwidget_p.h>
46#include "qevent.h"
47#include "qpixmap.h"
48#include "qscreen.h"
49#include "qpainter.h"
50#include "qtimer.h"
51#if QT_CONFIG(action)
52#include "qaction.h"
53#endif // QT_CONFIG(action)
54#include "qcursor.h"
55#include "qbitmap.h"
56#include "qtextdocument.h"
57#include <qpa/qplatformtheme.h>
58#include "private/qtextdocumentlayout_p.h"
59#include "qdebug.h"
60#ifndef QT_NO_ACCESSIBILITY
61#include "qaccessible.h"
62#endif
63
64QT_BEGIN_NAMESPACE
65
66/*!
67 \class QWhatsThis
68 \brief The QWhatsThis class provides a simple description of any
69 widget, i.e. answering the question "What's This?".
70
71 \ingroup helpsystem
72 \inmodule QtWidgets
73
74 "What's This?" help is part of an application's online help
75 system, and provides users with information about the
76 functionality and usage of a particular widget. "What's This?"
77 help texts are typically longer and more detailed than
78 \l{QToolTip}{tooltips}, but generally provide less information
79 than that supplied by separate help windows.
80
81 QWhatsThis provides a single window with an explanatory text that
82 pops up when the user asks "What's This?". The default way for
83 users to ask the question is to move the focus to the relevant
84 widget and press Shift+F1. The help text appears immediately; it
85 goes away as soon as the user does something else.
86 (Note that if there is a shortcut for Shift+F1, this mechanism
87 will not work.) Some dialogs provide a "?" button that users can
88 click to enter "What's This?" mode; they then click the relevant
89 widget to pop up the "What's This?" window. It is also possible to
90 provide a a menu option or toolbar button to switch into "What's
91 This?" mode.
92
93 To add "What's This?" text to a widget or an action, you simply
94 call QWidget::setWhatsThis() or QAction::setWhatsThis().
95
96 The text can be either rich text or plain text. If you specify a
97 rich text formatted string, it will be rendered using the default
98 stylesheet, making it possible to embed images in the displayed
99 text. To be as fast as possible, the default stylesheet uses a
100 simple method to determine whether the text can be rendered as
101 plain text. See Qt::mightBeRichText() for details.
102
103 \snippet whatsthis/whatsthis.cpp 0
104
105 An alternative way to enter "What's This?" mode is to call
106 createAction(), and add the returned QAction to either a menu or
107 a tool bar. By invoking this context help action (in the picture
108 below, the button with the arrow and question mark icon) the user
109 switches into "What's This?" mode. If they now click on a widget
110 the appropriate help text is shown. The mode is left when help is
111 given or when the user presses Esc.
112
113 \image whatsthis.png
114
115 You can enter "What's This?" mode programmatically with
116 enterWhatsThisMode(), check the mode with inWhatsThisMode(), and
117 return to normal mode with leaveWhatsThisMode().
118
119 If you want to control the "What's This?" behavior of a widget
120 manually see Qt::WA_CustomWhatsThis.
121
122 It is also possible to show different help texts for different
123 regions of a widget, by using a QHelpEvent of type
124 QEvent::WhatsThis. Intercept the help event in your widget's
125 QWidget::event() function and call QWhatsThis::showText() with the
126 text you want to display for the position specified in
127 QHelpEvent::pos(). If the text is rich text and the user clicks
128 on a link, the widget also receives a QWhatsThisClickedEvent with
129 the link's reference as QWhatsThisClickedEvent::href(). If a
130 QWhatsThisClickedEvent is handled (i.e. QWidget::event() returns
131 true), the help window remains visible. Call
132 QWhatsThis::hideText() to hide it explicitly.
133
134 \sa QToolTip
135*/
136
137Q_CORE_EXPORT void qDeleteInEventHandler(QObject *o);
138
139class QWhatsThat : public QWidget
140{
141 Q_OBJECT
142
143public:
144 QWhatsThat(const QString& txt, QWidget* parent, QWidget *showTextFor);
145 ~QWhatsThat() ;
146
147 static QWhatsThat *instance;
148
149protected:
150 void showEvent(QShowEvent *e) override;
151 void mousePressEvent(QMouseEvent*) override;
152 void mouseReleaseEvent(QMouseEvent*) override;
153 void mouseMoveEvent(QMouseEvent*) override;
154 void keyPressEvent(QKeyEvent*) override;
155 void paintEvent(QPaintEvent*) override;
156
157private:
158 QPointer<QWidget>widget;
159 bool pressed;
160 QString text;
161 QTextDocument* doc;
162 QString anchor;
163 QPixmap background;
164};
165
166QWhatsThat *QWhatsThat::instance = nullptr;
167
168// shadowWidth not const, for XP drop-shadow-fu turns it to 0
169static int shadowWidth = 6; // also used as '5' and '6' and even '8' below
170static const int vMargin = 8;
171static const int hMargin = 12;
172
173static inline bool dropShadow()
174{
175 if (const QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme())
176 return theme->themeHint(hint: QPlatformTheme::DropShadow).toBool();
177 return false;
178}
179
180QWhatsThat::QWhatsThat(const QString& txt, QWidget* parent, QWidget *showTextFor)
181 : QWidget(parent, Qt::Popup),
182 widget(showTextFor), pressed(false), text(txt)
183{
184 delete instance;
185 instance = this;
186 setAttribute(Qt::WA_DeleteOnClose, on: true);
187 setAttribute(Qt::WA_NoSystemBackground, on: true);
188 if (parent)
189 setPalette(parent->palette());
190 setMouseTracking(true);
191 setFocusPolicy(Qt::StrongFocus);
192#ifndef QT_NO_CURSOR
193 setCursor(Qt::ArrowCursor);
194#endif
195 QRect r;
196 doc = nullptr;
197 ensurePolished(); // Ensures style sheet font before size calc
198 if (Qt::mightBeRichText(text)) {
199 doc = new QTextDocument();
200 doc->setUndoRedoEnabled(false);
201 doc->setDefaultFont(QApplication::font(this));
202#ifdef QT_NO_TEXTHTMLPARSER
203 doc->setPlainText(text);
204#else
205 doc->setHtml(text);
206#endif
207 doc->setUndoRedoEnabled(false);
208 doc->adjustSize();
209 r.setTop(0);
210 r.setLeft(0);
211 r.setSize(doc->size().toSize());
212 }
213 else
214 {
215 int sw = QDesktopWidgetPrivate::width() / 3;
216 if (sw < 200)
217 sw = 200;
218 else if (sw > 300)
219 sw = 300;
220
221 r = fontMetrics().boundingRect(x: 0, y: 0, w: sw, h: 1000,
222 flags: Qt::AlignLeft + Qt::AlignTop
223 + Qt::TextWordWrap + Qt::TextExpandTabs,
224 text);
225 }
226 shadowWidth = dropShadow() ? 0 : 6;
227 resize(w: r.width() + 2*hMargin + shadowWidth, h: r.height() + 2*vMargin + shadowWidth);
228}
229
230QWhatsThat::~QWhatsThat()
231{
232 instance = nullptr;
233 if (doc)
234 delete doc;
235}
236
237void QWhatsThat::showEvent(QShowEvent *)
238{
239 background = QGuiApplication::primaryScreen()->grabWindow(window: QApplication::desktop()->internalWinId(),
240 x: x(), y: y(), w: width(), h: height());
241}
242
243void QWhatsThat::mousePressEvent(QMouseEvent* e)
244{
245 pressed = true;
246 if (e->button() == Qt::LeftButton && rect().contains(p: e->pos())) {
247 if (doc)
248 anchor = doc->documentLayout()->anchorAt(pos: e->pos() - QPoint(hMargin, vMargin));
249 return;
250 }
251 close();
252}
253
254void QWhatsThat::mouseReleaseEvent(QMouseEvent* e)
255{
256 if (!pressed)
257 return;
258 if (widget && e->button() == Qt::LeftButton && doc && rect().contains(p: e->pos())) {
259 QString a = doc->documentLayout()->anchorAt(pos: e->pos() - QPoint(hMargin, vMargin));
260 QString href;
261 if (anchor == a)
262 href = a;
263 anchor.clear();
264 if (!href.isEmpty()) {
265 QWhatsThisClickedEvent e(href);
266 if (QCoreApplication::sendEvent(receiver: widget, event: &e))
267 return;
268 }
269 }
270 close();
271}
272
273void QWhatsThat::mouseMoveEvent(QMouseEvent* e)
274{
275#ifdef QT_NO_CURSOR
276 Q_UNUSED(e);
277#else
278 if (!doc)
279 return;
280 QString a = doc->documentLayout()->anchorAt(pos: e->pos() - QPoint(hMargin, vMargin));
281 if (!a.isEmpty())
282 setCursor(Qt::PointingHandCursor);
283 else
284 setCursor(Qt::ArrowCursor);
285#endif
286}
287
288void QWhatsThat::keyPressEvent(QKeyEvent*)
289{
290 close();
291}
292
293void QWhatsThat::paintEvent(QPaintEvent*)
294{
295 const bool drawShadow = dropShadow();
296
297 QRect r = rect();
298 r.adjust(dx1: 0, dy1: 0, dx2: -1, dy2: -1);
299 if (drawShadow)
300 r.adjust(dx1: 0, dy1: 0, dx2: -shadowWidth, dy2: -shadowWidth);
301 QPainter p(this);
302 p.drawPixmap(x: 0, y: 0, pm: background);
303 p.setPen(QPen(palette().toolTipText(), 0));
304 p.setBrush(palette().toolTipBase());
305 p.drawRect(r);
306 int w = r.width();
307 int h = r.height();
308 p.setPen(palette().brush(cr: QPalette::Dark).color());
309 p.drawRect(x: 1, y: 1, w: w-2, h: h-2);
310 if (drawShadow) {
311 p.setPen(palette().shadow().color());
312 p.drawPoint(x: w + 5, y: 6);
313 p.drawLine(x1: w + 3, y1: 6, x2: w + 5, y2: 8);
314 p.drawLine(x1: w + 1, y1: 6, x2: w + 5, y2: 10);
315 int i;
316 for(i=7; i < h; i += 2)
317 p.drawLine(x1: w, y1: i, x2: w + 5, y2: i + 5);
318 for(i = w - i + h; i > 6; i -= 2)
319 p.drawLine(x1: i, y1: h, x2: i + 5, y2: h + 5);
320 for(; i > 0 ; i -= 2)
321 p.drawLine(x1: 6, y1: h + 6 - i, x2: i + 5, y2: h + 5);
322 }
323 r.adjust(dx1: 0, dy1: 0, dx2: 1, dy2: 1);
324 p.setPen(palette().toolTipText().color());
325 r.adjust(dx1: hMargin, dy1: vMargin, dx2: -hMargin, dy2: -vMargin);
326
327 if (doc) {
328 p.translate(dx: r.x(), dy: r.y());
329 QRect rect = r;
330 rect.translate(dx: -r.x(), dy: -r.y());
331 p.setClipRect(rect);
332 QAbstractTextDocumentLayout::PaintContext context;
333 context.palette.setBrush(acr: QPalette::Text, abrush: context.palette.toolTipText());
334 doc->documentLayout()->draw(painter: &p, context);
335 }
336 else
337 {
338 p.drawText(r, flags: Qt::AlignLeft + Qt::AlignTop + Qt::TextWordWrap + Qt::TextExpandTabs, text);
339 }
340}
341
342static const char * const button_image[] = {
343"16 16 3 1",
344" c None",
345"o c #000000",
346"a c #000080",
347"o aaaaa ",
348"oo aaa aaa ",
349"ooo aaa aaa",
350"oooo aa aa",
351"ooooo aa aa",
352"oooooo a aaa",
353"ooooooo aaa ",
354"oooooooo aaa ",
355"ooooooooo aaa ",
356"ooooo aaa ",
357"oo ooo ",
358"o ooo aaa ",
359" ooo aaa ",
360" ooo ",
361" ooo ",
362" ooo "};
363
364class QWhatsThisPrivate : public QObject
365{
366 public:
367 QWhatsThisPrivate();
368 ~QWhatsThisPrivate();
369 static QWhatsThisPrivate *instance;
370 bool eventFilter(QObject *, QEvent *) override;
371#if QT_CONFIG(action)
372 QPointer<QAction> action;
373#endif // QT_CONFIG(action)
374 static void say(QWidget *, const QString &, int x = 0, int y = 0);
375 static void notifyToplevels(QEvent *e);
376 bool leaveOnMouseRelease;
377};
378
379void QWhatsThisPrivate::notifyToplevels(QEvent *e)
380{
381 const QWidgetList toplevels = QApplication::topLevelWidgets();
382 for (auto *w : toplevels)
383 QCoreApplication::sendEvent(receiver: w, event: e);
384}
385
386QWhatsThisPrivate *QWhatsThisPrivate::instance = nullptr;
387
388QWhatsThisPrivate::QWhatsThisPrivate()
389 : leaveOnMouseRelease(false)
390{
391 instance = this;
392 qApp->installEventFilter(filterObj: this);
393
394 QPoint pos = QCursor::pos();
395 if (QWidget *w = QApplication::widgetAt(p: pos)) {
396 QHelpEvent e(QEvent::QueryWhatsThis, w->mapFromGlobal(pos), pos);
397 const bool sentEvent = QCoreApplication::sendEvent(receiver: w, event: &e);
398#ifdef QT_NO_CURSOR
399 Q_UNUSED(sentEvent);
400#else
401 QGuiApplication::setOverrideCursor((!sentEvent || !e.isAccepted())?
402 Qt::ForbiddenCursor:Qt::WhatsThisCursor);
403 } else {
404 QGuiApplication::setOverrideCursor(Qt::WhatsThisCursor);
405#endif
406 }
407#ifndef QT_NO_ACCESSIBILITY
408 QAccessibleEvent event(this, QAccessible::ContextHelpStart);
409 QAccessible::updateAccessibility(event: &event);
410#endif
411}
412
413QWhatsThisPrivate::~QWhatsThisPrivate()
414{
415#if QT_CONFIG(action)
416 if (action)
417 action->setChecked(false);
418#endif // QT_CONFIG(action)
419#ifndef QT_NO_CURSOR
420 QGuiApplication::restoreOverrideCursor();
421#endif
422#ifndef QT_NO_ACCESSIBILITY
423 QAccessibleEvent event(this, QAccessible::ContextHelpEnd);
424 QAccessible::updateAccessibility(event: &event);
425#endif
426 instance = nullptr;
427}
428
429bool QWhatsThisPrivate::eventFilter(QObject *o, QEvent *e)
430{
431 if (!o->isWidgetType())
432 return false;
433 QWidget * w = static_cast<QWidget *>(o);
434 bool customWhatsThis = w->testAttribute(attribute: Qt::WA_CustomWhatsThis);
435 switch (e->type()) {
436 case QEvent::MouseButtonPress:
437 {
438 QMouseEvent *me = static_cast<QMouseEvent*>(e);
439 if (me->button() == Qt::RightButton || customWhatsThis)
440 return false;
441 QHelpEvent e(QEvent::WhatsThis, me->pos(), me->globalPos());
442 if (!QCoreApplication::sendEvent(receiver: w, event: &e) || !e.isAccepted())
443 leaveOnMouseRelease = true;
444
445 } break;
446
447 case QEvent::MouseMove:
448 {
449 QMouseEvent *me = static_cast<QMouseEvent*>(e);
450 QHelpEvent e(QEvent::QueryWhatsThis, me->pos(), me->globalPos());
451 const bool sentEvent = QCoreApplication::sendEvent(receiver: w, event: &e);
452#ifdef QT_NO_CURSOR
453 Q_UNUSED(sentEvent);
454#else
455 QGuiApplication::changeOverrideCursor((!sentEvent || !e.isAccepted())?
456 Qt::ForbiddenCursor:Qt::WhatsThisCursor);
457#endif
458 Q_FALLTHROUGH();
459 }
460 case QEvent::MouseButtonRelease:
461 case QEvent::MouseButtonDblClick:
462 if (leaveOnMouseRelease && e->type() == QEvent::MouseButtonRelease)
463 QWhatsThis::leaveWhatsThisMode();
464 if (static_cast<QMouseEvent*>(e)->button() == Qt::RightButton || customWhatsThis)
465 return false; // ignore RMB release
466 break;
467 case QEvent::KeyPress:
468 {
469 QKeyEvent* kev = (QKeyEvent*)e;
470#if QT_CONFIG(shortcut)
471 if (kev->matches(key: QKeySequence::Cancel)) {
472 QWhatsThis::leaveWhatsThisMode();
473 return true;
474 } else
475#endif
476 if (customWhatsThis) {
477 return false;
478 } else if (kev->key() == Qt::Key_Menu ||
479 (kev->key() == Qt::Key_F10 &&
480 kev->modifiers() == Qt::ShiftModifier)) {
481 // we don't react to these keys, they are used for context menus
482 return false;
483 } else if (kev->key() != Qt::Key_Shift && kev->key() != Qt::Key_Alt // not a modifier key
484 && kev->key() != Qt::Key_Control && kev->key() != Qt::Key_Meta) {
485 QWhatsThis::leaveWhatsThisMode();
486 }
487 } break;
488 default:
489 return false;
490 }
491 return true;
492}
493
494#if QT_CONFIG(action)
495class QWhatsThisAction: public QAction
496{
497 Q_OBJECT
498
499public:
500 explicit QWhatsThisAction(QObject* parent = nullptr);
501
502private slots:
503 void actionTriggered();
504};
505
506QWhatsThisAction::QWhatsThisAction(QObject *parent) : QAction(tr(s: "What's This?"), parent)
507{
508#ifndef QT_NO_IMAGEFORMAT_XPM
509 QPixmap p(button_image);
510 setIcon(p);
511#endif
512 setCheckable(true);
513 connect(sender: this, SIGNAL(triggered()), receiver: this, SLOT(actionTriggered()));
514#ifndef QT_NO_SHORTCUT
515 setShortcut(Qt::ShiftModifier + Qt::Key_F1);
516#endif
517}
518
519void QWhatsThisAction::actionTriggered()
520{
521 if (isChecked()) {
522 QWhatsThis::enterWhatsThisMode();
523 QWhatsThisPrivate::instance->action = this;
524 }
525}
526#endif // QT_CONFIG(action)
527
528/*!
529 This function switches the user interface into "What's This?"
530 mode. The user interface can be switched back into normal mode by
531 the user (e.g. by them clicking or pressing Esc), or
532 programmatically by calling leaveWhatsThisMode().
533
534 When entering "What's This?" mode, a QEvent of type
535 Qt::EnterWhatsThisMode is sent to all toplevel widgets.
536
537 \sa inWhatsThisMode(), leaveWhatsThisMode()
538*/
539void QWhatsThis::enterWhatsThisMode()
540{
541 if (QWhatsThisPrivate::instance)
542 return;
543 (void) new QWhatsThisPrivate;
544 QEvent e(QEvent::EnterWhatsThisMode);
545 QWhatsThisPrivate::notifyToplevels(e: &e);
546 }
547
548/*!
549 Returns \c true if the user interface is in "What's This?" mode;
550 otherwise returns \c false.
551
552 \sa enterWhatsThisMode()
553*/
554bool QWhatsThis::inWhatsThisMode()
555{
556 return (QWhatsThisPrivate::instance != nullptr);
557}
558
559/*!
560 If the user interface is in "What's This?" mode, this function
561 switches back to normal mode; otherwise it does nothing.
562
563 When leaving "What's This?" mode, a QEvent of type
564 Qt::LeaveWhatsThisMode is sent to all toplevel widgets.
565
566 \sa enterWhatsThisMode(), inWhatsThisMode()
567*/
568void QWhatsThis::leaveWhatsThisMode()
569{
570 delete QWhatsThisPrivate::instance;
571 QEvent e(QEvent::LeaveWhatsThisMode);
572 QWhatsThisPrivate::notifyToplevels(e: &e);
573}
574
575void QWhatsThisPrivate::say(QWidget * widget, const QString &text, int x, int y)
576{
577 if (text.size() == 0)
578 return;
579 // make a fresh widget, and set it up
580 QWhatsThat *whatsThat = new QWhatsThat(text, nullptr, widget);
581
582 // okay, now to find a suitable location
583 int scr = (widget ?
584 QDesktopWidgetPrivate::screenNumber(widget) :
585 QDesktopWidgetPrivate::screenNumber(QPoint(x,y))
586 );
587 QRect screen = QDesktopWidgetPrivate::screenGeometry(screen: scr);
588
589 int w = whatsThat->width();
590 int h = whatsThat->height();
591 int sx = screen.x();
592 int sy = screen.y();
593
594 // first try locating the widget immediately above/below,
595 // with nice alignment if possible.
596 QPoint pos;
597 if (widget)
598 pos = widget->mapToGlobal(QPoint(0,0));
599
600 if (widget && w > widget->width() + 16)
601 x = pos.x() + widget->width()/2 - w/2;
602 else
603 x = x - w/2;
604
605 // squeeze it in if that would result in part of what's this
606 // being only partially visible
607 if (x + w + shadowWidth > sx+screen.width())
608 x = (widget? (qMin(a: screen.width(),
609 b: pos.x() + widget->width())
610 ) : screen.width())
611 - w;
612
613 if (x < sx)
614 x = sx;
615
616 if (widget && h > widget->height() + 16) {
617 y = pos.y() + widget->height() + 2; // below, two pixels spacing
618 // what's this is above or below, wherever there's most space
619 if (y + h + 10 > sy+screen.height())
620 y = pos.y() + 2 - shadowWidth - h; // above, overlap
621 }
622 y = y + 2;
623
624 // squeeze it in if that would result in part of what's this
625 // being only partially visible
626 if (y + h + shadowWidth > sy+screen.height())
627 y = (widget ? (qMin(a: screen.height(),
628 b: pos.y() + widget->height())
629 ) : screen.height())
630 - h;
631 if (y < sy)
632 y = sy;
633
634 whatsThat->move(ax: x, ay: y);
635 whatsThat->show();
636 whatsThat->grabKeyboard();
637}
638
639/*!
640 Shows \a text as a "What's This?" window, at global position \a
641 pos. The optional widget argument, \a w, is used to determine the
642 appropriate screen on multi-head systems.
643
644 \sa hideText()
645*/
646void QWhatsThis::showText(const QPoint &pos, const QString &text, QWidget *w)
647{
648 leaveWhatsThisMode();
649 QWhatsThisPrivate::say(widget: w, text, x: pos.x(), y: pos.y());
650}
651
652/*!
653 If a "What's This?" window is showing, this destroys it.
654
655 \sa showText()
656*/
657void QWhatsThis::hideText()
658{
659 qDeleteInEventHandler(o: QWhatsThat::instance);
660}
661
662/*!
663 Returns a ready-made QAction, used to invoke "What's This?" context
664 help, with the given \a parent.
665
666 The returned QAction provides a convenient way to let users enter
667 "What's This?" mode.
668*/
669#if QT_CONFIG(action)
670QAction *QWhatsThis::createAction(QObject *parent)
671{
672 return new QWhatsThisAction(parent);
673}
674#endif // QT_CONFIG(action)
675
676QT_END_NAMESPACE
677
678#include "qwhatsthis.moc"
679

source code of qtbase/src/widgets/kernel/qwhatsthis.cpp