src/eric7/HelpViewer/HelpViewerWidget.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 9172
4bac907a4c74
child 9221
bf71ee032bb4
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2021 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing an embedded viewer for QtHelp and local HTML files.
8 """
9
10 import os
11
12 from PyQt6.QtCore import pyqtSlot, Qt, QUrl, QTimer, QByteArray
13 from PyQt6.QtGui import QAction, QFont, QFontMetrics
14 from PyQt6.QtHelp import QHelpEngine
15 from PyQt6.QtWidgets import (
16 QWidget, QHBoxLayout, QVBoxLayout, QComboBox, QSizePolicy, QStackedWidget,
17 QToolButton, QButtonGroup, QAbstractButton, QMenu, QFrame, QLabel,
18 QProgressBar, QSplitter
19 )
20 try:
21 from PyQt6.QtWebEngineCore import QWebEngineProfile, QWebEngineSettings
22 WEBENGINE_AVAILABLE = True
23 except ImportError:
24 WEBENGINE_AVAILABLE = False
25
26 from EricWidgets import EricFileDialog, EricMessageBox
27 from EricWidgets.EricApplication import ericApp
28 from EricWidgets.EricTextEditSearchWidget import (
29 EricTextEditSearchWidget, EricTextEditType
30 )
31
32 import UI.PixmapCache
33 import Utilities
34 import Preferences
35
36 from .OpenPagesWidget import OpenPagesWidget
37 from .HelpBookmarksWidget import HelpBookmarksWidget
38
39 from WebBrowser.QtHelp.HelpTocWidget import HelpTocWidget
40 from WebBrowser.QtHelp.HelpIndexWidget import HelpIndexWidget
41 from WebBrowser.QtHelp.HelpSearchWidget import HelpSearchWidget
42
43
44 class HelpViewerWidget(QWidget):
45 """
46 Class implementing an embedded viewer for QtHelp and local HTML files.
47 """
48 MaxHistoryItems = 20 # max. number of history items to be shown
49
50 EmpytDocument_Light = (
51 '''<!DOCTYPE html>\n'''
52 '''<html lang="EN">\n'''
53 '''<head>\n'''
54 '''<style type="text/css">\n'''
55 '''html {background-color: #ffffff;}\n'''
56 '''body {background-color: #ffffff;\n'''
57 ''' color: #000000;\n'''
58 ''' margin: 10px 10px 10px 10px;\n'''
59 '''}\n'''
60 '''</style'''
61 '''</head>\n'''
62 '''<body>\n'''
63 '''</body>\n'''
64 '''</html>'''
65 )
66 EmpytDocument_Dark = (
67 '''<!DOCTYPE html>\n'''
68 '''<html lang="EN">\n'''
69 '''<head>\n'''
70 '''<style type="text/css">\n'''
71 '''html {background-color: #262626;}\n'''
72 '''body {background-color: #262626;\n'''
73 ''' color: #ffffff;\n'''
74 ''' margin: 10px 10px 10px 10px;\n'''
75 '''}\n'''
76 '''</style'''
77 '''</head>\n'''
78 '''<body>\n'''
79 '''</body>\n'''
80 '''</html>'''
81 )
82
83 def __init__(self, parent=None):
84 """
85 Constructor
86
87 @param parent reference to the parent widget (defaults to None)
88 @type QWidget (optional)
89 """
90 super().__init__(parent)
91 self.setObjectName("HelpViewerWidget")
92
93 self.__ui = parent
94
95 self.__initHelpEngine()
96
97 self.__layout = QVBoxLayout()
98 self.__layout.setObjectName("MainLayout")
99 self.__layout.setContentsMargins(0, 3, 0, 0)
100
101 ###################################################################
102 ## Help Topic Selector
103 ###################################################################
104
105 self.__selectorLayout = QHBoxLayout()
106
107 self.__helpSelector = QComboBox(self)
108 self.__helpSelector.setSizePolicy(
109 QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred)
110 self.__selectorLayout.addWidget(self.__helpSelector)
111 self.__populateHelpSelector()
112 self.__helpSelector.activated.connect(self.__helpTopicSelected)
113
114 self.__openButton = QToolButton(self)
115 self.__openButton.setIcon(UI.PixmapCache.getIcon("open"))
116 self.__openButton.setToolTip(self.tr("Open a local file"))
117 self.__openButton.clicked.connect(self.__openFile)
118 self.__selectorLayout.addWidget(self.__openButton)
119
120 self.__actionsButton = QToolButton(self)
121 self.__actionsButton.setIcon(
122 UI.PixmapCache.getIcon("actionsToolButton"))
123 self.__actionsButton.setToolTip(
124 self.tr("Select action from menu"))
125 self.__actionsButton.setPopupMode(
126 QToolButton.ToolButtonPopupMode.InstantPopup)
127 self.__selectorLayout.addWidget(self.__actionsButton)
128
129 self.__layout.addLayout(self.__selectorLayout)
130
131 ###################################################################
132 ## Navigation Buttons
133 ###################################################################
134
135 self.__navButtonsLayout = QHBoxLayout()
136
137 self.__navButtonsLayout.addStretch()
138
139 self.__backwardButton = QToolButton(self)
140 self.__backwardButton.setIcon(UI.PixmapCache.getIcon("back"))
141 self.__backwardButton.setToolTip(self.tr("Move one page backward"))
142 self.__backwardButton.clicked.connect(self.__backward)
143
144 self.__forwardButton = QToolButton(self)
145 self.__forwardButton.setIcon(UI.PixmapCache.getIcon("forward"))
146 self.__forwardButton.setToolTip(self.tr("Move one page forward"))
147 self.__forwardButton.clicked.connect(self.__forward)
148
149 self.__backForButtonLayout = QHBoxLayout()
150 self.__backForButtonLayout.setContentsMargins(0, 0, 0, 0)
151 self.__backForButtonLayout.setSpacing(0)
152 self.__backForButtonLayout.addWidget(self.__backwardButton)
153 self.__backForButtonLayout.addWidget(self.__forwardButton)
154 self.__navButtonsLayout.addLayout(self.__backForButtonLayout)
155
156 self.__reloadButton = QToolButton(self)
157 self.__reloadButton.setIcon(UI.PixmapCache.getIcon("reload"))
158 self.__reloadButton.setToolTip(self.tr("Reload the current page"))
159 self.__reloadButton.clicked.connect(self.__reload)
160 self.__navButtonsLayout.addWidget(self.__reloadButton)
161
162 self.__buttonLine1 = QFrame(self)
163 self.__buttonLine1.setFrameShape(QFrame.Shape.VLine)
164 self.__buttonLine1.setFrameShadow(QFrame.Shadow.Sunken)
165 self.__navButtonsLayout.addWidget(self.__buttonLine1)
166
167 self.__zoomInButton = QToolButton(self)
168 self.__zoomInButton.setIcon(UI.PixmapCache.getIcon("zoomIn"))
169 self.__zoomInButton.setToolTip(
170 self.tr("Zoom in on the current page"))
171 self.__zoomInButton.clicked.connect(self.__zoomIn)
172 self.__navButtonsLayout.addWidget(self.__zoomInButton)
173
174 self.__zoomOutButton = QToolButton(self)
175 self.__zoomOutButton.setIcon(UI.PixmapCache.getIcon("zoomOut"))
176 self.__zoomOutButton.setToolTip(
177 self.tr("Zoom out on the current page"))
178 self.__zoomOutButton.clicked.connect(self.__zoomOut)
179 self.__navButtonsLayout.addWidget(self.__zoomOutButton)
180
181 self.__zoomResetButton = QToolButton(self)
182 self.__zoomResetButton.setIcon(UI.PixmapCache.getIcon("zoomReset"))
183 self.__zoomResetButton.setToolTip(
184 self.tr("Reset the zoom level of the current page"))
185 self.__zoomResetButton.clicked.connect(self.__zoomReset)
186 self.__navButtonsLayout.addWidget(self.__zoomResetButton)
187
188 self.__buttonLine2 = QFrame(self)
189 self.__buttonLine2.setFrameShape(QFrame.Shape.VLine)
190 self.__buttonLine2.setFrameShadow(QFrame.Shadow.Sunken)
191 self.__navButtonsLayout.addWidget(self.__buttonLine2)
192
193 self.__addPageButton = QToolButton(self)
194 self.__addPageButton.setIcon(UI.PixmapCache.getIcon("plus"))
195 self.__addPageButton.setToolTip(
196 self.tr("Add a new empty page"))
197 self.__addPageButton.clicked.connect(self.__addNewPage)
198 self.__navButtonsLayout.addWidget(self.__addPageButton)
199
200 self.__closePageButton = QToolButton(self)
201 self.__closePageButton.setIcon(UI.PixmapCache.getIcon("minus"))
202 self.__closePageButton.setToolTip(
203 self.tr("Close the current page"))
204 self.__closePageButton.clicked.connect(self.closeCurrentPage)
205 self.__navButtonsLayout.addWidget(self.__closePageButton)
206
207 self.__buttonLine3 = QFrame(self)
208 self.__buttonLine3.setFrameShape(QFrame.Shape.VLine)
209 self.__buttonLine3.setFrameShadow(QFrame.Shadow.Sunken)
210 self.__navButtonsLayout.addWidget(self.__buttonLine3)
211
212 self.__searchButton = QToolButton(self)
213 self.__searchButton.setIcon(UI.PixmapCache.getIcon("find"))
214 self.__searchButton.setToolTip(
215 self.tr("Show or hide the search pane"))
216 self.__searchButton.setCheckable(True)
217 self.__searchButton.setChecked(False)
218 self.__searchButton.clicked.connect(self.showHideSearch)
219 self.__navButtonsLayout.addWidget(self.__searchButton)
220
221 self.__navButtonsLayout.addStretch()
222
223 self.__layout.addLayout(self.__navButtonsLayout)
224
225 self.__backMenu = QMenu(self)
226 self.__backMenu.triggered.connect(self.__navigationMenuActionTriggered)
227 self.__backwardButton.setMenu(self.__backMenu)
228 self.__backMenu.aboutToShow.connect(self.__showBackMenu)
229
230 self.__forwardMenu = QMenu(self)
231 self.__forwardMenu.triggered.connect(
232 self.__navigationMenuActionTriggered)
233 self.__forwardButton.setMenu(self.__forwardMenu)
234 self.__forwardMenu.aboutToShow.connect(self.__showForwardMenu)
235
236 ###################################################################
237 ## Center widget with help pages, search widget and navigation
238 ## widgets
239 ###################################################################
240
241 self.__centerSplitter = QSplitter(Qt.Orientation.Vertical, self)
242 self.__centerSplitter.setChildrenCollapsible(False)
243 self.__layout.addWidget(self.__centerSplitter)
244
245 self.__helpCenterWidget = QWidget(self)
246 self.__helpCenterLayout = QVBoxLayout()
247 self.__helpCenterLayout.setContentsMargins(0, 0, 0, 0)
248 self.__helpCenterWidget.setLayout(self.__helpCenterLayout)
249
250 ###################################################################
251
252 self.__helpStack = QStackedWidget(self)
253 self.__helpStack.setSizePolicy(
254 QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
255 self.__helpCenterLayout.addWidget(self.__helpStack)
256
257 ###################################################################
258
259 self.__searchWidget = EricTextEditSearchWidget(
260 self, widthForHeight=False, enableClose=True)
261 self.__helpCenterLayout.addWidget(self.__searchWidget)
262 self.__searchWidget.closePressed.connect(self.__searchWidgetClosed)
263 self.__searchWidget.hide()
264
265 self.__centerSplitter.addWidget(self.__helpCenterWidget)
266
267 ###################################################################
268
269 self.__helpNavigationStack = QStackedWidget(self)
270 self.__helpNavigationStack.setSizePolicy(
271 QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred)
272 self.__helpNavigationStack.setMinimumHeight(100)
273 self.__centerSplitter.addWidget(self.__helpNavigationStack)
274 self.__populateNavigationStack()
275
276 ###################################################################
277 ## Bottom buttons
278 ###################################################################
279
280 self.__buttonLayout = QHBoxLayout()
281
282 self.__buttonGroup = QButtonGroup(self)
283 self.__buttonGroup.setExclusive(True)
284 self.__buttonGroup.buttonClicked.connect(
285 self.__selectNavigationWidget)
286
287 self.__buttonLayout.addStretch()
288
289 self.__openPagesButton = self.__addNavigationButton(
290 "fileMisc", self.tr("Show list of open pages"))
291 self.__helpTocButton = self.__addNavigationButton(
292 "tableOfContents", self.tr("Show the table of contents"))
293 self.__helpIndexButton = self.__addNavigationButton(
294 "helpIndex", self.tr("Show the help document index"))
295 self.__helpSearchButton = self.__addNavigationButton(
296 "documentFind", self.tr("Show the help search window"))
297 self.__bookmarksButton = self.__addNavigationButton(
298 "bookmark22", self.tr("Show list of bookmarks"))
299
300 self.__buttonLayout.addStretch()
301
302 self.__helpFilterWidget = self.__initFilterWidget()
303 self.__buttonLayout.addWidget(self.__helpFilterWidget)
304
305 self.__layout.addLayout(self.__buttonLayout)
306
307 self.__indexingProgressWidget = self.__initIndexingProgress()
308 self.__layout.addWidget(self.__indexingProgressWidget)
309 self.__indexingProgressWidget.hide()
310
311 ###################################################################
312
313 self.setLayout(self.__layout)
314
315 self.__openPagesButton.setChecked(True)
316
317 self.__ui.preferencesChanged.connect(self.__populateHelpSelector)
318
319 self.__initActionsMenu()
320
321 if WEBENGINE_AVAILABLE:
322 self.__initQWebEngine()
323 self.__ui.preferencesChanged.connect(self.__initQWebEngineSettings)
324
325 self.addPage()
326 self.__checkActionButtons()
327
328 self.__centerSplitter.setSizes([900, 150])
329
330 QTimer.singleShot(50, self.__lookForNewDocumentation)
331
332 def __addNavigationButton(self, iconName, toolTip):
333 """
334 Private method to create and add a navigation button.
335
336 @param iconName name of the icon
337 @type str
338 @param toolTip tooltip to be shown
339 @type str
340 @return reference to the created button
341 @rtype QToolButton
342 """
343 button = QToolButton(self)
344 button.setIcon(UI.PixmapCache.getIcon(iconName))
345 button.setToolTip(toolTip)
346 button.setCheckable(True)
347 self.__buttonGroup.addButton(button)
348 self.__buttonLayout.addWidget(button)
349
350 return button
351
352 def __populateNavigationStack(self):
353 """
354 Private method to populate the stack of navigation widgets.
355 """
356 # Open Pages
357 self.__openPagesList = OpenPagesWidget(self.__helpStack, self)
358 self.__openPagesList.currentPageChanged.connect(
359 self.__currentPageChanged)
360 self.__helpNavigationStack.addWidget(self.__openPagesList)
361
362 # QtHelp TOC widget
363 self.__helpTocWidget = HelpTocWidget(
364 self.__helpEngine, internal=True)
365 self.__helpTocWidget.escapePressed.connect(self.__activateCurrentPage)
366 self.__helpTocWidget.openUrl.connect(self.openUrl)
367 self.__helpTocWidget.newTab.connect(self.openUrlNewPage)
368 self.__helpTocWidget.newBackgroundTab.connect(
369 self.openUrlNewBackgroundPage)
370 self.__helpNavigationStack.addWidget(self.__helpTocWidget)
371
372 # QtHelp Index widget
373 self.__helpIndexWidget = HelpIndexWidget(
374 self.__helpEngine, internal=True)
375 self.__helpIndexWidget.escapePressed.connect(
376 self.__activateCurrentPage)
377 self.__helpIndexWidget.openUrl.connect(self.openUrl)
378 self.__helpIndexWidget.newTab.connect(self.openUrlNewPage)
379 self.__helpIndexWidget.newBackgroundTab.connect(
380 self.openUrlNewBackgroundPage)
381 self.__helpNavigationStack.addWidget(self.__helpIndexWidget)
382
383 # QtHelp Search widget
384 self.__indexing = False
385 self.__indexingProgress = None
386 self.__helpSearchEngine = self.__helpEngine.searchEngine()
387 self.__helpSearchEngine.indexingStarted.connect(
388 self.__indexingStarted)
389 self.__helpSearchEngine.indexingFinished.connect(
390 self.__indexingFinished)
391
392 self.__helpSearchWidget = HelpSearchWidget(
393 self.__helpSearchEngine, internal=True)
394 self.__helpSearchWidget.escapePressed.connect(
395 self.__activateCurrentPage)
396 self.__helpSearchWidget.openUrl.connect(self.openUrl)
397 self.__helpSearchWidget.newTab.connect(self.openUrlNewPage)
398 self.__helpSearchWidget.newBackgroundTab.connect(
399 self.openUrlNewBackgroundPage)
400 self.__helpNavigationStack.addWidget(self.__helpSearchWidget)
401
402 # Bookmarks widget
403 self.__bookmarksList = HelpBookmarksWidget(self)
404 self.__bookmarksList.escapePressed.connect(self.__activateCurrentPage)
405 self.__bookmarksList.openUrl.connect(self.openUrl)
406 self.__bookmarksList.newTab.connect(self.openUrlNewPage)
407 self.__bookmarksList.newBackgroundTab.connect(
408 self.openUrlNewBackgroundPage)
409 self.__helpNavigationStack.addWidget(self.__bookmarksList)
410
411 @pyqtSlot(QAbstractButton)
412 def __selectNavigationWidget(self, button):
413 """
414 Private slot to select the navigation widget.
415
416 @param button reference to the clicked button
417 @type QAbstractButton
418 """
419 if button == self.__openPagesButton:
420 self.__helpNavigationStack.setCurrentWidget(
421 self.__openPagesList)
422 elif button == self.__helpTocButton:
423 self.__helpNavigationStack.setCurrentWidget(
424 self.__helpTocWidget)
425 elif button == self.__helpIndexButton:
426 self.__helpNavigationStack.setCurrentWidget(
427 self.__helpIndexWidget)
428 elif button == self.__helpSearchButton:
429 self.__helpNavigationStack.setCurrentWidget(
430 self.__helpSearchWidget)
431 elif button == self.__bookmarksButton:
432 self.__helpNavigationStack.setCurrentWidget(
433 self.__bookmarksList)
434
435 def __populateHelpSelector(self):
436 """
437 Private method to populate the help selection combo box.
438 """
439 self.__helpSelector.clear()
440
441 self.__helpSelector.addItem("", "")
442
443 for key, topic in [
444 ("EricDocDir", self.tr("eric API Documentation")),
445 ("PythonDocDir", self.tr("Python 3 Documentation")),
446 ("Qt5DocDir", self.tr("Qt5 Documentation")),
447 ("Qt6DocDir", self.tr("Qt6 Documentation")),
448 ("PyQt5DocDir", self.tr("PyQt5 Documentation")),
449 ("PyQt6DocDir", self.tr("PyQt6 Documentation")),
450 ("PySide2DocDir", self.tr("PySide2 Documentation")),
451 ("PySide6DocDir", self.tr("PySide6 Documentation")),
452 ]:
453 urlStr = Preferences.getHelp(key)
454 if urlStr:
455 self.__helpSelector.addItem(topic, urlStr)
456
457 @pyqtSlot()
458 def __helpTopicSelected(self):
459 """
460 Private slot handling the selection of a new help topic.
461 """
462 urlStr = self.__helpSelector.currentData()
463 if urlStr:
464 url = QUrl(urlStr)
465 self.openUrl(url)
466 else:
467 self.openUrl(QUrl("about:blank"))
468
469 def activate(self, searchWord=None, url=None):
470 """
471 Public method to activate the widget and search for a given word.
472
473 @param searchWord word to search for (defaults to None)
474 @type str (optional)
475 @param url URL to show in a new page
476 @type QUrl
477 """
478 if url is not None:
479 cv = self.currentViewer()
480 if cv and cv.isEmptyPage():
481 self.openUrl(url)
482 else:
483 self.openUrlNewPage(url)
484 else:
485 cv = self.currentViewer()
486 if cv:
487 cv.setFocus(Qt.FocusReason.OtherFocusReason)
488
489 if searchWord:
490 self.searchQtHelp(searchWord)
491
492 def shutdown(self):
493 """
494 Public method to perform shut down actions.
495 """
496 self.__helpSearchEngine.cancelIndexing()
497 self.__helpSearchEngine.cancelSearching()
498
499 self.__helpInstaller.stop()
500
501 @pyqtSlot()
502 def __openFile(self):
503 """
504 Private slot to open a local help file (*.html).
505 """
506 htmlFile = EricFileDialog.getOpenFileName(
507 self,
508 self.tr("Open HTML File"),
509 "",
510 self.tr("HTML Files (*.htm *.html);;All Files (*)")
511 )
512 if htmlFile:
513 self.currentViewer().setLink(QUrl.fromLocalFile(htmlFile))
514
515 @pyqtSlot()
516 def __addNewPage(self):
517 """
518 Private slot to add a new empty page.
519 """
520 urlStr = self.__helpSelector.currentData()
521 url = QUrl(urlStr) if bool(urlStr) else None
522 self.addPage(url=url)
523
524 def addPage(self, url=None, background=False):
525 """
526 Public method to add a new help page with the given URL.
527
528 @param url requested URL (defaults to QUrl("about:blank"))
529 @type QUrl (optional)
530 @param background flag indicating to open the page in the background
531 (defaults to False)
532 @type bool (optional)
533 @return reference to the created page
534 @rtype HelpViewerImpl
535 """
536 if url is None:
537 url = QUrl("about:blank")
538
539 viewer, viewerType = self.__newViewer()
540 viewer.setLink(url)
541
542 cv = self.currentViewer()
543 if background and bool(cv):
544 index = self.__helpStack.indexOf(cv) + 1
545 self.__helpStack.insertWidget(index, viewer)
546 self.__openPagesList.insertPage(
547 index, viewer, background=background)
548 cv.setFocus(Qt.FocusReason.OtherFocusReason)
549 else:
550 self.__helpStack.addWidget(viewer)
551 self.__openPagesList.addPage(viewer, background=background)
552 viewer.setFocus(Qt.FocusReason.OtherFocusReason)
553 self.__searchWidget.attachTextEdit(viewer, editType=viewerType)
554
555 return viewer
556
557 @pyqtSlot(QUrl)
558 def openUrl(self, url):
559 """
560 Public slot to load a URL in the current page.
561
562 @param url URL to be opened
563 @type QUrl
564 """
565 cv = self.currentViewer()
566 if cv:
567 cv.setLink(url)
568 cv.setFocus(Qt.FocusReason.OtherFocusReason)
569
570 @pyqtSlot(QUrl)
571 def openUrlNewPage(self, url):
572 """
573 Public slot to load a URL in a new page.
574
575 @param url URL to be opened
576 @type QUrl
577 """
578 self.addPage(url=url)
579
580 @pyqtSlot(QUrl)
581 def openUrlNewBackgroundPage(self, url):
582 """
583 Public slot to load a URL in a new background page.
584
585 @param url URL to be opened
586 @type QUrl
587 """
588 self.addPage(url=url, background=True)
589
590 @pyqtSlot()
591 def closeCurrentPage(self):
592 """
593 Public slot to close the current page.
594 """
595 self.__openPagesList.closeCurrentPage()
596
597 @pyqtSlot()
598 def closeOtherPages(self):
599 """
600 Public slot to close all other pages.
601 """
602 self.__openPagesList.closeOtherPages()
603
604 @pyqtSlot()
605 def closeAllPages(self):
606 """
607 Public slot to close all pages.
608 """
609 self.__openPagesList.closeAllPages()
610
611 @pyqtSlot()
612 def __activateCurrentPage(self):
613 """
614 Private slot to activate the current page.
615 """
616 cv = self.currentViewer()
617 if cv:
618 cv.setFocus()
619
620 def __newViewer(self):
621 """
622 Private method to create a new help viewer.
623
624 @return tuple containing the reference to the created help viewer
625 object and its type
626 @rtype tuple of (HelpViewerImpl, EricTextEditType)
627 """
628 if WEBENGINE_AVAILABLE:
629 from .HelpViewerImplQWE import HelpViewerImplQWE
630 viewer = HelpViewerImplQWE(self.__helpEngine, self)
631 viewerType = EricTextEditType.QWEBENGINEVIEW
632 else:
633 from .HelpViewerImplQTB import HelpViewerImplQTB
634 viewer = HelpViewerImplQTB(self.__helpEngine, self)
635 viewerType = EricTextEditType.QTEXTBROWSER
636
637 viewer.zoomChanged.connect(self.__checkActionButtons)
638
639 return viewer, viewerType
640
641 def currentViewer(self):
642 """
643 Public method to get the active viewer.
644
645 @return reference to the active help viewer
646 @rtype HelpViewerImpl
647 """
648 return self.__helpStack.currentWidget()
649
650 def bookmarkPage(self, title, url):
651 """
652 Public method to bookmark a page with the given data.
653
654 @param title title of the page
655 @type str
656 @param url URL of the page
657 @type QUrl
658 """
659 self.__bookmarksList.addBookmark(title, url)
660
661 #######################################################################
662 ## QtHelp related code below
663 #######################################################################
664
665 def __initHelpEngine(self):
666 """
667 Private method to initialize the QtHelp related stuff.
668 """
669 self.__helpEngine = QHelpEngine(
670 self.__getQtHelpCollectionFileName(),
671 self)
672 self.__helpEngine.setReadOnly(False)
673 self.__helpEngine.setUsesFilterEngine(True)
674
675 self.__helpEngine.warning.connect(self.__warning)
676
677 self.__helpEngine.setupData()
678 self.__removeOldDocumentation()
679
680 def __getQtHelpCollectionFileName(self):
681 """
682 Private method to determine the name of the QtHelp collection file.
683
684 @return path of the QtHelp collection file
685 @rtype str
686 """
687 qthelpDir = os.path.join(Utilities.getConfigDir(), "qthelp")
688 if not os.path.exists(qthelpDir):
689 os.makedirs(qthelpDir)
690 return os.path.join(qthelpDir, "eric7help.qhc")
691
692 @pyqtSlot(str)
693 def __warning(self, msg):
694 """
695 Private slot handling warnings of the help engine.
696
697 @param msg message sent by the help engine
698 @type str
699 """
700 EricMessageBox.warning(
701 self,
702 self.tr("Help Engine"), msg)
703
704 @pyqtSlot()
705 def __removeOldDocumentation(self):
706 """
707 Private slot to remove non-existing documentation from the help engine.
708 """
709 for namespace in self.__helpEngine.registeredDocumentations():
710 docFile = self.__helpEngine.documentationFileName(namespace)
711 if not os.path.exists(docFile):
712 self.__helpEngine.unregisterDocumentation(namespace)
713
714 @pyqtSlot()
715 def __lookForNewDocumentation(self):
716 """
717 Private slot to look for new documentation to be loaded into the
718 help database.
719 """
720 from WebBrowser.QtHelp.HelpDocsInstaller import HelpDocsInstaller
721 self.__helpInstaller = HelpDocsInstaller(
722 self.__helpEngine.collectionFile())
723 self.__helpInstaller.errorMessage.connect(
724 self.__showInstallationError)
725 self.__helpInstaller.docsInstalled.connect(self.__docsInstalled)
726
727 self.__ui.statusBar().showMessage(
728 self.tr("Looking for Documentation..."))
729 self.__helpInstaller.installDocs()
730
731 @pyqtSlot(str)
732 def __showInstallationError(self, message):
733 """
734 Private slot to show installation errors.
735
736 @param message message to be shown
737 @type str
738 """
739 EricMessageBox.warning(
740 self,
741 self.tr("eric Help Viewer"),
742 message)
743
744 @pyqtSlot(bool)
745 def __docsInstalled(self, installed):
746 """
747 Private slot handling the end of documentation installation.
748
749 @param installed flag indicating that documents were installed
750 @type bool
751 """
752 self.__ui.statusBar().clearMessage()
753 self.__helpEngine.setupData()
754
755 #######################################################################
756 ## Actions Menu related methods
757 #######################################################################
758
759 def __initActionsMenu(self):
760 """
761 Private method to initialize the actions menu.
762 """
763 self.__actionsMenu = QMenu()
764 self.__actionsMenu.setToolTipsVisible(True)
765
766 self.__actionsMenu.addAction(
767 self.tr("Manage QtHelp Documents"),
768 self.__manageQtHelpDocuments)
769 self.__actionsMenu.addAction(
770 self.tr("Reindex Documentation"),
771 self.__helpSearchEngine.reindexDocumentation)
772 self.__actionsMenu.addSeparator()
773 self.__actionsMenu.addAction(
774 self.tr("Configure Help Documentation"),
775 self.__configureHelpDocumentation)
776
777 self.__actionsButton.setMenu(self.__actionsMenu)
778
779 @pyqtSlot()
780 def __manageQtHelpDocuments(self):
781 """
782 Private slot to manage the QtHelp documentation database.
783 """
784 from WebBrowser.QtHelp.QtHelpDocumentationConfigurationDialog import (
785 QtHelpDocumentationConfigurationDialog
786 )
787 dlg = QtHelpDocumentationConfigurationDialog(
788 self.__helpEngine, self)
789 dlg.exec()
790
791 @pyqtSlot()
792 def __configureHelpDocumentation(self):
793 """
794 Private slot to open the Help Documentation configuration page.
795 """
796 self.__ui.showPreferences("helpDocumentationPage")
797
798 #######################################################################
799 ## Navigation related methods below
800 #######################################################################
801
802 @pyqtSlot()
803 def __backward(self):
804 """
805 Private slot to move one page backward.
806 """
807 cv = self.currentViewer()
808 if cv:
809 cv.backward()
810
811 @pyqtSlot()
812 def __forward(self):
813 """
814 Private slot to move one page foreward.
815 """
816 cv = self.currentViewer()
817 if cv:
818 cv.forward()
819
820 @pyqtSlot()
821 def __reload(self):
822 """
823 Private slot to reload the current page.
824 """
825 cv = self.currentViewer()
826 if cv:
827 cv.reload()
828
829 def __showBackMenu(self):
830 """
831 Private slot showing the backward navigation menu.
832 """
833 cv = self.currentViewer()
834 if cv:
835 self.__backMenu.clear()
836 backwardHistoryCount = min(cv.backwardHistoryCount(),
837 HelpViewerWidget.MaxHistoryItems)
838
839 for index in range(1, backwardHistoryCount + 1):
840 act = QAction(self)
841 act.setData(-index)
842 act.setText(cv.historyTitle(-index))
843 self.__backMenu.addAction(act)
844
845 self.__backMenu.addSeparator()
846 self.__backMenu.addAction(self.tr("Clear History"),
847 self.__clearHistory)
848
849 def __showForwardMenu(self):
850 """
851 Private slot showing the forward navigation menu.
852 """
853 cv = self.currentViewer()
854 if cv:
855 self.__forwardMenu.clear()
856 forwardHistoryCount = min(cv.forwardHistoryCount(),
857 HelpViewerWidget.MaxHistoryItems)
858
859 for index in range(1, forwardHistoryCount + 1):
860 act = QAction(self)
861 act.setData(index)
862 act.setText(cv.historyTitle(index))
863 self.__forwardMenu.addAction(act)
864
865 self.__forwardMenu.addSeparator()
866 self.__forwardMenu.addAction(self.tr("Clear History"),
867 self.__clearHistory)
868
869 def __navigationMenuActionTriggered(self, act):
870 """
871 Private slot to go to the selected page.
872
873 @param act reference to the action selected in the navigation menu
874 @type QAction
875 """
876 cv = self.currentViewer()
877 if cv:
878 index = act.data()
879 if index is not None:
880 cv.gotoHistory(index)
881
882 def __clearHistory(self):
883 """
884 Private slot to clear the history of the current viewer.
885 """
886 cv = self.currentViewer()
887 if cv:
888 cv.clearHistory()
889 self.__checkActionButtons()
890
891 #######################################################################
892 ## Page navigation related methods below
893 #######################################################################
894
895 @pyqtSlot()
896 def __checkActionButtons(self):
897 """
898 Private slot to set the enabled state of the action buttons.
899 """
900 cv = self.currentViewer()
901 if cv:
902 self.__backwardButton.setEnabled(cv.isBackwardAvailable())
903 self.__forwardButton.setEnabled(cv.isForwardAvailable())
904 self.__zoomInButton.setEnabled(cv.isScaleUpAvailable())
905 self.__zoomOutButton.setEnabled(cv.isScaleDownAvailable())
906 else:
907 self.__backwardButton.setEnabled(False)
908 self.__forwardButton.setEnabled(False)
909 self.__zoomInButton.setEnabled(False)
910 self.__zoomOutButton.setEnabled(False)
911
912 @pyqtSlot()
913 def __currentPageChanged(self):
914 """
915 Private slot handling the selection of another page.
916 """
917 self.__checkActionButtons()
918 cv = self.currentViewer()
919 if cv:
920 self.__searchWidget.attachTextEdit(
921 cv,
922 editType=(
923 EricTextEditType.QWEBENGINEVIEW
924 if WEBENGINE_AVAILABLE else
925 EricTextEditType.QTEXTBROWSER
926 )
927 )
928 cv.setFocus(Qt.FocusReason.OtherFocusReason)
929
930 #######################################################################
931 ## Zoom related methods below
932 #######################################################################
933
934 @pyqtSlot()
935 def __zoomIn(self):
936 """
937 Private slot to zoom in.
938 """
939 cv = self.currentViewer()
940 if cv:
941 cv.scaleUp()
942
943 @pyqtSlot()
944 def __zoomOut(self):
945 """
946 Private slot to zoom out.
947 """
948 cv = self.currentViewer()
949 if cv:
950 cv.scaleDown()
951
952 @pyqtSlot()
953 def __zoomReset(self):
954 """
955 Private slot to reset the zoom level.
956 """
957 cv = self.currentViewer()
958 if cv:
959 cv.resetScale()
960
961 #######################################################################
962 ## QtHelp Search related methods below
963 #######################################################################
964
965 def __initIndexingProgress(self):
966 """
967 Private method to initialize the help documents indexing progress
968 widget.
969
970 @return reference to the generated widget
971 @rtype QWidget
972 """
973 progressWidget = QWidget(self)
974 layout = QHBoxLayout(progressWidget)
975 layout.setContentsMargins(0, 0, 0, 0)
976
977 label = QLabel(self.tr("Updating search index"))
978 layout.addWidget(label)
979
980 progressBar = QProgressBar()
981 progressBar.setRange(0, 0)
982 progressBar.setTextVisible(False)
983 progressBar.setFixedHeight(16)
984 layout.addWidget(progressBar)
985
986 return progressWidget
987
988 @pyqtSlot()
989 def __indexingStarted(self):
990 """
991 Private slot handling the start of the indexing process.
992 """
993 self.__indexing = True
994 self.__indexingProgressWidget.show()
995
996 @pyqtSlot()
997 def __indexingFinished(self):
998 """
999 Private slot handling the end of the indexing process.
1000 """
1001 self.__indexingProgressWidget.hide()
1002 self.__indexing = False
1003
1004 @pyqtSlot(str)
1005 def searchQtHelp(self, searchExpression):
1006 """
1007 Public slot to search for a given search expression.
1008
1009 @param searchExpression expression to search for
1010 @type str
1011 """
1012 if searchExpression:
1013 if self.__indexing:
1014 # Try again a second later
1015 QTimer.singleShot(
1016 1000,
1017 lambda: self.searchQtHelp(searchExpression)
1018 )
1019 else:
1020 self.__helpSearchButton.setChecked(True)
1021 self.__helpSearchEngine.search(searchExpression)
1022
1023 #######################################################################
1024 ## QtHelp filter related methods below
1025 #######################################################################
1026
1027 def __initFilterWidget(self):
1028 """
1029 Private method to initialize the filter selection widget.
1030
1031 @return reference to the generated widget
1032 @rtype QWidget
1033 """
1034 filterWidget = QWidget()
1035 layout = QHBoxLayout(filterWidget)
1036 layout.setContentsMargins(0, 0, 0, 0)
1037
1038 label = QLabel(self.tr("Filtered by: "))
1039 layout.addWidget(label)
1040
1041 self.__helpFilterCombo = QComboBox()
1042 comboWidth = QFontMetrics(QFont()).horizontalAdvance(
1043 "ComboBoxWithEnoughWidth")
1044 self.__helpFilterCombo.setMinimumWidth(comboWidth)
1045 layout.addWidget(self.__helpFilterCombo)
1046
1047 self.__helpEngine.setupFinished.connect(
1048 self.__setupFilterCombo, Qt.ConnectionType.QueuedConnection)
1049 self.__helpFilterCombo.currentIndexChanged.connect(
1050 self.__filterQtHelpDocumentation)
1051 self.__helpEngine.filterEngine().filterActivated.connect(
1052 self.__currentFilterChanged)
1053
1054 self.__setupFilterCombo()
1055
1056 return filterWidget
1057
1058 @pyqtSlot()
1059 def __setupFilterCombo(self):
1060 """
1061 Private slot to setup the filter combo box.
1062 """
1063 activeFilter = self.__helpFilterCombo.currentText()
1064 if not activeFilter:
1065 activeFilter = self.__helpEngine.filterEngine().activeFilter()
1066 if not activeFilter:
1067 activeFilter = self.tr("Unfiltered")
1068 allFilters = self.__helpEngine.filterEngine().filters()
1069
1070 blocked = self.__helpFilterCombo.blockSignals(True)
1071 self.__helpFilterCombo.clear()
1072 self.__helpFilterCombo.addItem(self.tr("Unfiltered"))
1073 if allFilters:
1074 self.__helpFilterCombo.insertSeparator(1)
1075 for helpFilter in sorted(allFilters):
1076 self.__helpFilterCombo.addItem(helpFilter, helpFilter)
1077 self.__helpFilterCombo.blockSignals(blocked)
1078
1079 self.__helpFilterCombo.setCurrentText(activeFilter)
1080
1081 @pyqtSlot(int)
1082 def __filterQtHelpDocumentation(self, index):
1083 """
1084 Private slot to filter the QtHelp documentation.
1085
1086 @param index index of the selected QtHelp documentation filter
1087 @type int
1088 """
1089 if self.__helpEngine:
1090 helpFilter = self.__helpFilterCombo.itemData(index)
1091 self.__helpEngine.filterEngine().setActiveFilter(helpFilter)
1092
1093 @pyqtSlot(str)
1094 def __currentFilterChanged(self, filter_):
1095 """
1096 Private slot handling a change of the active QtHelp filter.
1097
1098 @param filter_ filter name
1099 @type str
1100 """
1101 index = self.__helpFilterCombo.findData(filter_)
1102 if index < 0:
1103 index = 0
1104 self.__helpFilterCombo.setCurrentIndex(index)
1105
1106 #######################################################################
1107 ## QWebEngine related code below
1108 #######################################################################
1109
1110 def __initQWebEngine(self):
1111 """
1112 Private method to initialize global QWebEngine related objects.
1113 """
1114 self.__webProfile = QWebEngineProfile.defaultProfile()
1115 self.__webProfile.setHttpCacheType(
1116 QWebEngineProfile.HttpCacheType.MemoryHttpCache)
1117 self.__webProfile.setHttpCacheMaximumSize(0)
1118
1119 self.__initQWebEngineSettings()
1120
1121 from WebBrowser.Network.QtHelpSchemeHandler import QtHelpSchemeHandler
1122 self.__qtHelpSchemeHandler = QtHelpSchemeHandler(self.__helpEngine)
1123 self.__webProfile.installUrlSchemeHandler(
1124 QByteArray(b"qthelp"), self.__qtHelpSchemeHandler)
1125
1126 def webProfile(self):
1127 """
1128 Public method to get a reference to the global web profile object.
1129
1130 @return reference to the global web profile object
1131 @rtype QWebEngineProfile
1132 """
1133 return self.__webProfile
1134
1135 def webSettings(self):
1136 """
1137 Public method to get the web settings of the current profile.
1138
1139 @return web settings of the current profile
1140 @rtype QWebEngineSettings
1141 """
1142 return self.webProfile().settings()
1143
1144 def __initQWebEngineSettings(self):
1145 """
1146 Private method to set the global web settings.
1147 """
1148 settings = self.webSettings()
1149
1150 settings.setFontFamily(
1151 QWebEngineSettings.FontFamily.StandardFont,
1152 Preferences.getWebBrowser("StandardFontFamily"))
1153 settings.setFontFamily(
1154 QWebEngineSettings.FontFamily.FixedFont,
1155 Preferences.getWebBrowser("FixedFontFamily"))
1156 settings.setFontFamily(
1157 QWebEngineSettings.FontFamily.SerifFont,
1158 Preferences.getWebBrowser("SerifFontFamily"))
1159 settings.setFontFamily(
1160 QWebEngineSettings.FontFamily.SansSerifFont,
1161 Preferences.getWebBrowser("SansSerifFontFamily"))
1162 settings.setFontFamily(
1163 QWebEngineSettings.FontFamily.CursiveFont,
1164 Preferences.getWebBrowser("CursiveFontFamily"))
1165 settings.setFontFamily(
1166 QWebEngineSettings.FontFamily.FantasyFont,
1167 Preferences.getWebBrowser("FantasyFontFamily"))
1168
1169 settings.setFontSize(
1170 QWebEngineSettings.FontSize.DefaultFontSize,
1171 Preferences.getWebBrowser("DefaultFontSize"))
1172 settings.setFontSize(
1173 QWebEngineSettings.FontSize.DefaultFixedFontSize,
1174 Preferences.getWebBrowser("DefaultFixedFontSize"))
1175 settings.setFontSize(
1176 QWebEngineSettings.FontSize.MinimumFontSize,
1177 Preferences.getWebBrowser("MinimumFontSize"))
1178 settings.setFontSize(
1179 QWebEngineSettings.FontSize.MinimumLogicalFontSize,
1180 Preferences.getWebBrowser("MinimumLogicalFontSize"))
1181
1182 settings.setAttribute(
1183 QWebEngineSettings.WebAttribute.AutoLoadImages,
1184 Preferences.getWebBrowser("AutoLoadImages"))
1185 settings.setAttribute(
1186 QWebEngineSettings.WebAttribute.JavascriptEnabled,
1187 True)
1188 # JavaScript is needed for the web browser functionality
1189 settings.setAttribute(
1190 QWebEngineSettings.WebAttribute.JavascriptCanOpenWindows,
1191 Preferences.getWebBrowser("JavaScriptCanOpenWindows"))
1192 settings.setAttribute(
1193 QWebEngineSettings.WebAttribute.JavascriptCanAccessClipboard,
1194 Preferences.getWebBrowser("JavaScriptCanAccessClipboard"))
1195 settings.setAttribute(
1196 QWebEngineSettings.WebAttribute.PluginsEnabled,
1197 False)
1198
1199 settings.setAttribute(
1200 QWebEngineSettings.WebAttribute.LocalStorageEnabled,
1201 False)
1202 settings.setDefaultTextEncoding(
1203 Preferences.getWebBrowser("DefaultTextEncoding"))
1204
1205 settings.setAttribute(
1206 QWebEngineSettings.WebAttribute.SpatialNavigationEnabled,
1207 Preferences.getWebBrowser("SpatialNavigationEnabled"))
1208 settings.setAttribute(
1209 QWebEngineSettings.WebAttribute.LinksIncludedInFocusChain,
1210 Preferences.getWebBrowser("LinksIncludedInFocusChain"))
1211 settings.setAttribute(
1212 QWebEngineSettings.WebAttribute.LocalContentCanAccessRemoteUrls,
1213 Preferences.getWebBrowser("LocalContentCanAccessRemoteUrls"))
1214 settings.setAttribute(
1215 QWebEngineSettings.WebAttribute.LocalContentCanAccessFileUrls,
1216 Preferences.getWebBrowser("LocalContentCanAccessFileUrls"))
1217 settings.setAttribute(
1218 QWebEngineSettings.WebAttribute.XSSAuditingEnabled,
1219 Preferences.getWebBrowser("XSSAuditingEnabled"))
1220 settings.setAttribute(
1221 QWebEngineSettings.WebAttribute.ScrollAnimatorEnabled,
1222 Preferences.getWebBrowser("ScrollAnimatorEnabled"))
1223 settings.setAttribute(
1224 QWebEngineSettings.WebAttribute.ErrorPageEnabled,
1225 Preferences.getWebBrowser("ErrorPageEnabled"))
1226 settings.setAttribute(
1227 QWebEngineSettings.WebAttribute.FullScreenSupportEnabled,
1228 False)
1229 settings.setAttribute(
1230 QWebEngineSettings.WebAttribute.ScreenCaptureEnabled,
1231 Preferences.getWebBrowser("ScreenCaptureEnabled"))
1232 settings.setAttribute(
1233 QWebEngineSettings.WebAttribute.WebGLEnabled,
1234 Preferences.getWebBrowser("WebGLEnabled"))
1235 settings.setAttribute(
1236 QWebEngineSettings.WebAttribute.FocusOnNavigationEnabled,
1237 Preferences.getWebBrowser("FocusOnNavigationEnabled"))
1238 settings.setAttribute(
1239 QWebEngineSettings.WebAttribute.PrintElementBackgrounds,
1240 Preferences.getWebBrowser("PrintElementBackgrounds"))
1241 settings.setAttribute(
1242 QWebEngineSettings.WebAttribute.AllowRunningInsecureContent,
1243 Preferences.getWebBrowser("AllowRunningInsecureContent"))
1244 settings.setAttribute(
1245 QWebEngineSettings.WebAttribute.AllowGeolocationOnInsecureOrigins,
1246 Preferences.getWebBrowser("AllowGeolocationOnInsecureOrigins"))
1247 settings.setAttribute(
1248 QWebEngineSettings.WebAttribute
1249 .AllowWindowActivationFromJavaScript,
1250 Preferences.getWebBrowser(
1251 "AllowWindowActivationFromJavaScript"))
1252 settings.setAttribute(
1253 QWebEngineSettings.WebAttribute.ShowScrollBars,
1254 Preferences.getWebBrowser("ShowScrollBars"))
1255 settings.setAttribute(
1256 QWebEngineSettings.WebAttribute.PlaybackRequiresUserGesture,
1257 Preferences.getWebBrowser(
1258 "PlaybackRequiresUserGesture"))
1259 settings.setAttribute(
1260 QWebEngineSettings.WebAttribute.JavascriptCanPaste,
1261 Preferences.getWebBrowser(
1262 "JavaScriptCanPaste"))
1263 settings.setAttribute(
1264 QWebEngineSettings.WebAttribute.WebRTCPublicInterfacesOnly,
1265 False)
1266 settings.setAttribute(
1267 QWebEngineSettings.WebAttribute.DnsPrefetchEnabled,
1268 False)
1269 settings.setAttribute(
1270 QWebEngineSettings.WebAttribute.PdfViewerEnabled,
1271 Preferences.getWebBrowser(
1272 "PdfViewerEnabled"))
1273
1274 #######################################################################
1275 ## Search widget related methods below
1276 #######################################################################
1277
1278 @pyqtSlot()
1279 def __searchWidgetClosed(self):
1280 """
1281 Private slot to handle the closing of the search widget.
1282 """
1283 self.__searchButton.setChecked(False)
1284
1285 @pyqtSlot(bool)
1286 def showHideSearch(self, visible):
1287 """
1288 Public slot to show or hide the search widget.
1289
1290 @param visible flag indicating to show or hide the search widget
1291 @type bool
1292 """
1293 self.__searchWidget.setVisible(visible)
1294 if visible:
1295 self.__searchWidget.activate()
1296 else:
1297 self.__searchWidget.deactivate()
1298
1299 @pyqtSlot()
1300 def searchPrev(self):
1301 """
1302 Public slot to find the previous occurrence of the current search term.
1303 """
1304 self.__searchWidget.findPrev()
1305
1306 @pyqtSlot()
1307 def searchNext(self):
1308 """
1309 Public slot to find the next occurrence of the current search term.
1310 """
1311 self.__searchWidget.findNext()
1312
1313 #######################################################################
1314 ## Utility methods below
1315 #######################################################################
1316
1317 def openPagesCount(self):
1318 """
1319 Public method to get the count of open pages.
1320
1321 @return count of open pages
1322 @rtype int
1323 """
1324 return self.__helpStack.count()
1325
1326 @classmethod
1327 def emptyDocument(cls):
1328 """
1329 Class method to get the HTML code for an empty page.
1330
1331 @return HTML code for an empty page.
1332 @rtype str
1333 """
1334 if ericApp().usesDarkPalette():
1335 return cls.EmpytDocument_Dark
1336 else:
1337 return cls.EmpytDocument_Light

eric ide

mercurial