1
/****************************************************************************
3
** Copyright (C) 1992-2005 Trolltech AS. All rights reserved.
5
** This file is part of the gui module of the Qt Toolkit.
7
** This file may be distributed under the terms of the Q Public License
8
** as defined by Trolltech AS of Norway and appearing in the file
9
** LICENSE.QPL included in the packaging of this file.
11
** This file may be distributed and/or modified under the terms of the
12
** GNU General Public License version 2 as published by the Free Software
13
** Foundation and appearing in the file LICENSE.GPL included in the
14
** packaging of this file.
16
** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
17
** information about Qt Commercial License Agreements.
18
** See http://www.trolltech.com/qpl/ for QPL licensing information.
19
** See http://www.trolltech.com/gpl/ for GPL licensing information.
21
** Contact info@trolltech.com if any conditions of this licensing are
24
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
25
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
27
****************************************************************************/
29
#include "qwhatsthis.h"
30
#ifndef QT_NO_WHATSTHIS
32
#include "qapplication.h"
33
#include "qdesktopwidget.h"
43
#include "qtextdocument.h"
44
#include "../text/qtextdocumentlayout_p.h"
45
#include "qtoolbutton.h"
47
#ifndef QT_NO_ACCESSIBILITY
48
#include "qaccessible.h"
51
#include "qt_windows.h"
52
#ifndef SPI_GETDROPSHADOW
53
#define SPI_GETDROPSHADOW 0x1024
57
#include "qx11info_x11.h"
63
\brief The QWhatsThis class provides a simple description of any
64
widget, i.e. answering the question "What's This?".
69
"What's This?" help is part of an application's online help
70
system, and provides users with information about the
71
functionality and usage of a particular widget. "What's This?"
72
help texts are typically longer and more detailed than \link
73
QToolTip tooltips\endlink, but generally provide less information
74
than that supplied by separate help windows.
76
QWhatsThis provides a single window with an explanatory text that
77
pops up when the user asks "What's This?". The default way for
78
users to ask the question is to move the focus to the relevant
79
widget and press Shift+F1. The help text appears immediately; it
80
goes away as soon as the user does something else.
81
(Note that if there is a shortcut for Shift+F1, this mechanism
82
will not work.) Some dialogs provide a "?" button that users can
83
click to enter "What's This?" mode; they then click the relevant
84
widget to pop up the "What's This?" window. It is also possible to
85
provide a a menu option or toolbar button to switch into "What's
88
To add "What's This?" text to a widget or an action, you simply
89
call QWidget::setWhatsThis() or QAction::setWhatsThis().
91
The text can be either rich text or plain text. If you specify a
92
rich text formatted string, it will be rendered using the default
93
stylesheet, making it possible to embed images in the displayed
94
text. To be as fast as possible, the default stylesheet uses a
95
simple method to determine whether the text can be rendered as
96
plain text. See Qt::mightBeRichText() for details.
98
\quotefile snippets/whatsthis/whatsthis.cpp
100
\printuntil setWhatsThis
102
An alternative way to enter "What's This?" mode is to call
103
createAction(), and add the returned QAction to either a menu or
104
a tool bar. By invoking this context help action (in the picture
105
below, the button with the arrow and question mark icon) the user
106
switches into "What's This?" mode. If they now click on a widget
107
the appropriate help text is shown. The mode is left when help is
108
given or when the user presses Esc.
112
You can enter "What's This?" mode programmatically with
113
enterWhatsThisMode(), check the mode with inWhatsThisMode(), and
114
return to normal mode with leaveWhatsThisMode().
116
If you want to control the "What's This?" behavior of a widget
117
manually see Qt::WA_CustomWhatsThis.
119
It is also possible to show different help texts for different
120
regions of a widget, by using a QHelpEvent of type
121
QEvent::WhatsThis. Intercept the help event in your widget's
122
QWidget::event() function and call QWhatsThis::showText() with the
123
text you want to display for the position specified in
124
QHelpEvent::pos(). If the text is rich text and the user clicks
125
on a link, the widget also receives a QWhatsThisClickedEvent with
126
the link's reference as QWhatsThisClickedEvent::href(). If a
127
QWhatsThisClickedEvent is handled (i.e. QWidget::event() returns
128
true), the help window remains visible. Call
129
QWhatsThis::hideText() to hide it explicitly.
134
class QWhatsThat : public QWidget
139
QWhatsThat(const QString& txt, QWidget* parent, QWidget *showTextFor);
142
static QWhatsThat *instance;
145
void showEvent(QShowEvent *e);
146
void mousePressEvent(QMouseEvent*);
147
void mouseReleaseEvent(QMouseEvent*);
148
void mouseMoveEvent(QMouseEvent*);
149
void keyPressEvent(QKeyEvent*);
150
void paintEvent(QPaintEvent*);
153
QPointer<QWidget>widget;
156
#ifndef QT_NO_RICHTEXT
163
QWhatsThat *QWhatsThat::instance = 0;
165
// shadowWidth not const, for XP drop-shadow-fu turns it to 0
166
static int shadowWidth = 6; // also used as '5' and '6' and even '8' below
167
static const int vMargin = 8;
168
static const int hMargin = 12;
170
QWhatsThat::QWhatsThat(const QString& txt, QWidget* parent, QWidget *showTextFor)
171
: QWidget(parent, Qt::Popup),
172
widget(showTextFor), pressed(false), text(txt)
176
setAttribute(Qt::WA_DeleteOnClose, true);
177
setAttribute(Qt::WA_NoSystemBackground, true);
178
setPalette(QToolTip::palette());
179
setMouseTracking(true);
180
setFocusPolicy(Qt::StrongFocus);
182
setCursor(Qt::ArrowCursor);
186
#ifndef QT_NO_RICHTEXT
188
if (Qt::mightBeRichText(text)) {
189
doc = new QTextDocument();
190
doc->setUndoRedoEnabled(false);
191
doc->setDefaultFont(QApplication::font(this));
193
QTextDocumentLayout *layout = qobject_cast<QTextDocumentLayout *>(doc->documentLayout());
194
layout->adjustSize();
197
r.setSize(layout->documentSize().toSize());
202
int sw = QApplication::desktop()->width() / 3;
208
r = fontMetrics().boundingRect(0, 0, sw, 1000,
209
Qt::AlignLeft + Qt::AlignTop
210
+ Qt::TextWordWrap + Qt::TextExpandTabs,
213
#if defined(Q_WS_WIN)
214
if ((QSysInfo::WindowsVersion&QSysInfo::WV_NT_based) > QSysInfo::WV_2000) {
216
SystemParametersInfo(SPI_GETDROPSHADOW, 0, &shadow, 0);
217
shadowWidth = shadow ? 0 : 6;
220
resize(r.width() + 2*hMargin + shadowWidth, r.height() + 2*vMargin + shadowWidth);
223
QWhatsThat::~QWhatsThat()
226
#ifndef QT_NO_RICHTEXT
232
void QWhatsThat::showEvent(QShowEvent *)
234
background = QPixmap::grabWindow(QApplication::desktop()->winId(),
235
x(), y(), width(), height());
238
void QWhatsThat::mousePressEvent(QMouseEvent* e)
241
if (e->button() == Qt::LeftButton && rect().contains(e->pos())) {
242
#ifndef QT_NO_RICHTEXT
244
anchor = doc->documentLayout()->anchorAt(e->pos() - QPoint(hMargin, vMargin));
251
void QWhatsThat::mouseReleaseEvent(QMouseEvent* e)
255
#ifndef QT_NO_RICHTEXT
256
if (widget && e->button() == Qt::LeftButton && doc && rect().contains(e->pos())) {
257
QString a = doc->documentLayout()->anchorAt(e->pos() - QPoint(hMargin, vMargin));
262
if (!href.isEmpty()) {
263
QWhatsThisClickedEvent e(href);
264
if (QApplication::sendEvent(widget, &e))
272
void QWhatsThat::mouseMoveEvent(QMouseEvent* e)
274
#ifndef QT_NO_RICHTEXT
278
QString a = doc->documentLayout()->anchorAt(e->pos() - QPoint(hMargin, vMargin));
280
setCursor(Qt::PointingHandCursor);
282
setCursor(Qt::ArrowCursor);
287
void QWhatsThat::keyPressEvent(QKeyEvent*)
292
void QWhatsThat::paintEvent(QPaintEvent*)
294
bool drawShadow = true;
295
#if defined(Q_WS_WIN)
296
if ((QSysInfo::WindowsVersion&QSysInfo::WV_NT_based) > QSysInfo::WV_2000) {
298
SystemParametersInfo(SPI_GETDROPSHADOW, 0, &shadow, 0);
299
drawShadow = !shadow;
301
#elif defined(Q_WS_MAC)
302
drawShadow = false; // never draw it on OS X we get it for free
307
r.adjust(0, 0, -shadowWidth, -shadowWidth);
309
p.drawPixmap(0, 0, background);
310
p.setPen(palette().foreground().color());
312
p.setPen(palette().mid().color());
313
p.setBrush(palette().brush(QPalette::Background));
316
p.drawRect(1, 1, w-2, h-2);
318
p.setPen(palette().shadow().color());
319
p.drawPoint(w + 5, 6);
320
p.drawLine(w + 3, 6, w + 5, 8);
321
p.drawLine(w + 1, 6, w + 5, 10);
323
for(i=7; i < h; i += 2)
324
p.drawLine(w, i, w + 5, i + 5);
325
for(i = w - i + h; i > 6; i -= 2)
326
p.drawLine(i, h, i + 5, h + 5);
327
for(; i > 0 ; i -= 2)
328
p.drawLine(6, h + 6 - i, i + 5, h + 5);
330
p.setPen(palette().foreground().color());
331
r.adjust(hMargin, vMargin, -hMargin, -vMargin);
333
#ifndef QT_NO_RICHTEXT
335
p.translate(r.x(), r.y());
337
rect.translate(-r.x(), -r.y());
339
QAbstractTextDocumentLayout::PaintContext context;
340
doc->documentLayout()->draw(&p, context);
345
p.drawText(r, Qt::AlignLeft + Qt::AlignTop + Qt::TextWordWrap + Qt::TextExpandTabs, text);
349
static const char * const button_image[] = {
371
class QWhatsThisPrivate : public QObject
375
~QWhatsThisPrivate();
376
static QWhatsThisPrivate *instance;
377
bool eventFilter(QObject *, QEvent *);
378
QPointer<QAction> action;
380
QPointer<QToolButton> button;
382
static void say(QWidget *, const QString &, int x = 0, int y = 0);
383
static void notifyToplevels(QEvent *e);
386
void QWhatsThisPrivate::notifyToplevels(QEvent *e)
388
QWidgetList toplevels = QApplication::topLevelWidgets();
389
for (int i = 0; i < toplevels.count(); ++i) {
390
register QWidget *w = toplevels.at(i);
391
QApplication::sendEvent(w, e);
395
QWhatsThisPrivate *QWhatsThisPrivate::instance = 0;
397
QWhatsThisPrivate::QWhatsThisPrivate()
400
qApp->installEventFilter(this);
402
QPoint pos = QCursor::pos();
403
if (QWidget *w = QApplication::widgetAt(pos)) {
404
QHelpEvent e(QEvent::QueryWhatsThis, w->mapFromGlobal(pos), pos);
405
QApplication::setOverrideCursor((!QApplication::sendEvent(w, &e) || !e.isAccepted())?
406
Qt::ForbiddenCursor:Qt::WhatsThisCursor);
408
QApplication::setOverrideCursor(Qt::WhatsThisCursor);
410
#ifndef QT_NO_ACCESSIBILITY
411
QAccessible::updateAccessibility(this, 0, QAccessible::ContextHelpStart);
415
QWhatsThisPrivate::~QWhatsThisPrivate()
418
action->setChecked(false);
421
button->setChecked(false);
423
QApplication::restoreOverrideCursor();
424
#ifndef QT_NO_ACCESSIBILITY
425
QAccessible::updateAccessibility(this, 0, QAccessible::ContextHelpEnd);
430
bool QWhatsThisPrivate::eventFilter(QObject *o, QEvent *e)
432
if (!o->isWidgetType())
434
QWidget * w = static_cast<QWidget *>(o);
435
bool customWhatsThis = w->testAttribute(Qt::WA_CustomWhatsThis);
437
case QEvent::MouseButtonPress:
439
QMouseEvent *me = static_cast<QMouseEvent*>(e);
440
if (me->button() == Qt::RightButton || customWhatsThis)
442
QHelpEvent e(QEvent::WhatsThis, me->pos(), me->globalPos());
443
if (!QApplication::sendEvent(w, &e) || !e.isAccepted())
444
QWhatsThis::leaveWhatsThisMode();
448
case QEvent::MouseMove:
450
QMouseEvent *me = static_cast<QMouseEvent*>(e);
451
QHelpEvent e(QEvent::QueryWhatsThis, me->pos(), me->globalPos());
452
QApplication::changeOverrideCursor((!QApplication::sendEvent(w, &e) || !e.isAccepted())?
453
Qt::ForbiddenCursor:Qt::WhatsThisCursor);
456
case QEvent::MouseButtonRelease:
457
case QEvent::MouseButtonDblClick:
458
if (static_cast<QMouseEvent*>(e)->button() == Qt::RightButton || customWhatsThis)
459
return false; // ignore RMB release
461
case QEvent::KeyPress:
463
QKeyEvent* kev = (QKeyEvent*)e;
465
if (kev->key() == Qt::Key_Escape) {
466
QWhatsThis::leaveWhatsThisMode();
468
} else if (customWhatsThis) {
470
} else if (kev->key() == Qt::Key_Menu ||
471
(kev->key() == Qt::Key_F10 &&
472
kev->modifiers() == Qt::ShiftModifier)) {
473
// we don't react to these keys, they are used for context menus
475
} else if (kev->key() != Qt::Key_Shift && kev->key() != Qt::Key_Alt // not a modifier key
476
&& kev->key() != Qt::Key_Control && kev->key() != Qt::Key_Meta) {
477
QWhatsThis::leaveWhatsThisMode();
486
class QWhatsThisAction: public QAction
491
explicit QWhatsThisAction(QObject* parent = 0);
494
void actionTriggered();
497
QWhatsThisAction::QWhatsThisAction(QObject *parent) : QAction(tr("What's This?"), parent)
499
QPixmap p((const char**)button_image);
502
connect(this, SIGNAL(triggered()), this, SLOT(actionTriggered()));
503
setShortcut(Qt::ShiftModifier + Qt::Key_F1);
506
void QWhatsThisAction::actionTriggered()
509
QWhatsThis::enterWhatsThisMode();
510
QWhatsThisPrivate::instance->action = this;
514
QWhatsThis::QWhatsThis()
522
Sets the What's This text \a s for the widget \a w.
524
Use QWidget::setWhatsThis() or QAction::setWhatsThis() instead.
526
void QWhatsThis::add(QWidget *w, const QString &s)
534
Remove's the What's This text for the widget \a w.
536
Use QWidget::setWhatsThis() or QAction::setWhatsThis() instead.
538
void QWhatsThis::remove(QWidget *w)
540
w->setWhatsThis(QString());
543
class QWhatsThisButton : public QToolButton
547
QWhatsThisButton(QWidget *p) : QToolButton(p) {
549
QPixmap pix( const_cast<const char**>(button_image) );
551
QObject::connect(this, SIGNAL(toggled(bool)), this, SLOT(whatToggled(bool)));
553
setFocusPolicy(Qt::NoFocus);
557
void whatToggled(bool b) {
559
QWhatsThis::enterWhatsThisMode();
560
QWhatsThisPrivate::instance->button = this;
566
Returns a new "What's This?" QToolButton with the given \a
567
parent. To do this now, create your own QToolButton and a
568
QWhatsThis object and call the QWhatsThis object's showText()
569
function when the QToolButton is invoked.
571
Use createAction() instead.
573
QToolButton * QWhatsThis::whatsThisButton(QWidget * parent)
575
return new QWhatsThisButton(parent);
580
This function switches the user interface into "What's This?"
581
mode. The user interface can be switched back into normal mode by
582
the user (e.g. by them clicking or pressing Esc), or
583
programmatically by calling leaveWhatsThisMode().
585
When entering "What's This?" mode, a QEvent of type
586
Qt::EnterWhatsThisMode is sent to all toplevel widgets.
588
\sa inWhatsThisMode() leaveWhatsThisMode()
590
void QWhatsThis::enterWhatsThisMode()
592
if (QWhatsThisPrivate::instance)
594
(void) new QWhatsThisPrivate;
595
QEvent e(QEvent::EnterWhatsThisMode);
596
QWhatsThisPrivate::notifyToplevels(&e);
600
Returns true if the user interface is in "What's This?" mode;
601
otherwise returns false.
603
\sa enterWhatsThisMode()
605
bool QWhatsThis::inWhatsThisMode()
607
return (QWhatsThisPrivate::instance != 0);
611
If the user interface is in "What's This?" mode, this function
612
switches back to normal mode; otherwise it does nothing.
614
When leaving "What's This?" mode, a QEvent of type
615
Qt::LeaveWhatsThisMode is sent to all toplevel widgets.
617
\sa enterWhatsThisMode() inWhatsThisMode()
619
void QWhatsThis::leaveWhatsThisMode()
621
delete QWhatsThisPrivate::instance;
622
QEvent e(QEvent::LeaveWhatsThisMode);
623
QWhatsThisPrivate::notifyToplevels(&e);
626
void QWhatsThisPrivate::say(QWidget * widget, const QString &text, int x, int y)
628
if (text.size() == 0)
630
// make a fresh widget, and set it up
631
QWhatsThat *whatsThat = new QWhatsThat(
633
#if defined(Q_WS_X11)
634
QApplication::desktop()->screen(widget ? widget->x11Info().screen() : QCursor::x11Screen()),
642
// okay, now to find a suitable location
645
QApplication::desktop()->screenNumber(widget) :
646
#if defined(Q_WS_X11)
649
QApplication::desktop()->screenNumber(QPoint(x,y))
652
QRect screen = QApplication::desktop()->screenGeometry(scr);
654
int w = whatsThat->width();
655
int h = whatsThat->height();
659
// first try locating the widget immediately above/below,
660
// with nice alignment if possible.
663
pos = widget->mapToGlobal(QPoint(0,0));
665
if (widget && w > widget->width() + 16)
666
x = pos.x() + widget->width()/2 - w/2;
670
// squeeze it in if that would result in part of what's this
671
// being only partially visible
672
if (x + w + shadowWidth > sx+screen.width())
673
x = (widget? (qMin(screen.width(),
674
pos.x() + widget->width())
681
if (widget && h > widget->height() + 16) {
682
y = pos.y() + widget->height() + 2; // below, two pixels spacing
683
// what's this is above or below, wherever there's most space
684
if (y + h + 10 > sy+screen.height())
685
y = pos.y() + 2 - shadowWidth - h; // above, overlap
689
// squeeze it in if that would result in part of what's this
690
// being only partially visible
691
if (y + h + shadowWidth > sy+screen.height())
692
y = (widget ? (qMin(screen.height(),
693
pos.y() + widget->height())
699
whatsThat->move(x, y);
701
whatsThat->grabKeyboard();
705
Shows \a text as a "What's This?" window, at global position \a
706
pos. The optional widget argument, \a w, is used to determine the
707
appropriate screen on multi-head systems.
711
void QWhatsThis::showText(const QPoint &pos, const QString &text, QWidget *w)
713
leaveWhatsThisMode();
714
QWhatsThisPrivate::say(w, text, pos.x(), pos.y());
718
If a "What's This?" window is showing, this destroys it.
722
void QWhatsThis::hideText()
724
delete QWhatsThat::instance;
728
Returns a ready-made QAction, used to invoke "What's This?" context
729
help, with the given \a parent.
731
The returned QAction provides a convenient way to let users enter
734
QAction *QWhatsThis::createAction(QObject *parent)
736
return new QWhatsThisAction(parent);
739
#include "qwhatsthis.moc"