VirtualBox

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

Last change on this file since 100347 was 100344, checked in by vboxsync, 19 months ago

FE/Qt: bugref:10450: Qt6 compatibility bits for QMouseEvent; Replacing QMouseEvent::pos with QSinglePointEvent::position; S.a. r157776.

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

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette