VirtualBox

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

Last change on this file since 103977 was 103977, checked in by vboxsync, 2 months ago

Apply RT_OVERRIDE/NS_OVERRIDE where required to shut up clang.

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

© 2023 Oracle
ContactPrivacy policyTerms of Use