VirtualBox

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

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

FE/Qt: bugref:9831. No image zoom when there is selected text.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 33.1 KB
Line 
1/* $Id: UIHelpViewer.cpp 88516 2021-04-15 07:50:51Z 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::sltHandleFindWidgetDrag);
348 connect(m_pFindInPageWidget, &UIFindInPageWidget::sigSearchTextChanged,
349 this, &UIHelpViewer::sltHandleFindInPageSearchTextChange);
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::sigCloseFindInPageWidget);
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::sltToggleFindInPageWidget(bool fVisible)
411{
412 if (!m_pFindInPageWidget)
413 return;
414 /* Closing the find in page widget causes QTextBrowser to jump to the top of the document. This hack puts it back into position: */
415 int iPosition = verticalScrollBar()->value();
416 m_iMarginForFindWidget = verticalScrollBar()->width() +
417 qApp->style()->pixelMetric(QStyle::PM_LayoutLeftMargin);
418 /* Try to position the widget somewhere meaningful initially: */
419 if (!m_fFindWidgetDragged)
420 m_pFindInPageWidget->move(width() - m_iMarginForFindWidget - m_pFindInPageWidget->width(),
421 m_iMarginForFindWidget);
422
423 m_pFindInPageWidget->setVisible(fVisible);
424
425 if (!fVisible)
426 {
427 /* Clear highlights: */
428 setExtraSelections(QList<QTextEdit::ExtraSelection>());
429 m_pFindInPageWidget->clearSearchField();
430 verticalScrollBar()->setValue(iPosition);
431 }
432 else
433 m_pFindInPageWidget->setFocus();
434}
435
436void UIHelpViewer::setFont(const QFont &font)
437{
438 QIWithRetranslateUI<QTextBrowser>::setFont(font);
439 /* Make sure the font size of the find in widget stays constant: */
440 if (m_pFindInPageWidget)
441 {
442 QFont wFont(font);
443 wFont.setPointSize(m_iInitialFontPointSize);
444 m_pFindInPageWidget->setFont(wFont);
445 }
446}
447
448bool UIHelpViewer::isFindInPageWidgetVisible() const
449{
450 if (m_pFindInPageWidget)
451 return m_pFindInPageWidget->isVisible();
452 return false;
453}
454
455void UIHelpViewer::zoom(ZoomOperation enmZoomOperation)
456{
457 clearOverlay();
458 int iPrevZoom = m_iZoomPercentage;
459 switch (enmZoomOperation)
460 {
461 case ZoomOperation_In:
462 iPrevZoom += iZoomPercentageStep;
463 break;
464 case ZoomOperation_Out:
465 iPrevZoom -= iZoomPercentageStep;
466 break;
467 case ZoomOperation_Reset:
468 default:
469 iPrevZoom = 100;
470 break;
471 }
472 setZoomPercentage(iPrevZoom);
473}
474
475void UIHelpViewer::setZoomPercentage(int iZoomPercentage)
476{
477 if (iZoomPercentage > zoomPercentageMinMax.second ||
478 iZoomPercentage < zoomPercentageMinMax.first ||
479 m_iZoomPercentage == iZoomPercentage)
480 return;
481
482 m_iZoomPercentage = iZoomPercentage;
483 scaleFont();
484 scaleImages();
485 emit sigZoomPercentageChanged(m_iZoomPercentage);
486}
487
488void UIHelpViewer::setHelpFileList(const QList<QUrl> &helpFileList)
489{
490 m_helpFileList = helpFileList;
491 /* File list necessary to get the image data from the help engine: */
492 iterateDocumentImages();
493 scaleImages();
494}
495
496bool UIHelpViewer::isInOverlayMode() const
497{
498 return m_fOverlayMode;
499}
500
501int UIHelpViewer::zoomPercentage() const
502{
503 return m_iZoomPercentage;
504}
505
506void UIHelpViewer::contextMenuEvent(QContextMenuEvent *event)
507{
508 QMenu pMenu;
509
510 UIContextMenuNavigationAction *pNavigationActions = new UIContextMenuNavigationAction;
511 pNavigationActions->setBackwardAvailable(isBackwardAvailable());
512 pNavigationActions->setForwardAvailable(isForwardAvailable());
513
514 connect(pNavigationActions, &UIContextMenuNavigationAction::sigGoBackward,
515 this, &UIHelpViewer::sigGoBackward);
516 connect(pNavigationActions, &UIContextMenuNavigationAction::sigGoForward,
517 this, &UIHelpViewer::sigGoForward);
518 connect(pNavigationActions, &UIContextMenuNavigationAction::sigGoHome,
519 this, &UIHelpViewer::sigGoHome);
520 connect(pNavigationActions, &UIContextMenuNavigationAction::sigAddBookmark,
521 this, &UIHelpViewer::sigAddBookmark);
522
523 QAction *pOpenLinkAction = new QAction(UIHelpBrowserWidget::tr("Open Link"));
524 connect(pOpenLinkAction, &QAction::triggered,
525 this, &UIHelpViewer::sltHandleOpenLink);
526
527 QAction *pOpenInNewTabAction = new QAction(UIHelpBrowserWidget::tr("Open Link in New Tab"));
528 connect(pOpenInNewTabAction, &QAction::triggered,
529 this, &UIHelpViewer::sltHandleOpenLinkInNewTab);
530
531 QAction *pCopyLink = new QAction(UIHelpBrowserWidget::tr("Copy Link"));
532 connect(pCopyLink, &QAction::triggered,
533 this, &UIHelpViewer::sltHandleCopyLink);
534
535 QAction *pFindInPage = new QAction(UIHelpBrowserWidget::tr("Find in Page"));
536 pFindInPage->setCheckable(true);
537 if (m_pFindInPageWidget)
538 pFindInPage->setChecked(m_pFindInPageWidget->isVisible());
539 connect(pFindInPage, &QAction::toggled, this, &UIHelpViewer::sltToggleFindInPageWidget);
540
541 pMenu.addAction(pNavigationActions);
542 pMenu.addAction(pOpenLinkAction);
543 pMenu.addAction(pOpenInNewTabAction);
544 pMenu.addAction(pCopyLink);
545 pMenu.addAction(pFindInPage);
546
547 QString strAnchor = anchorAt(event->pos());
548 if (!strAnchor.isEmpty())
549 {
550 QString strLink = source().resolved(anchorAt(event->pos())).toString();
551 pOpenLinkAction->setData(strLink);
552 pOpenInNewTabAction->setData(strLink);
553 pCopyLink->setData(strLink);
554 }
555 else
556 {
557 pOpenLinkAction->setEnabled(false);
558 pOpenInNewTabAction->setEnabled(false);
559 pCopyLink->setEnabled(false);
560 }
561 pMenu.exec(event->globalPos());
562}
563
564void UIHelpViewer::resizeEvent(QResizeEvent *pEvent)
565{
566 clearOverlay();
567 /* Make sure the widget stays inside the parent during parent resize: */
568 if (m_pFindInPageWidget)
569 {
570 if (!isRectInside(m_pFindInPageWidget->geometry(), m_iMarginForFindWidget))
571 moveFindWidgetIn(m_iMarginForFindWidget);
572 }
573 QIWithRetranslateUI<QTextBrowser>::resizeEvent(pEvent);
574}
575
576void UIHelpViewer::wheelEvent(QWheelEvent *pEvent)
577{
578 if (m_fOverlayMode && !pEvent)
579 return;
580 /* QTextBrowser::wheelEvent scales font when some modifiers are pressed. We dont want that: */
581 if (pEvent->modifiers() == Qt::NoModifier)
582 QTextBrowser::wheelEvent(pEvent);
583 else if (pEvent->modifiers() & Qt::ControlModifier)
584 {
585 if (pEvent->angleDelta().y() > 0)
586 zoom(ZoomOperation_In);
587 else if (pEvent->angleDelta().y() < 0)
588 zoom(ZoomOperation_Out);
589 }
590}
591
592void UIHelpViewer::mouseReleaseEvent(QMouseEvent *pEvent)
593{
594 bool fOverlayMode = m_fOverlayMode;
595 clearOverlay();
596
597 QString strAnchor = anchorAt(pEvent->pos());
598 if (!strAnchor.isEmpty())
599 {
600 if ((pEvent->modifiers() & Qt::ControlModifier) ||
601 pEvent->button() == Qt::MidButton)
602 {
603 QString strLink = source().resolved(strAnchor).toString();
604 emit sigOpenLinkInNewTab(strLink, true);
605 return;
606 }
607 }
608 QIWithRetranslateUI<QTextBrowser>::mouseReleaseEvent(pEvent);
609
610 if (!fOverlayMode)
611 loadImageAtPosition(pEvent->globalPos());
612}
613
614void UIHelpViewer::mousePressEvent(QMouseEvent *pEvent)
615{
616 QIWithRetranslateUI<QTextBrowser>::mousePressEvent(pEvent);
617}
618
619
620void UIHelpViewer::mouseMoveEvent(QMouseEvent *pEvent)
621{
622 if (m_fOverlayMode)
623 return;
624
625 QPoint viewportCoordinates = viewport()->mapFromGlobal(pEvent->globalPos());
626 QTextCursor cursor = cursorForPosition(viewportCoordinates);
627 if (!m_fCursorChanged && cursor.charFormat().isImageFormat())
628 {
629 m_fCursorChanged = true;
630 viewport()->setCursor(m_handCursor);
631 }
632 if (m_fCursorChanged && !cursor.charFormat().isImageFormat())
633 {
634 viewport()->setCursor(m_defaultCursor);
635 m_fCursorChanged = false;
636 }
637 QIWithRetranslateUI<QTextBrowser>::mouseMoveEvent(pEvent);
638}
639
640void UIHelpViewer::mouseDoubleClickEvent(QMouseEvent *pEvent)
641{
642 clearOverlay();
643 QIWithRetranslateUI<QTextBrowser>::mouseDoubleClickEvent(pEvent);
644}
645
646void UIHelpViewer::paintEvent(QPaintEvent *pEvent)
647{
648 QIWithRetranslateUI<QTextBrowser>::paintEvent(pEvent);
649 QPainter painter(viewport());
650 foreach(const DocumentImage &image, m_imageMap)
651 {
652 QRect rect = cursorRect(image.m_textCursor);
653 QPixmap newPixmap = image.m_pixmap.scaledToWidth(image.m_fScaledWidth, Qt::SmoothTransformation);
654 QRectF imageRect(rect.x() - newPixmap.width(), rect.y(), newPixmap.width(), newPixmap.height());
655
656 int iMargin = 3;
657 QRectF fillRect(imageRect.x() - iMargin, imageRect.y() - iMargin,
658 imageRect.width() + 2 * iMargin, imageRect.height() + 2 * iMargin);
659 /** @todo I need to find the default color somehow and replace hard coded Qt::white. */
660 painter.fillRect(fillRect, Qt::white);
661 painter.drawPixmap(imageRect, newPixmap, newPixmap.rect());
662 }
663 if (m_pOverlayLabel)
664 {
665 if (m_fOverlayMode)
666 {
667 /* Scale the image to 1:1 as long as it fits into avaible space (minus some margins and scrollbar sizes): */
668 int vWidth = 0;
669 if (verticalScrollBar() && verticalScrollBar()->isVisible())
670 vWidth = verticalScrollBar()->width();
671 int hMargin = qApp->style()->pixelMetric(QStyle::PM_LayoutLeftMargin) +
672 qApp->style()->pixelMetric(QStyle::PM_LayoutRightMargin) + vWidth;
673
674 int hHeight = 0;
675 if (horizontalScrollBar() && horizontalScrollBar()->isVisible())
676 hHeight = horizontalScrollBar()->height();
677 int vMargin = qApp->style()->pixelMetric(QStyle::PM_LayoutTopMargin) +
678 qApp->style()->pixelMetric(QStyle::PM_LayoutBottomMargin) + hHeight;
679
680 QSize size(qMin(width() - hMargin, m_overlayPixmap.width()),
681 qMin(height() - vMargin, m_overlayPixmap.height()));
682 m_pOverlayLabel->setPixmap(m_overlayPixmap.scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation));
683 /* Center the label: */
684 int x = 0.5 * (width() - vWidth - m_pOverlayLabel->width());
685 int y = 0.5 * (height() - hHeight - m_pOverlayLabel->height());
686 m_pOverlayLabel->move(x, y);
687 m_pOverlayLabel->show();
688 }
689 else
690 m_pOverlayLabel->hide();
691 }
692}
693
694bool UIHelpViewer::eventFilter(QObject *pObject, QEvent *pEvent)
695{
696 if (pObject == m_pOverlayLabel)
697 {
698 if (pEvent->type() == QEvent::MouseButtonPress ||
699 pEvent->type() == QEvent::MouseButtonDblClick)
700 clearOverlay();
701 }
702 return QIWithRetranslateUI<QTextBrowser>::eventFilter(pObject, pEvent);
703}
704
705void UIHelpViewer::keyPressEvent(QKeyEvent *pEvent)
706{
707 if (pEvent && pEvent->key() == Qt::Key_Escape)
708 clearOverlay();
709 QIWithRetranslateUI<QTextBrowser>::keyPressEvent(pEvent);
710}
711
712void UIHelpViewer::retranslateUi()
713{
714}
715
716void UIHelpViewer::moveFindWidgetIn(int iMargin)
717{
718 if (!m_pFindInPageWidget)
719 return;
720
721 QRect rect = m_pFindInPageWidget->geometry();
722 if (rect.left() < iMargin)
723 rect.translate(-rect.left() + iMargin, 0);
724 if (rect.right() > width() - iMargin)
725 rect.translate((width() - iMargin - rect.right()), 0);
726 if (rect.top() < iMargin)
727 rect.translate(0, -rect.top() + iMargin);
728
729 if (rect.bottom() > height() - iMargin)
730 rect.translate(0, (height() - iMargin - rect.bottom()));
731 m_pFindInPageWidget->setGeometry(rect);
732 m_pFindInPageWidget->update();
733}
734
735bool UIHelpViewer::isRectInside(const QRect &rect, int iMargin) const
736{
737 if (rect.left() < iMargin || rect.top() < iMargin)
738 return false;
739 if (rect.right() > width() - iMargin || rect.bottom() > height() - iMargin)
740 return false;
741 return true;
742}
743
744void UIHelpViewer::findAllMatches(const QString &searchString)
745{
746 QTextDocument *pDocument = document();
747 AssertReturnVoid(pDocument);
748
749 m_matchedCursorPosition.clear();
750 if (searchString.isEmpty())
751 return;
752 QTextCursor cursor(pDocument);
753 QTextDocument::FindFlags flags;
754 int iMatchCount = 0;
755 while (!cursor.isNull() && !cursor.atEnd())
756 {
757 cursor = pDocument->find(searchString, cursor, flags);
758 if (!cursor.isNull())
759 {
760 m_matchedCursorPosition << cursor.position() - searchString.length();
761 ++iMatchCount;
762 }
763 }
764}
765
766void UIHelpViewer::highlightFinds(int iSearchTermLength)
767{
768 QTextDocument* pDocument = document();
769 AssertReturnVoid(pDocument);
770
771 QList<QTextEdit::ExtraSelection> extraSelections;
772
773 for (int i = 0; i < m_matchedCursorPosition.size(); ++i)
774 {
775 QTextEdit::ExtraSelection selection;
776 QTextCursor cursor = textCursor();
777 cursor.setPosition(m_matchedCursorPosition[i]);
778 cursor.setPosition(m_matchedCursorPosition[i] + iSearchTermLength, QTextCursor::KeepAnchor);
779 QTextCharFormat format = cursor.charFormat();
780 format.setBackground(Qt::yellow);
781
782 selection.cursor = cursor;
783 selection.format = format;
784 extraSelections.append(selection);
785 }
786 setExtraSelections(extraSelections);
787}
788
789void UIHelpViewer::selectMatch(int iMatchIndex, int iSearchStringLength)
790{
791 QTextCursor cursor = textCursor();
792 /* Move the cursor to the beginning of the matched string: */
793 cursor.setPosition(m_matchedCursorPosition.at(iMatchIndex), QTextCursor::MoveAnchor);
794 /* Move the cursor to the end of the matched string while keeping the anchor at the begining thus selecting the text: */
795 cursor.setPosition(m_matchedCursorPosition.at(iMatchIndex) + iSearchStringLength, QTextCursor::KeepAnchor);
796 ensureCursorVisible();
797 setTextCursor(cursor);
798}
799
800void UIHelpViewer::sltHandleOpenLinkInNewTab()
801{
802 QAction *pSender = qobject_cast<QAction*>(sender());
803 if (!pSender)
804 return;
805 QUrl url = pSender->data().toUrl();
806 if (url.isValid())
807 emit sigOpenLinkInNewTab(url, false);
808}
809
810void UIHelpViewer::sltHandleOpenLink()
811{
812 QAction *pSender = qobject_cast<QAction*>(sender());
813 if (!pSender)
814 return;
815 QUrl url = pSender->data().toUrl();
816 if (url.isValid())
817 QTextBrowser::setSource(url);
818}
819
820void UIHelpViewer::sltHandleCopyLink()
821{
822 QAction *pSender = qobject_cast<QAction*>(sender());
823 if (!pSender)
824 return;
825 QUrl url = pSender->data().toUrl();
826 if (url.isValid())
827 {
828 QClipboard *pClipboard = QApplication::clipboard();
829 if (pClipboard)
830 pClipboard->setText(url.toString());
831 }
832}
833
834void UIHelpViewer::sltHandleFindWidgetDrag(const QPoint &delta)
835{
836 if (!m_pFindInPageWidget)
837 return;
838 QRect geo = m_pFindInPageWidget->geometry();
839 geo.translate(delta);
840
841 /* Allow the move if m_pFindInPageWidget stays inside after the move: */
842 if (isRectInside(geo, m_iMarginForFindWidget))
843 m_pFindInPageWidget->move(m_pFindInPageWidget->pos() + delta);
844 m_fFindWidgetDragged = true;
845 update();
846}
847
848void UIHelpViewer::sltHandleFindInPageSearchTextChange(const QString &strSearchText)
849{
850 m_iSearchTermLength = strSearchText.length();
851 findAllMatches(strSearchText);
852 highlightFinds(m_iSearchTermLength);
853 //scrollToMatch(int iMatchIndex);
854 selectMatch(0, m_iSearchTermLength);
855 if (m_pFindInPageWidget)
856 m_pFindInPageWidget->setMatchCountAndCurrentIndex(m_matchedCursorPosition.size(), 0);
857}
858
859void UIHelpViewer::sltSelectPreviousMatch()
860{
861 m_iSelectedMatchIndex = m_iSelectedMatchIndex <= 0 ? m_matchedCursorPosition.size() - 1 : (m_iSelectedMatchIndex - 1);
862 selectMatch(m_iSelectedMatchIndex, m_iSearchTermLength);
863 if (m_pFindInPageWidget)
864 m_pFindInPageWidget->setMatchCountAndCurrentIndex(m_matchedCursorPosition.size(), m_iSelectedMatchIndex);
865}
866
867void UIHelpViewer::sltSelectNextMatch()
868{
869 m_iSelectedMatchIndex = m_iSelectedMatchIndex >= m_matchedCursorPosition.size() - 1 ? 0 : (m_iSelectedMatchIndex + 1);
870 selectMatch(m_iSelectedMatchIndex, m_iSearchTermLength);
871 if (m_pFindInPageWidget)
872 m_pFindInPageWidget->setMatchCountAndCurrentIndex(m_matchedCursorPosition.size(), m_iSelectedMatchIndex);
873}
874
875void UIHelpViewer::iterateDocumentImages()
876{
877 m_imageMap.clear();
878 QTextCursor cursor = textCursor();
879 cursor.movePosition(QTextCursor::Start);
880 while (!cursor.atEnd())
881 {
882 cursor.movePosition(QTextCursor::NextCharacter);
883 if (cursor.charFormat().isImageFormat())
884 {
885 QTextImageFormat imageFormat = cursor.charFormat().toImageFormat();
886 /* There seems to be two cursors per image. Use the first one: */
887 if (m_imageMap.contains(imageFormat.name()))
888 continue;
889 QHash<QString, DocumentImage>::iterator iterator = m_imageMap.insert(imageFormat.name(), DocumentImage());
890 DocumentImage &image = iterator.value();
891 image.m_fInitialWidth = imageFormat.width();
892 image.m_strName = imageFormat.name();
893 image.m_textCursor = cursor;
894 QUrl imageFileUrl;
895 foreach (const QUrl &fileUrl, m_helpFileList)
896 {
897 if (fileUrl.toString().contains(imageFormat.name(), Qt::CaseInsensitive))
898 {
899 imageFileUrl = fileUrl;
900 break;
901 }
902 }
903 if (imageFileUrl.isValid())
904 {
905 QByteArray fileData = m_pHelpEngine->fileData(imageFileUrl);
906 if (!fileData.isEmpty())
907 image.m_pixmap.loadFromData(fileData,"PNG");
908 }
909 }
910 }
911}
912
913void UIHelpViewer::scaleFont()
914{
915 QFont mFont = font();
916 mFont.setPointSize(m_iInitialFontPointSize * m_iZoomPercentage / 100.);
917 setFont(mFont);
918}
919
920void UIHelpViewer::scaleImages()
921{
922 for (QHash<QString, DocumentImage>::iterator iterator = m_imageMap.begin();
923 iterator != m_imageMap.end(); ++iterator)
924 {
925 DocumentImage &image = *iterator;
926 QTextCursor cursor = image.m_textCursor;
927 QTextCharFormat format = cursor.charFormat();
928 if (!format.isImageFormat())
929 continue;
930 QTextImageFormat imageFormat = format.toImageFormat();
931 image.m_fScaledWidth = image.m_fInitialWidth * m_iZoomPercentage / 100.;
932 imageFormat.setWidth(image.m_fScaledWidth);
933 cursor.deletePreviousChar();
934 cursor.deleteChar();
935 cursor.insertImage(imageFormat);
936 }
937}
938
939void UIHelpViewer::clearOverlay()
940{
941 if (!m_fOverlayMode)
942 return;
943 m_overlayPixmap = QPixmap();
944 m_fOverlayMode = false;
945 if (m_pOverlayBlurEffect)
946 m_pOverlayBlurEffect->setEnabled(false);
947 emit sigOverlayModeChanged(false);
948}
949
950void UIHelpViewer::loadImageAtPosition(const QPoint &globalPosition)
951{
952 clearOverlay();
953 QPoint viewportCoordinates = viewport()->mapFromGlobal(globalPosition);
954 QTextCursor cursor = cursorForPosition(viewportCoordinates);
955 if (!cursor.charFormat().isImageFormat())
956 return;
957 /* Dont zoom into image if mouse button released after a mouse drag: */
958 if (textCursor().hasSelection())
959 return;
960
961 QTextImageFormat imageFormat = cursor.charFormat().toImageFormat();
962 QUrl imageFileUrl;
963 foreach (const QUrl &fileUrl, m_helpFileList)
964 {
965 if (fileUrl.toString().contains(imageFormat.name(), Qt::CaseInsensitive))
966 {
967 imageFileUrl = fileUrl;
968 break;
969 }
970 }
971
972 if (!imageFileUrl.isValid())
973 return;
974 QByteArray fileData = m_pHelpEngine->fileData(imageFileUrl);
975 if (!fileData.isEmpty())
976 {
977 m_overlayPixmap.loadFromData(fileData,"PNG");
978 if (!m_overlayPixmap.isNull())
979 {
980 m_fOverlayMode = true;
981 if (m_pOverlayBlurEffect)
982 m_pOverlayBlurEffect->setEnabled(true);
983 viewport()->setCursor(m_defaultCursor);
984 emit sigOverlayModeChanged(true);
985 }
986 }
987}
988
989#include "UIHelpViewer.moc"
990
991#endif /* #ifdef VBOX_WITH_QHELP_VIEWER */
Note: See TracBrowser for help on using the repository browser.

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