VirtualBox

source: vbox/trunk/src/VBox/Frontends/VirtualBox/src/helpbrowser/UIHelpViewer.cpp

Last change on this file was 104358, checked in by vboxsync, 5 weeks ago

FE/Qt. bugref:10622. More refactoring around the retranslation functionality.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 34.5 KB
Line 
1/* $Id: UIHelpViewer.cpp 104358 2024-04-18 05:33:40Z vboxsync $ */
2/** @file
3 * VBox Qt GUI - UIHelpViewer class implementation.
4 */
5
6/*
7 * Copyright (C) 2010-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28/* Qt includes: */
29#include <QApplication>
30#include <QClipboard>
31#include <QtGlobal>
32#include <QtHelp/QHelpEngine>
33#include <QtHelp/QHelpContentWidget>
34#include <QtHelp/QHelpIndexWidget>
35#include <QtHelp/QHelpSearchEngine>
36#include <QtHelp/QHelpSearchQueryWidget>
37#include <QtHelp/QHelpSearchResultWidget>
38#include <QLabel>
39#include <QMenu>
40#include <QHBoxLayout>
41#include <QGraphicsBlurEffect>
42#include <QLabel>
43#include <QMimeDatabase>
44#include <QPainter>
45#include <QScrollBar>
46#include <QTextBlock>
47#include <QWidgetAction>
48#ifdef RT_OS_SOLARIS
49# include <QFontDatabase>
50#endif
51
52/* GUI includes: */
53#include "QIToolButton.h"
54#include "UICommon.h"
55#include "UIHelpViewer.h"
56#include "UIHelpBrowserWidget.h"
57#include "UIIconPool.h"
58#include "UISearchLineEdit.h"
59
60/* COM includes: */
61#include "CSystemProperties.h"
62
63
64/*********************************************************************************************************************************
65* UIContextMenuNavigationAction definition. *
66*********************************************************************************************************************************/
67class UIContextMenuNavigationAction : public QWidgetAction
68{
69
70 Q_OBJECT;
71
72signals:
73
74 void sigGoBackward();
75 void sigGoForward();
76 void sigGoHome();
77 void sigReloadPage();
78 void sigAddBookmark();
79
80public:
81
82 UIContextMenuNavigationAction(QObject *pParent = 0);
83 void setBackwardAvailable(bool fAvailable);
84 void setForwardAvailable(bool fAvailable);
85
86private slots:
87
88 void sltGoBackward();
89 void sltGoForward();
90 void sltGoHome();
91 void sltReloadPage();
92 void sltAddBookmark();
93
94private:
95
96 void prepare();
97 QIToolButton *m_pBackwardButton;
98 QIToolButton *m_pForwardButton;
99 QIToolButton *m_pHomeButton;
100 QIToolButton *m_pReloadPageButton;
101 QIToolButton *m_pAddBookmarkButton;
102};
103
104/*********************************************************************************************************************************
105* UIFindInPageWidget definition. *
106*********************************************************************************************************************************/
107class UIFindInPageWidget : public QWidget
108{
109
110 Q_OBJECT;
111
112signals:
113
114 void sigDragging(const QPoint &delta);
115 void sigSearchTextChanged(const QString &strSearchText);
116 void sigSelectNextMatch();
117 void sigSelectPreviousMatch();
118 void sigClose();
119
120public:
121
122 UIFindInPageWidget(QWidget *pParent = 0);
123 void setMatchCountAndCurrentIndex(int iTotalMatchCount, int iCurrentlyScrolledIndex);
124 void clearSearchField();
125
126protected:
127
128 virtual bool eventFilter(QObject *pObject, QEvent *pEvent) RT_OVERRIDE;
129 virtual void keyPressEvent(QKeyEvent *pEvent) RT_OVERRIDE;
130
131private:
132
133 void prepare();
134 UISearchLineEdit *m_pSearchLineEdit;
135 QIToolButton *m_pNextButton;
136 QIToolButton *m_pPreviousButton;
137 QIToolButton *m_pCloseButton;
138 QLabel *m_pDragMoveLabel;
139 QPoint m_previousMousePosition;
140};
141
142
143/*********************************************************************************************************************************
144* UIContextMenuNavigationAction implementation. *
145*********************************************************************************************************************************/
146UIContextMenuNavigationAction::UIContextMenuNavigationAction(QObject *pParent /* = 0 */)
147 :QWidgetAction(pParent)
148 , m_pBackwardButton(0)
149 , m_pForwardButton(0)
150 , m_pHomeButton(0)
151 , m_pReloadPageButton(0)
152 , m_pAddBookmarkButton(0)
153{
154 prepare();
155}
156
157void UIContextMenuNavigationAction::setBackwardAvailable(bool fAvailable)
158{
159 if (m_pBackwardButton)
160 m_pBackwardButton->setEnabled(fAvailable);
161}
162
163void UIContextMenuNavigationAction::setForwardAvailable(bool fAvailable)
164{
165 if (m_pForwardButton)
166 m_pForwardButton->setEnabled(fAvailable);
167}
168
169void UIContextMenuNavigationAction::sltGoBackward()
170{
171 emit sigGoBackward();
172 emit triggered();
173}
174
175void UIContextMenuNavigationAction::sltGoForward()
176{
177 emit sigGoForward();
178 emit triggered();
179}
180
181void UIContextMenuNavigationAction::sltGoHome()
182{
183 emit sigGoHome();
184 emit triggered();
185}
186
187void UIContextMenuNavigationAction::sltReloadPage()
188{
189 emit sigReloadPage();
190 emit triggered();
191}
192
193void UIContextMenuNavigationAction::sltAddBookmark()
194{
195 emit sigAddBookmark();
196 emit triggered();
197}
198
199void UIContextMenuNavigationAction::prepare()
200{
201 QWidget *pWidget = new QWidget;
202 setDefaultWidget(pWidget);
203 QHBoxLayout *pMainLayout = new QHBoxLayout(pWidget);
204 AssertReturnVoid(pMainLayout);
205
206 m_pBackwardButton = new QIToolButton;
207 m_pForwardButton = new QIToolButton;
208 m_pHomeButton = new QIToolButton;
209 m_pReloadPageButton = new QIToolButton;
210 m_pAddBookmarkButton = new QIToolButton;
211
212 AssertReturnVoid(m_pBackwardButton &&
213 m_pForwardButton &&
214 m_pHomeButton &&
215 m_pReloadPageButton);
216
217 m_pForwardButton->setEnabled(false);
218 m_pBackwardButton->setEnabled(false);
219 m_pHomeButton->setIcon(UIIconPool::iconSet(":/help_browser_home_16px.png", ":/help_browser_home_disabled_16px.png"));
220 m_pReloadPageButton->setIcon(UIIconPool::iconSet(":/help_browser_reload_16px.png", ":/help_browser_reload_disabled_16px.png"));
221 m_pForwardButton->setIcon(UIIconPool::iconSet(":/help_browser_forward_16px.png", ":/help_browser_forward_disabled_16px.png"));
222 m_pBackwardButton->setIcon(UIIconPool::iconSet(":/help_browser_backward_16px.png", ":/help_browser_backward_disabled_16px.png"));
223 m_pAddBookmarkButton->setIcon(UIIconPool::iconSet(":/help_browser_add_bookmark_16px.png", ":/help_browser_add_bookmark_disabled_16px.png"));
224
225 m_pHomeButton->setToolTip(UIHelpBrowserWidget::tr("Return to Start Page"));
226 m_pReloadPageButton->setToolTip(UIHelpBrowserWidget::tr("Reload the Current Page"));
227 m_pForwardButton->setToolTip(UIHelpBrowserWidget::tr("Go Forward to Next Page"));
228 m_pBackwardButton->setToolTip(UIHelpBrowserWidget::tr("Go Back to Previous Page"));
229 m_pAddBookmarkButton->setToolTip(UIHelpBrowserWidget::tr("Add a New Bookmark"));
230
231 pMainLayout->addWidget(m_pBackwardButton);
232 pMainLayout->addWidget(m_pForwardButton);
233 pMainLayout->addWidget(m_pHomeButton);
234 pMainLayout->addWidget(m_pReloadPageButton);
235 pMainLayout->addWidget(m_pAddBookmarkButton);
236 pMainLayout->setContentsMargins(0, 0, 0, 0);
237
238 connect(m_pBackwardButton, &QIToolButton::pressed,
239 this, &UIContextMenuNavigationAction::sltGoBackward);
240 connect(m_pForwardButton, &QIToolButton::pressed,
241 this, &UIContextMenuNavigationAction::sltGoForward);
242 connect(m_pHomeButton, &QIToolButton::pressed,
243 this, &UIContextMenuNavigationAction::sltGoHome);
244 connect(m_pReloadPageButton, &QIToolButton::pressed,
245 this, &UIContextMenuNavigationAction::sltReloadPage);
246 connect(m_pAddBookmarkButton, &QIToolButton::pressed,
247 this, &UIContextMenuNavigationAction::sltAddBookmark);
248 connect(m_pReloadPageButton, &QIToolButton::pressed,
249 this, &UIContextMenuNavigationAction::sltAddBookmark);
250}
251
252
253/*********************************************************************************************************************************
254* UIFindInPageWidget implementation. *
255*********************************************************************************************************************************/
256UIFindInPageWidget::UIFindInPageWidget(QWidget *pParent /* = 0 */)
257 : QWidget(pParent)
258 , m_pSearchLineEdit(0)
259 , m_pNextButton(0)
260 , m_pPreviousButton(0)
261 , m_pCloseButton(0)
262 , m_previousMousePosition(-1, -1)
263{
264 prepare();
265}
266
267void UIFindInPageWidget::setMatchCountAndCurrentIndex(int iTotalMatchCount, int iCurrentlyScrolledIndex)
268{
269 if (!m_pSearchLineEdit)
270 return;
271 m_pSearchLineEdit->setMatchCount(iTotalMatchCount);
272 m_pSearchLineEdit->setScrollToIndex(iCurrentlyScrolledIndex);
273}
274
275void UIFindInPageWidget::clearSearchField()
276{
277 if (!m_pSearchLineEdit)
278 return;
279 m_pSearchLineEdit->blockSignals(true);
280 m_pSearchLineEdit->reset();
281 m_pSearchLineEdit->blockSignals(false);
282}
283
284bool UIFindInPageWidget::eventFilter(QObject *pObject, QEvent *pEvent)
285{
286 if (pObject == m_pDragMoveLabel)
287 {
288 if (pEvent->type() == QEvent::Enter)
289 m_pDragMoveLabel->setCursor(Qt::CrossCursor);
290 else if (pEvent->type() == QEvent::Leave)
291 {
292 if (parentWidget())
293 m_pDragMoveLabel->setCursor(parentWidget()->cursor());
294 }
295 else if (pEvent->type() == QEvent::MouseMove)
296 {
297 QMouseEvent *pMouseEvent = static_cast<QMouseEvent*>(pEvent);
298 const QPoint gPos = pMouseEvent->globalPosition().toPoint();
299 if (pMouseEvent->buttons() == Qt::LeftButton)
300 {
301 if (m_previousMousePosition != QPoint(-1, -1))
302 emit sigDragging(gPos - m_previousMousePosition);
303 m_previousMousePosition = gPos;
304 m_pDragMoveLabel->setCursor(Qt::ClosedHandCursor);
305 }
306 }
307 else if (pEvent->type() == QEvent::MouseButtonRelease)
308 {
309 m_previousMousePosition = QPoint(-1, -1);
310 m_pDragMoveLabel->setCursor(Qt::CrossCursor);
311 }
312 }
313 return QWidget::eventFilter(pObject, pEvent);
314}
315
316void UIFindInPageWidget::keyPressEvent(QKeyEvent *pEvent)
317{
318 switch (pEvent->key())
319 {
320 case Qt::Key_Escape:
321 emit sigClose();
322 return;
323 break;
324 case Qt::Key_Down:
325 emit sigSelectNextMatch();
326 return;
327 break;
328 case Qt::Key_Up:
329 emit sigSelectPreviousMatch();
330 return;
331 break;
332 default:
333 QWidget::keyPressEvent(pEvent);
334 break;
335 }
336}
337
338void UIFindInPageWidget::prepare()
339{
340 setAutoFillBackground(true);
341 setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum);
342
343 QHBoxLayout *pLayout = new QHBoxLayout(this);
344 m_pSearchLineEdit = new UISearchLineEdit;
345 AssertReturnVoid(pLayout && m_pSearchLineEdit);
346 setFocusProxy(m_pSearchLineEdit);
347 QFontMetrics fontMetric(m_pSearchLineEdit->font());
348#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
349 setMinimumSize(40 * fontMetric.horizontalAdvance("x"),
350 fontMetric.height() +
351 qApp->style()->pixelMetric(QStyle::PM_LayoutBottomMargin) +
352 qApp->style()->pixelMetric(QStyle::PM_LayoutTopMargin));
353
354#else
355 setMinimumSize(40 * fontMetric.width("x"),
356 fontMetric.height() +
357 qApp->style()->pixelMetric(QStyle::PM_LayoutBottomMargin) +
358 qApp->style()->pixelMetric(QStyle::PM_LayoutTopMargin));
359#endif
360 connect(m_pSearchLineEdit, &UISearchLineEdit::textChanged,
361 this, &UIFindInPageWidget::sigSearchTextChanged);
362
363 m_pDragMoveLabel = new QLabel;
364 AssertReturnVoid(m_pDragMoveLabel);
365 m_pDragMoveLabel->installEventFilter(this);
366 m_pDragMoveLabel->setPixmap(QPixmap(":/drag_move_16px.png"));
367 pLayout->addWidget(m_pDragMoveLabel);
368
369
370 pLayout->setSpacing(0);
371 pLayout->addWidget(m_pSearchLineEdit);
372
373 m_pPreviousButton = new QIToolButton;
374 m_pNextButton = new QIToolButton;
375 m_pCloseButton = new QIToolButton;
376
377 pLayout->addWidget(m_pPreviousButton);
378 pLayout->addWidget(m_pNextButton);
379 pLayout->addWidget(m_pCloseButton);
380
381 m_pPreviousButton->setIcon(UIIconPool::iconSet(":/arrow_up_10px.png"));
382 m_pNextButton->setIcon(UIIconPool::iconSet(":/arrow_down_10px.png"));
383 m_pCloseButton->setIcon(UIIconPool::iconSet(":/close_16px.png"));
384
385 connect(m_pPreviousButton, &QIToolButton::pressed, this, &UIFindInPageWidget::sigSelectPreviousMatch);
386 connect(m_pNextButton, &QIToolButton::pressed, this, &UIFindInPageWidget::sigSelectNextMatch);
387 connect(m_pCloseButton, &QIToolButton::pressed, this, &UIFindInPageWidget::sigClose);
388}
389
390/*********************************************************************************************************************************
391* UIHelpViewer implementation. *
392*********************************************************************************************************************************/
393
394UIHelpViewer::UIHelpViewer(const QHelpEngine *pHelpEngine, QWidget *pParent /* = 0 */)
395 : QTextBrowser(pParent)
396 , m_pHelpEngine(pHelpEngine)
397 , m_pFindInPageWidget(new UIFindInPageWidget(this))
398 , m_fFindWidgetDragged(false)
399 , m_iMarginForFindWidget(qApp->style()->pixelMetric(QStyle::PM_LayoutLeftMargin))
400 , m_iSelectedMatchIndex(0)
401 , m_iSearchTermLength(0)
402 , m_fOverlayMode(false)
403 , m_pOverlayLabel(0)
404 , m_iZoomPercentage(100)
405{
406 m_iInitialFontPointSize = font().pointSize();
407 setUndoRedoEnabled(true);
408 connect(m_pFindInPageWidget, &UIFindInPageWidget::sigDragging,
409 this, &UIHelpViewer::sltFindWidgetDrag);
410 connect(m_pFindInPageWidget, &UIFindInPageWidget::sigSearchTextChanged,
411 this, &UIHelpViewer::sltFindInPageSearchTextChange);
412
413 connect(m_pFindInPageWidget, &UIFindInPageWidget::sigSelectPreviousMatch,
414 this, &UIHelpViewer::sltSelectPreviousMatch);
415 connect(m_pFindInPageWidget, &UIFindInPageWidget::sigSelectNextMatch,
416 this, &UIHelpViewer::sltSelectNextMatch);
417 connect(m_pFindInPageWidget, &UIFindInPageWidget::sigClose,
418 this, &UIHelpViewer::sltCloseFindInPageWidget);
419
420 m_pFindInPageWidget->setVisible(false);
421
422 m_pOverlayLabel = new QLabel(this);
423 if (m_pOverlayLabel)
424 {
425 m_pOverlayLabel->hide();
426 m_pOverlayLabel->installEventFilter(this);
427 }
428
429 m_pOverlayBlurEffect = new QGraphicsBlurEffect(this);
430 if (m_pOverlayBlurEffect)
431 {
432 viewport()->setGraphicsEffect(m_pOverlayBlurEffect);
433 m_pOverlayBlurEffect->setEnabled(false);
434 m_pOverlayBlurEffect->setBlurRadius(8);
435 }
436}
437
438QVariant UIHelpViewer::loadResource(int type, const QUrl &name)
439{
440 if (name.scheme() == "qthelp" && m_pHelpEngine)
441 return QVariant(m_pHelpEngine->fileData(name));
442 else
443 return QTextBrowser::loadResource(type, name);
444}
445
446void UIHelpViewer::emitHistoryChangedSignal()
447{
448 emit historyChanged();
449 emit backwardAvailable(true);
450}
451
452void UIHelpViewer::doSetSource(const QUrl &url, QTextDocument::ResourceType type)
453{
454 clearOverlay();
455 if (url.scheme() != "qthelp")
456 return;
457 QTextBrowser::doSetSource(url, type);
458 QTextDocument *pDocument = document();
459 if (!pDocument || pDocument->isEmpty())
460 {
461 setText(UIHelpBrowserWidget::tr("<div><p><h3>Not found.</h3>The page <b>%1</b> could not be found.</p></div>").arg(url.toString()));
462 setDocumentTitle(UIHelpBrowserWidget::tr("Not Found"));
463 }
464 if (m_pFindInPageWidget && m_pFindInPageWidget->isVisible())
465 {
466 document()->undo();
467 m_pFindInPageWidget->clearSearchField();
468 }
469 iterateDocumentImages();
470 scaleImages();
471}
472
473void UIHelpViewer::toggleFindInPageWidget(bool fVisible)
474{
475 if (!m_pFindInPageWidget)
476 return;
477
478 /* Closing the find in page widget causes QTextBrowser to jump to the top of the document. This hack puts it back into position: */
479 int iPosition = verticalScrollBar()->value();
480 m_iMarginForFindWidget = verticalScrollBar()->width() +
481 qApp->style()->pixelMetric(QStyle::PM_LayoutLeftMargin);
482 /* Try to position the widget somewhere meaningful initially: */
483 if (!m_fFindWidgetDragged)
484 m_pFindInPageWidget->move(width() - m_iMarginForFindWidget - m_pFindInPageWidget->width(),
485 m_iMarginForFindWidget);
486
487 m_pFindInPageWidget->setVisible(fVisible);
488
489 if (!fVisible)
490 {
491 /* Clear highlights: */
492 setExtraSelections(QList<QTextEdit::ExtraSelection>());
493 m_pFindInPageWidget->clearSearchField();
494 verticalScrollBar()->setValue(iPosition);
495 }
496 else
497 m_pFindInPageWidget->setFocus();
498 emit sigFindInPageWidgetToogle(fVisible);
499}
500
501void UIHelpViewer::reload()
502{
503 setSource(source());
504}
505
506void UIHelpViewer::sltToggleFindInPageWidget(bool fVisible)
507{
508 clearOverlay();
509 toggleFindInPageWidget(fVisible);
510}
511
512void UIHelpViewer::sltCloseFindInPageWidget()
513{
514 sltToggleFindInPageWidget(false);
515}
516
517void UIHelpViewer::setFont(const QFont &font)
518{
519 QTextBrowser::setFont(font);
520 /* Make sure the font size of the find in widget stays constant: */
521 if (m_pFindInPageWidget)
522 {
523 QFont wFont(font);
524 wFont.setPointSize(m_iInitialFontPointSize);
525 m_pFindInPageWidget->setFont(wFont);
526 }
527}
528
529bool UIHelpViewer::isFindInPageWidgetVisible() const
530{
531 if (m_pFindInPageWidget)
532 return m_pFindInPageWidget->isVisible();
533 return false;
534}
535
536void UIHelpViewer::setZoomPercentage(int iZoomPercentage)
537{
538 m_iZoomPercentage = iZoomPercentage;
539 clearOverlay();
540 scaleFont();
541 scaleImages();
542}
543
544void UIHelpViewer::setHelpFileList(const QList<QUrl> &helpFileList)
545{
546 m_helpFileList = helpFileList;
547 /* File list necessary to get the image data from the help engine: */
548 iterateDocumentImages();
549 scaleImages();
550}
551
552bool UIHelpViewer::hasSelectedText() const
553{
554 return textCursor().hasSelection();
555}
556
557void UIHelpViewer::contextMenuEvent(QContextMenuEvent *event)
558{
559 QMenu menu;
560
561 if (textCursor().hasSelection())
562 {
563 QAction *pCopySelectedTextAction = new QAction(UIHelpBrowserWidget::tr("Copy Selected Text"));
564 connect(pCopySelectedTextAction, &QAction::triggered,
565 this, &UIHelpViewer::copy);
566 menu.addAction(pCopySelectedTextAction);
567 menu.addSeparator();
568 }
569
570 UIContextMenuNavigationAction *pNavigationActions = new UIContextMenuNavigationAction;
571 pNavigationActions->setBackwardAvailable(isBackwardAvailable());
572 pNavigationActions->setForwardAvailable(isForwardAvailable());
573
574 connect(pNavigationActions, &UIContextMenuNavigationAction::sigGoBackward,
575 this, &UIHelpViewer::sigGoBackward);
576 connect(pNavigationActions, &UIContextMenuNavigationAction::sigGoForward,
577 this, &UIHelpViewer::sigGoForward);
578 connect(pNavigationActions, &UIContextMenuNavigationAction::sigGoHome,
579 this, &UIHelpViewer::sigGoHome);
580 connect(pNavigationActions, &UIContextMenuNavigationAction::sigReloadPage,
581 this, &UIHelpViewer::reload);
582 connect(pNavigationActions, &UIContextMenuNavigationAction::sigAddBookmark,
583 this, &UIHelpViewer::sigAddBookmark);
584
585 QAction *pOpenLinkAction = new QAction(UIHelpBrowserWidget::tr("Open Link"));
586 connect(pOpenLinkAction, &QAction::triggered,
587 this, &UIHelpViewer::sltOpenLink);
588
589 QAction *pOpenInNewTabAction = new QAction(UIHelpBrowserWidget::tr("Open Link in New Tab"));
590 connect(pOpenInNewTabAction, &QAction::triggered,
591 this, &UIHelpViewer::sltOpenLinkInNewTab);
592
593 QAction *pCopyLink = new QAction(UIHelpBrowserWidget::tr("Copy Link"));
594 connect(pCopyLink, &QAction::triggered,
595 this, &UIHelpViewer::sltCopyLink);
596
597 QAction *pFindInPage = new QAction(UIHelpBrowserWidget::tr("Find in Page"));
598 pFindInPage->setCheckable(true);
599 if (m_pFindInPageWidget)
600 pFindInPage->setChecked(m_pFindInPageWidget->isVisible());
601 connect(pFindInPage, &QAction::toggled, this, &UIHelpViewer::sltToggleFindInPageWidget);
602
603 menu.addAction(pNavigationActions);
604 menu.addAction(pOpenLinkAction);
605 menu.addAction(pOpenInNewTabAction);
606 menu.addAction(pCopyLink);
607 menu.addAction(pFindInPage);
608
609 QString strAnchor = anchorAt(event->pos());
610 if (!strAnchor.isEmpty())
611 {
612 QString strLink = source().resolved(anchorAt(event->pos())).toString();
613 pOpenLinkAction->setData(strLink);
614 pOpenInNewTabAction->setData(strLink);
615 pCopyLink->setData(strLink);
616 }
617 else
618 {
619 pOpenLinkAction->setEnabled(false);
620 pOpenInNewTabAction->setEnabled(false);
621 pCopyLink->setEnabled(false);
622 }
623
624 menu.exec(event->globalPos());
625}
626
627void UIHelpViewer::resizeEvent(QResizeEvent *pEvent)
628{
629 if (m_fOverlayMode)
630 clearOverlay();
631 /* Make sure the widget stays inside the parent during parent resize: */
632 if (m_pFindInPageWidget)
633 {
634 if (!isRectInside(m_pFindInPageWidget->geometry(), m_iMarginForFindWidget))
635 moveFindWidgetIn(m_iMarginForFindWidget);
636 }
637 QTextBrowser::resizeEvent(pEvent);
638}
639
640void UIHelpViewer::wheelEvent(QWheelEvent *pEvent)
641{
642 if (m_fOverlayMode && !pEvent)
643 return;
644 /* QTextBrowser::wheelEvent scales font when some modifiers are pressed. We dont want that: */
645 if (pEvent->modifiers() == Qt::NoModifier)
646 QTextBrowser::wheelEvent(pEvent);
647 else if (pEvent->modifiers() & Qt::ControlModifier)
648 {
649 if (pEvent->angleDelta().y() > 0)
650 emit sigZoomRequest(ZoomOperation_In);
651 else if (pEvent->angleDelta().y() < 0)
652 emit sigZoomRequest(ZoomOperation_Out);
653 }
654}
655
656void UIHelpViewer::mouseReleaseEvent(QMouseEvent *pEvent)
657{
658 /* If overlay mode is active just clear it and return: */
659 bool fOverlayMode = m_fOverlayMode;
660 clearOverlay();
661 if (fOverlayMode)
662 return;
663 QString strAnchor = anchorAt(pEvent->position().toPoint());
664
665 if (!strAnchor.isEmpty())
666 {
667 QString strLink = source().resolved(strAnchor).toString();
668 QFileInfo fInfo(strLink);
669 QMimeDatabase base;
670 QMimeType type = base.mimeTypeForFile(fInfo);
671 if (type.isValid() && type.inherits("image/png"))
672 {
673 loadImage(source().resolved(strAnchor));
674 return;
675 }
676 if (source().resolved(strAnchor).scheme() != "qthelp" && pEvent->button() == Qt::LeftButton)
677 {
678 uiCommon().openURL(strLink);
679 return;
680 }
681
682 if ((pEvent->modifiers() & Qt::ControlModifier) ||
683 pEvent->button() == Qt::MiddleButton)
684 {
685
686 emit sigOpenLinkInNewTab(strLink, true);
687 return;
688 }
689 }
690 QTextBrowser::mousePressEvent(pEvent);
691}
692
693void UIHelpViewer::mousePressEvent(QMouseEvent *pEvent)
694{
695 QTextBrowser::mousePressEvent(pEvent);
696}
697
698void UIHelpViewer::mouseMoveEvent(QMouseEvent *pEvent)
699{
700 /*if (m_fOverlayMode)
701 return;*/
702 QTextBrowser::mouseMoveEvent(pEvent);
703}
704
705void UIHelpViewer::mouseDoubleClickEvent(QMouseEvent *pEvent)
706{
707 clearOverlay();
708 QTextBrowser::mouseDoubleClickEvent(pEvent);
709}
710
711void UIHelpViewer::paintEvent(QPaintEvent *pEvent)
712{
713 QTextBrowser::paintEvent(pEvent);
714 QPainter painter(viewport());
715 foreach(const DocumentImage &image, m_imageMap)
716 {
717 QRect rect = cursorRect(image.m_textCursor);
718 QPixmap newPixmap = image.m_pixmap.scaledToWidth(image.m_fScaledWidth, Qt::SmoothTransformation);
719 QRectF imageRect(rect.x() - newPixmap.width(), rect.y(), newPixmap.width(), newPixmap.height());
720
721 int iMargin = 3;
722 QRectF fillRect(imageRect.x() - iMargin, imageRect.y() - iMargin,
723 imageRect.width() + 2 * iMargin, imageRect.height() + 2 * iMargin);
724 /** @todo I need to find the default color somehow and replace hard coded Qt::white. */
725 painter.fillRect(fillRect, Qt::white);
726 painter.drawPixmap(imageRect, newPixmap, newPixmap.rect());
727 }
728}
729
730bool UIHelpViewer::eventFilter(QObject *pObject, QEvent *pEvent)
731{
732 if (pObject == m_pOverlayLabel)
733 {
734 if (pEvent->type() == QEvent::MouseButtonPress ||
735 pEvent->type() == QEvent::MouseButtonDblClick)
736 clearOverlay();
737 }
738 return QTextBrowser::eventFilter(pObject, pEvent);
739}
740
741void UIHelpViewer::keyPressEvent(QKeyEvent *pEvent)
742{
743 if (pEvent && pEvent->key() == Qt::Key_Escape)
744 clearOverlay();
745 if (pEvent && pEvent->modifiers() &Qt::ControlModifier)
746 {
747 switch (pEvent->key())
748 {
749 case Qt::Key_Equal:
750 emit sigZoomRequest(ZoomOperation_In);
751 break;
752 case Qt::Key_Minus:
753 emit sigZoomRequest(ZoomOperation_Out);
754 break;
755 case Qt::Key_0:
756 emit sigZoomRequest(ZoomOperation_Reset);
757 break;
758 default:
759 break;
760 }
761 }
762 QTextBrowser::keyPressEvent(pEvent);
763}
764
765void UIHelpViewer::moveFindWidgetIn(int iMargin)
766{
767 if (!m_pFindInPageWidget)
768 return;
769
770 QRect rect = m_pFindInPageWidget->geometry();
771 if (rect.left() < iMargin)
772 rect.translate(-rect.left() + iMargin, 0);
773 if (rect.right() > width() - iMargin)
774 rect.translate((width() - iMargin - rect.right()), 0);
775 if (rect.top() < iMargin)
776 rect.translate(0, -rect.top() + iMargin);
777
778 if (rect.bottom() > height() - iMargin)
779 rect.translate(0, (height() - iMargin - rect.bottom()));
780 m_pFindInPageWidget->setGeometry(rect);
781 m_pFindInPageWidget->update();
782}
783
784bool UIHelpViewer::isRectInside(const QRect &rect, int iMargin) const
785{
786 if (rect.left() < iMargin || rect.top() < iMargin)
787 return false;
788 if (rect.right() > width() - iMargin || rect.bottom() > height() - iMargin)
789 return false;
790 return true;
791}
792
793void UIHelpViewer::findAllMatches(const QString &searchString)
794{
795 QTextDocument *pDocument = document();
796 AssertReturnVoid(pDocument);
797
798 m_matchedCursorPosition.clear();
799 if (searchString.isEmpty())
800 return;
801 QTextCursor cursor(pDocument);
802 QTextDocument::FindFlags flags;
803 while (!cursor.isNull() && !cursor.atEnd())
804 {
805 cursor = pDocument->find(searchString, cursor, flags);
806 if (!cursor.isNull())
807 m_matchedCursorPosition << cursor.position() - searchString.length();
808 }
809}
810
811void UIHelpViewer::highlightFinds(int iSearchTermLength)
812{
813 QList<QTextEdit::ExtraSelection> extraSelections;
814 for (int i = 0; i < m_matchedCursorPosition.size(); ++i)
815 {
816 QTextEdit::ExtraSelection selection;
817 QTextCursor cursor = textCursor();
818 cursor.setPosition(m_matchedCursorPosition[i]);
819 cursor.setPosition(m_matchedCursorPosition[i] + iSearchTermLength, QTextCursor::KeepAnchor);
820 QTextCharFormat format = cursor.charFormat();
821 format.setBackground(Qt::yellow);
822
823 selection.cursor = cursor;
824 selection.format = format;
825 extraSelections.append(selection);
826 }
827 setExtraSelections(extraSelections);
828}
829
830void UIHelpViewer::selectMatch(int iMatchIndex, int iSearchStringLength)
831{
832 QTextCursor cursor = textCursor();
833 /* Move the cursor to the beginning of the matched string: */
834 cursor.setPosition(m_matchedCursorPosition.at(iMatchIndex), QTextCursor::MoveAnchor);
835 /* Move the cursor to the end of the matched string while keeping the anchor at the begining thus selecting the text: */
836 cursor.setPosition(m_matchedCursorPosition.at(iMatchIndex) + iSearchStringLength, QTextCursor::KeepAnchor);
837 ensureCursorVisible();
838 setTextCursor(cursor);
839}
840
841void UIHelpViewer::sltOpenLinkInNewTab()
842{
843 QAction *pSender = qobject_cast<QAction*>(sender());
844 if (!pSender)
845 return;
846 QUrl url = pSender->data().toUrl();
847 if (url.isValid())
848 emit sigOpenLinkInNewTab(url, false);
849}
850
851void UIHelpViewer::sltOpenLink()
852{
853 QAction *pSender = qobject_cast<QAction*>(sender());
854 if (!pSender)
855 return;
856 QUrl url = pSender->data().toUrl();
857 if (url.isValid())
858 setSource(url);
859}
860
861void UIHelpViewer::sltCopyLink()
862{
863 QAction *pSender = qobject_cast<QAction*>(sender());
864 if (!pSender)
865 return;
866 QUrl url = pSender->data().toUrl();
867 if (url.isValid())
868 {
869 QClipboard *pClipboard = QApplication::clipboard();
870 if (pClipboard)
871 pClipboard->setText(url.toString());
872 }
873}
874
875void UIHelpViewer::sltFindWidgetDrag(const QPoint &delta)
876{
877 if (!m_pFindInPageWidget)
878 return;
879 QRect geo = m_pFindInPageWidget->geometry();
880 geo.translate(delta);
881
882 /* Allow the move if m_pFindInPageWidget stays inside after the move: */
883 if (isRectInside(geo, m_iMarginForFindWidget))
884 m_pFindInPageWidget->move(m_pFindInPageWidget->pos() + delta);
885 m_fFindWidgetDragged = true;
886 update();
887}
888
889void UIHelpViewer::sltFindInPageSearchTextChange(const QString &strSearchText)
890{
891 m_iSearchTermLength = strSearchText.length();
892 findAllMatches(strSearchText);
893 highlightFinds(m_iSearchTermLength);
894 selectMatch(0, m_iSearchTermLength);
895 if (m_pFindInPageWidget)
896 m_pFindInPageWidget->setMatchCountAndCurrentIndex(m_matchedCursorPosition.size(), 0);
897}
898
899void UIHelpViewer::sltSelectPreviousMatch()
900{
901 m_iSelectedMatchIndex = m_iSelectedMatchIndex <= 0 ? m_matchedCursorPosition.size() - 1 : (m_iSelectedMatchIndex - 1);
902 selectMatch(m_iSelectedMatchIndex, m_iSearchTermLength);
903 if (m_pFindInPageWidget)
904 m_pFindInPageWidget->setMatchCountAndCurrentIndex(m_matchedCursorPosition.size(), m_iSelectedMatchIndex);
905}
906
907void UIHelpViewer::sltSelectNextMatch()
908{
909 m_iSelectedMatchIndex = m_iSelectedMatchIndex >= m_matchedCursorPosition.size() - 1 ? 0 : (m_iSelectedMatchIndex + 1);
910 selectMatch(m_iSelectedMatchIndex, m_iSearchTermLength);
911 if (m_pFindInPageWidget)
912 m_pFindInPageWidget->setMatchCountAndCurrentIndex(m_matchedCursorPosition.size(), m_iSelectedMatchIndex);
913}
914
915void UIHelpViewer::iterateDocumentImages()
916{
917 m_imageMap.clear();
918 QTextCursor cursor = textCursor();
919 cursor.movePosition(QTextCursor::Start);
920 while (!cursor.atEnd())
921 {
922 cursor.movePosition(QTextCursor::NextCharacter);
923 if (cursor.charFormat().isImageFormat())
924 {
925 QTextImageFormat imageFormat = cursor.charFormat().toImageFormat();
926 /* There seems to be two cursors per image. Use the first one: */
927 if (m_imageMap.contains(imageFormat.name()))
928 continue;
929 QHash<QString, DocumentImage>::iterator iterator = m_imageMap.insert(imageFormat.name(), DocumentImage());
930 DocumentImage &image = iterator.value();
931 image.m_fInitialWidth = imageFormat.width();
932 image.m_strName = imageFormat.name();
933 image.m_textCursor = cursor;
934 QUrl imageFileUrl;
935 foreach (const QUrl &fileUrl, m_helpFileList)
936 {
937 if (fileUrl.toString().contains(imageFormat.name(), Qt::CaseInsensitive))
938 {
939 imageFileUrl = fileUrl;
940 break;
941 }
942 }
943 if (imageFileUrl.isValid())
944 {
945 QByteArray fileData = m_pHelpEngine->fileData(imageFileUrl);
946 if (!fileData.isEmpty())
947 image.m_pixmap.loadFromData(fileData,"PNG");
948 }
949 }
950 }
951}
952
953void UIHelpViewer::scaleFont()
954{
955 QFont mFont = font();
956 mFont.setPointSize(m_iInitialFontPointSize * m_iZoomPercentage / 100.);
957 setFont(mFont);
958}
959
960void UIHelpViewer::scaleImages()
961{
962 for (QHash<QString, DocumentImage>::iterator iterator = m_imageMap.begin();
963 iterator != m_imageMap.end(); ++iterator)
964 {
965 DocumentImage &image = *iterator;
966 QTextCursor cursor = image.m_textCursor;
967 QTextCharFormat format = cursor.charFormat();
968 if (!format.isImageFormat())
969 continue;
970 QTextImageFormat imageFormat = format.toImageFormat();
971 image.m_fScaledWidth = image.m_fInitialWidth * m_iZoomPercentage / 100.;
972 imageFormat.setWidth(image.m_fScaledWidth);
973 cursor.deletePreviousChar();
974 cursor.deleteChar();
975 cursor.insertImage(imageFormat);
976 }
977}
978
979void UIHelpViewer::clearOverlay()
980{
981 AssertReturnVoid(m_pOverlayLabel);
982
983 if (!m_fOverlayMode)
984 return;
985 m_overlayPixmap = QPixmap();
986 m_fOverlayMode = false;
987 if (m_pOverlayBlurEffect)
988 m_pOverlayBlurEffect->setEnabled(false);
989 m_pOverlayLabel->hide();
990}
991
992void UIHelpViewer::enableOverlay()
993{
994 AssertReturnVoid(m_pOverlayLabel);
995 m_fOverlayMode = true;
996 if (m_pOverlayBlurEffect)
997 m_pOverlayBlurEffect->setEnabled(true);
998 toggleFindInPageWidget(false);
999
1000 /* Scale the image to 1:1 as long as it fits into avaible space (minus some margins and scrollbar sizes): */
1001 int vWidth = 0;
1002 if (verticalScrollBar() && verticalScrollBar()->isVisible())
1003 vWidth = verticalScrollBar()->width();
1004 int hMargin = qApp->style()->pixelMetric(QStyle::PM_LayoutLeftMargin) +
1005 qApp->style()->pixelMetric(QStyle::PM_LayoutRightMargin) + vWidth;
1006
1007 int hHeight = 0;
1008 if (horizontalScrollBar() && horizontalScrollBar()->isVisible())
1009 hHeight = horizontalScrollBar()->height();
1010 int vMargin = qApp->style()->pixelMetric(QStyle::PM_LayoutTopMargin) +
1011 qApp->style()->pixelMetric(QStyle::PM_LayoutBottomMargin) + hHeight;
1012
1013 QSize size(qMin(width() - hMargin, m_overlayPixmap.width()),
1014 qMin(height() - vMargin, m_overlayPixmap.height()));
1015 m_pOverlayLabel->setPixmap(m_overlayPixmap.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation));
1016 m_pOverlayLabel->show();
1017
1018 /* Center the label: */
1019 int x = 0.5 * (width() - vWidth - m_pOverlayLabel->width());
1020 int y = 0.5 * (height() - hHeight - m_pOverlayLabel->height());
1021 m_pOverlayLabel->move(x, y);
1022}
1023
1024void UIHelpViewer::loadImage(const QUrl &imageFileUrl)
1025{
1026 clearOverlay();
1027 /* Dont zoom into image if mouse button released after a mouse drag: */
1028 if (textCursor().hasSelection())
1029 return;
1030 if (!imageFileUrl.isValid())
1031 return;
1032 QByteArray fileData = m_pHelpEngine->fileData(imageFileUrl);
1033 if (!fileData.isEmpty())
1034 {
1035 m_overlayPixmap.loadFromData(fileData,"PNG");
1036 if (!m_overlayPixmap.isNull())
1037 enableOverlay();
1038 }
1039}
1040
1041
1042#include "UIHelpViewer.moc"
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use