|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2009 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a window for showing the QtHelp index. |
|
8 """ |
|
9 |
|
10 from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QUrl, QEvent |
|
11 from PyQt5.QtWidgets import ( |
|
12 QWidget, QVBoxLayout, QLabel, QLineEdit, QMenu, QDialog, QApplication |
|
13 ) |
|
14 |
|
15 |
|
16 class HelpIndexWidget(QWidget): |
|
17 """ |
|
18 Class implementing a window for showing the QtHelp index. |
|
19 |
|
20 @signal escapePressed() emitted when the ESC key was pressed |
|
21 @signal openUrl(QUrl, str) emitted to open an entry in the current tab |
|
22 @signal newTab(QUrl, str) emitted to open an entry in a new tab |
|
23 @signal newBackgroundTab(QUrl, str) emitted to open an entry in a |
|
24 new background tab |
|
25 @signal newWindow(QUrl, str) emitted to open an entry in a new window |
|
26 """ |
|
27 escapePressed = pyqtSignal() |
|
28 openUrl = pyqtSignal(QUrl) |
|
29 newTab = pyqtSignal(QUrl) |
|
30 newBackgroundTab = pyqtSignal(QUrl) |
|
31 newWindow = pyqtSignal(QUrl) |
|
32 |
|
33 def __init__(self, engine, parent=None): |
|
34 """ |
|
35 Constructor |
|
36 |
|
37 @param engine reference to the help engine (QHelpEngine) |
|
38 @param parent reference to the parent widget (QWidget) |
|
39 """ |
|
40 super().__init__(parent) |
|
41 |
|
42 self.__engine = engine |
|
43 |
|
44 self.__searchEdit = None |
|
45 self.__index = None |
|
46 |
|
47 self.__layout = QVBoxLayout(self) |
|
48 label = QLabel(self.tr("&Look for:")) |
|
49 self.__layout.addWidget(label) |
|
50 |
|
51 self.__searchEdit = QLineEdit() |
|
52 label.setBuddy(self.__searchEdit) |
|
53 self.__searchEdit.textChanged.connect(self.__filterIndices) |
|
54 self.__searchEdit.installEventFilter(self) |
|
55 self.__layout.addWidget(self.__searchEdit) |
|
56 |
|
57 self.__index = self.__engine.indexWidget() |
|
58 self.__index.setContextMenuPolicy( |
|
59 Qt.ContextMenuPolicy.CustomContextMenu) |
|
60 |
|
61 self.__engine.indexModel().indexCreationStarted.connect( |
|
62 self.__disableSearchEdit) |
|
63 self.__engine.indexModel().indexCreated.connect( |
|
64 self.__enableSearchEdit) |
|
65 self.__index.linkActivated.connect(self.__linkActivated) |
|
66 self.__index.linksActivated.connect(self.__linksActivated) |
|
67 self.__index.customContextMenuRequested.connect( |
|
68 self.__showContextMenu) |
|
69 self.__searchEdit.returnPressed.connect( |
|
70 self.__index.activateCurrentItem) |
|
71 self.__layout.addWidget(self.__index) |
|
72 |
|
73 @pyqtSlot(QUrl, str) |
|
74 def __linkActivated(self, url, keyword, modifiers=None): |
|
75 """ |
|
76 Private slot to handle the activation of a keyword entry. |
|
77 |
|
78 @param url URL of the selected entry |
|
79 @type QUrl |
|
80 @param keyword keyword for the URL |
|
81 @type str |
|
82 @param modifiers keyboard modifiers |
|
83 @type Qt.KeyboardModifiers or None |
|
84 """ |
|
85 if modifiers is None: |
|
86 modifiers = QApplication.keyboardModifiers() |
|
87 if not url.isEmpty() and url.isValid(): |
|
88 if ( |
|
89 modifiers & ( |
|
90 Qt.KeyboardModifier.ControlModifier | |
|
91 Qt.KeyboardModifier.ShiftModifier |
|
92 ) == ( |
|
93 Qt.KeyboardModifier.ControlModifier | |
|
94 Qt.KeyboardModifier.ShiftModifier |
|
95 ) |
|
96 ): |
|
97 self.newBackgroundTab.emit(url) |
|
98 elif modifiers & Qt.KeyboardModifier.ControlModifier: |
|
99 self.newTab.emit(url) |
|
100 elif modifiers & Qt.KeyboardModifier.ShiftModifier: |
|
101 self.newWindow.emit(url) |
|
102 else: |
|
103 self.openUrl.emit(url) |
|
104 |
|
105 def __linksActivated(self, links, keyword): |
|
106 """ |
|
107 Private slot to handle the activation of an entry with multiple links. |
|
108 |
|
109 @param links dictionary containing the links |
|
110 @type dict of key:str and value:QUrl |
|
111 @param keyword keyword for the entry |
|
112 @type str |
|
113 """ |
|
114 modifiers = QApplication.keyboardModifiers() |
|
115 url = ( |
|
116 QUrl(links[list(links.keys())[0]]) |
|
117 if len(links) == 1 else |
|
118 self.__selectLink(links, keyword) |
|
119 ) |
|
120 self.__linkActivated(url, keyword, modifiers) |
|
121 |
|
122 def __selectLink(self, links, keyword): |
|
123 """ |
|
124 Private method to give the user a chance to select among the |
|
125 returned links. |
|
126 |
|
127 @param links dictionary of document title and URL to select from |
|
128 @type dictionary of str (key) and QUrl (value) |
|
129 @param keyword keyword for the link set |
|
130 @type str |
|
131 @return selected link |
|
132 @rtype QUrl |
|
133 """ |
|
134 link = QUrl() |
|
135 from .HelpTopicDialog import HelpTopicDialog |
|
136 dlg = HelpTopicDialog(self, keyword, links) |
|
137 if dlg.exec() == QDialog.DialogCode.Accepted: |
|
138 link = dlg.link() |
|
139 return link |
|
140 |
|
141 def __filterIndices(self, indexFilter): |
|
142 """ |
|
143 Private slot to filter the indexes according to the given filter. |
|
144 |
|
145 @param indexFilter filter to be used (string) |
|
146 """ |
|
147 if '*' in indexFilter: |
|
148 self.__index.filterIndices(indexFilter, indexFilter) |
|
149 else: |
|
150 self.__index.filterIndices(indexFilter) |
|
151 |
|
152 def __enableSearchEdit(self): |
|
153 """ |
|
154 Private slot to enable the search edit. |
|
155 """ |
|
156 self.__searchEdit.setEnabled(True) |
|
157 self.__filterIndices(self.__searchEdit.text()) |
|
158 |
|
159 def __disableSearchEdit(self): |
|
160 """ |
|
161 Private slot to enable the search edit. |
|
162 """ |
|
163 self.__searchEdit.setEnabled(False) |
|
164 |
|
165 def focusInEvent(self, evt): |
|
166 """ |
|
167 Protected method handling focus in events. |
|
168 |
|
169 @param evt reference to the focus event object (QFocusEvent) |
|
170 """ |
|
171 if evt.reason() != Qt.FocusReason.MouseFocusReason: |
|
172 self.__searchEdit.selectAll() |
|
173 self.__searchEdit.setFocus() |
|
174 |
|
175 def eventFilter(self, watched, event): |
|
176 """ |
|
177 Public method called to filter the event queue. |
|
178 |
|
179 @param watched the QObject being watched (QObject) |
|
180 @param event the event that occurred (QEvent) |
|
181 @return flag indicating whether the event was handled (boolean) |
|
182 """ |
|
183 if ( |
|
184 self.__searchEdit and watched == self.__searchEdit and |
|
185 event.type() == QEvent.Type.KeyPress |
|
186 ): |
|
187 idx = self.__index.currentIndex() |
|
188 if event.key() == Qt.Key.Key_Up: |
|
189 idx = self.__index.model().index( |
|
190 idx.row() - 1, idx.column(), idx.parent()) |
|
191 if idx.isValid(): |
|
192 self.__index.setCurrentIndex(idx) |
|
193 elif event.key() == Qt.Key.Key_Down: |
|
194 idx = self.__index.model().index( |
|
195 idx.row() + 1, idx.column(), idx.parent()) |
|
196 if idx.isValid(): |
|
197 self.__index.setCurrentIndex(idx) |
|
198 elif event.key() == Qt.Key.Key_Escape: |
|
199 self.escapePressed.emit() |
|
200 |
|
201 return QWidget.eventFilter(self, watched, event) |
|
202 |
|
203 def __showContextMenu(self, pos): |
|
204 """ |
|
205 Private slot showing the context menu. |
|
206 |
|
207 @param pos position to show the menu at (QPoint) |
|
208 """ |
|
209 idx = self.__index.indexAt(pos) |
|
210 if idx.isValid(): |
|
211 menu = QMenu() |
|
212 curTab = menu.addAction(self.tr("Open Link")) |
|
213 newTab = menu.addAction(self.tr("Open Link in New Tab")) |
|
214 newBackgroundTab = menu.addAction( |
|
215 self.tr("Open Link in Background Tab")) |
|
216 newWindow = menu.addAction(self.tr("Open Link in New Window")) |
|
217 menu.move(self.__index.mapToGlobal(pos)) |
|
218 |
|
219 act = menu.exec() |
|
220 model = self.__index.model() |
|
221 if model is not None: |
|
222 keyword = model.data(idx, Qt.ItemDataRole.DisplayRole) |
|
223 links = model.linksForKeyword(keyword) |
|
224 if len(links) == 1: |
|
225 link = QUrl(links[list(links.keys())[0]]) |
|
226 else: |
|
227 link = self.__selectLink(links, keyword) |
|
228 |
|
229 if not link.isEmpty() and link.isValid(): |
|
230 if act == curTab: |
|
231 self.openUrl.emit(link) |
|
232 elif act == newTab: |
|
233 self.newTab.emit(link) |
|
234 elif act == newBackgroundTab: |
|
235 self.newBackgroundTab.emit(link) |
|
236 elif act == newWindow: |
|
237 self.newWindow.emit(link) |