VirtualBox

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

Last change on this file since 99880 was 99880, checked in by vboxsync, 19 months ago

FE/Qt: bugref:10451 Replacing VBOX_WITH_QHELP_VIEWER with VBOX_WITH_DOCS_QHELP.

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