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