VirtualBox

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

Last change on this file since 89369 was 89369, checked in by vboxsync, 4 years ago

FE/Qt: bugref:9831. Fixing overlay label drawing which caused recursive paint call

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