VirtualBox

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

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

FE/Qt: qt6: Qt::MidButton -> Qt::MiddleButton (5.0 or earlier). bugref:9898

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