VirtualBox

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

Last change on this file since 95068 was 95068, checked in by vboxsync, 3 years ago

FE/Qt: bugref:9831. Some improvements on the help viewer's context menu.

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