VirtualBox

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

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

FE/Qt: bugref:9831. Some menu work

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