eric7/WebBrowser/QtHelp/QtHelpDocumentationDialog.py

branch
eric7
changeset 8312
800c432b34c8
parent 8243
cc717c2ae956
child 8318
962bce857696
equal deleted inserted replaced
8311:4e8b98454baa 8312:800c432b34c8
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2009 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a dialog to manage the QtHelp documentation database.
8 """
9
10 import sqlite3
11 import contextlib
12
13 from PyQt5.QtCore import pyqtSlot, Qt, QItemSelectionModel
14 from PyQt5.QtWidgets import (
15 QDialog, QTreeWidgetItem, QListWidgetItem, QInputDialog, QLineEdit
16 )
17 from PyQt5.QtHelp import QHelpEngineCore
18
19 from E5Gui import E5MessageBox, E5FileDialog
20 from E5Gui.E5Application import e5App
21
22 from .Ui_QtHelpDocumentationDialog import Ui_QtHelpDocumentationDialog
23
24
25 class QtHelpDocumentationDialog(QDialog, Ui_QtHelpDocumentationDialog):
26 """
27 Class implementing a dialog to manage the QtHelp documentation database.
28 """
29 def __init__(self, engine, parent):
30 """
31 Constructor
32
33 @param engine reference to the help engine (QHelpEngine)
34 @param parent reference to the parent widget (QWidget)
35 """
36 super().__init__(parent)
37 self.setupUi(self)
38
39 self.__engine = engine
40 self.__mw = parent
41
42 self.__initDocumentsTab()
43 self.__initFiltersTab()
44
45 self.tabWidget.setCurrentIndex(0)
46
47 @pyqtSlot(int)
48 def on_tabWidget_currentChanged(self, index):
49 """
50 Private slot handling a change of the current tab.
51
52 @param index index of the current tab
53 @type int
54 """
55 if (
56 index != 1 and
57 (self.__hasChangedFilters() or self.__removedAttributes)
58 ):
59 yes = E5MessageBox.yesNo(
60 self,
61 self.tr("Unsaved Filter Changes"),
62 self.tr("""The page contains unsaved changes. Shall they be"""
63 """ saved?"""),
64 yesDefault=True)
65 if yes:
66 self.on_applyFilterChangesButton_clicked()
67
68 ##################################################################
69 ## Documentations Tab
70 ##################################################################
71
72 def __initDocumentsTab(self):
73 """
74 Private method to initialize the documents tab.
75 """
76 self.documentsList.clear()
77 self.removeDocumentsButton.setEnabled(False)
78
79 docs = self.__engine.registeredDocumentations()
80 self.documentsList.addItems(docs)
81
82 self.__registeredDocs = []
83 self.__unregisteredDocs = []
84 self.__tabsToClose = []
85
86 try:
87 self.__pluginHelpDocuments = (
88 e5App().getObject("PluginManager").getPluginQtHelpFiles()
89 )
90 except KeyError:
91 from PluginManager.PluginManager import PluginManager
92 pluginManager = PluginManager(self, doLoadPlugins=False)
93 pluginManager.loadDocumentationSetPlugins()
94 pluginManager.activatePlugins()
95 self.__pluginHelpDocuments = pluginManager.getPluginQtHelpFiles()
96 self.addPluginButton.setEnabled(bool(self.__pluginHelpDocuments))
97
98 @pyqtSlot()
99 def on_documentsList_itemSelectionChanged(self):
100 """
101 Private slot handling a change of the documents selection.
102 """
103 self.removeDocumentsButton.setEnabled(
104 len(self.documentsList.selectedItems()) != 0)
105
106 @pyqtSlot()
107 def on_addDocumentsButton_clicked(self):
108 """
109 Private slot to add QtHelp documents to the help database.
110 """
111 fileNames = E5FileDialog.getOpenFileNames(
112 self,
113 self.tr("Add Documentation"),
114 "",
115 self.tr("Qt Compressed Help Files (*.qch)"))
116 if not fileNames:
117 return
118
119 self.__registerDocumentations(fileNames)
120
121 @pyqtSlot()
122 def on_addPluginButton_clicked(self):
123 """
124 Private slot to add QtHelp documents provided by plug-ins to
125 the help database.
126 """
127 from .QtHelpDocumentationSelectionDialog import (
128 QtHelpDocumentationSelectionDialog
129 )
130 dlg = QtHelpDocumentationSelectionDialog(
131 self.__pluginHelpDocuments,
132 QtHelpDocumentationSelectionDialog.AddMode,
133 self)
134 if dlg.exec() == QDialog.DialogCode.Accepted:
135 documents = dlg.getData()
136 if not documents:
137 return
138
139 self.__registerDocumentations(documents)
140
141 @pyqtSlot()
142 def on_managePluginButton_clicked(self):
143 """
144 Private slot to manage the QtHelp documents provided by plug-ins.
145 """
146 from .QtHelpDocumentationSelectionDialog import (
147 QtHelpDocumentationSelectionDialog
148 )
149 dlg = QtHelpDocumentationSelectionDialog(
150 self.__pluginHelpDocuments,
151 QtHelpDocumentationSelectionDialog.ManageMode,
152 self)
153 dlg.exec()
154
155 def __registerDocumentations(self, fileNames):
156 """
157 Private method to register a given list of documentations.
158
159 @param fileNames list of documentation files to be registered
160 @type list of str
161 """
162 for fileName in fileNames:
163 ns = QHelpEngineCore.namespaceName(fileName)
164 if not ns:
165 E5MessageBox.warning(
166 self,
167 self.tr("Add Documentation"),
168 self.tr(
169 """The file <b>{0}</b> is not a valid"""
170 """ Qt Help File.""").format(fileName)
171 )
172 continue
173
174 if len(self.documentsList.findItems(
175 ns, Qt.MatchFlag.MatchFixedString
176 )):
177 E5MessageBox.warning(
178 self,
179 self.tr("Add Documentation"),
180 self.tr(
181 """The namespace <b>{0}</b> is already registered.""")
182 .format(ns)
183 )
184 continue
185
186 self.__engine.registerDocumentation(fileName)
187 self.documentsList.addItem(ns)
188 self.__registeredDocs.append(ns)
189 if ns in self.__unregisteredDocs:
190 self.__unregisteredDocs.remove(ns)
191
192 self.__initFiltersTab()
193
194 @pyqtSlot()
195 def on_removeDocumentsButton_clicked(self):
196 """
197 Private slot to remove a document from the help database.
198 """
199 res = E5MessageBox.yesNo(
200 self,
201 self.tr("Remove Documentation"),
202 self.tr(
203 """Do you really want to remove the selected documentation """
204 """sets from the database?"""))
205 if not res:
206 return
207
208 openedDocs = self.__mw.getSourceFileList()
209
210 items = self.documentsList.selectedItems()
211 for item in items:
212 ns = item.text()
213 if ns in list(openedDocs.values()):
214 res = E5MessageBox.yesNo(
215 self,
216 self.tr("Remove Documentation"),
217 self.tr(
218 """Some documents currently opened reference the """
219 """documentation you are attempting to remove. """
220 """Removing the documentation will close those """
221 """documents. Remove anyway?"""),
222 icon=E5MessageBox.Warning)
223 if not res:
224 return
225 self.__unregisteredDocs.append(ns)
226 for docId in openedDocs:
227 if openedDocs[docId] == ns and docId not in self.__tabsToClose:
228 self.__tabsToClose.append(docId)
229 itm = self.documentsList.takeItem(self.documentsList.row(item))
230 del itm
231
232 self.__engine.unregisterDocumentation(ns)
233
234 if self.documentsList.count():
235 self.documentsList.setCurrentRow(
236 0, QItemSelectionModel.SelectionFlag.ClearAndSelect)
237
238 def hasDocumentationChanges(self):
239 """
240 Public slot to test the dialog for changes of configured QtHelp
241 documents.
242
243 @return flag indicating presence of changes
244 @rtype bool
245 """
246 return (
247 len(self.__registeredDocs) > 0 or
248 len(self.__unregisteredDocs) > 0
249 )
250
251 def getTabsToClose(self):
252 """
253 Public method to get the list of tabs to close.
254
255 @return list of tab ids to be closed
256 @rtype list of int
257 """
258 return self.__tabsToClose
259
260 ##################################################################
261 ## Filters Tab
262 ##################################################################
263
264 def __initFiltersTab(self):
265 """
266 Private method to initialize the filters tab.
267 """
268 self.removeFiltersButton.setEnabled(False)
269 self.removeAttributesButton.setEnabled(False)
270
271 # save the current and selected filters
272 currentFilter = self.filtersList.currentItem()
273 currentFilterText = currentFilter.text() if currentFilter else ""
274 selectedFiltersText = [
275 itm.text() for itm in self.filtersList.selectedItems()]
276
277 # save the selected attributes
278 selectedAttributesText = [
279 itm.text(0) for itm in self.attributesList.selectedItems()]
280
281 self.filtersList.clear()
282 self.attributesList.clear()
283
284 helpEngineCore = QHelpEngineCore(self.__engine.collectionFile())
285
286 self.__removedFilters = []
287 self.__filterMap = {}
288 self.__filterMapBackup = {}
289 self.__removedAttributes = []
290
291 for customFilter in helpEngineCore.customFilters():
292 atts = helpEngineCore.filterAttributes(customFilter)
293 self.__filterMapBackup[customFilter] = atts
294 if customFilter not in self.__filterMap:
295 self.__filterMap[customFilter] = atts
296
297 self.filtersList.addItems(sorted(self.__filterMap.keys()))
298 for attr in helpEngineCore.filterAttributes():
299 QTreeWidgetItem(self.attributesList, [attr])
300 self.attributesList.sortItems(0, Qt.SortOrder.AscendingOrder)
301
302 if selectedFiltersText or currentFilterText or selectedAttributesText:
303 # restore the selected filters
304 for txt in selectedFiltersText:
305 items = self.filtersList.findItems(
306 txt, Qt.MatchFlag.MatchExactly)
307 for itm in items:
308 itm.setSelected(True)
309 # restore the current filter
310 if currentFilterText:
311 items = self.filtersList.findItems(currentFilterText,
312 Qt.MatchFlag.MatchExactly)
313 if items:
314 self.filtersList.setCurrentItem(
315 items[0], QItemSelectionModel.SelectionFlag.NoUpdate)
316 # restore the selected attributes
317 for txt in selectedAttributesText:
318 items = self.attributesList.findItems(
319 txt, Qt.MatchFlag.MatchExactly, 0)
320 for itm in items:
321 itm.setSelected(True)
322 elif self.__filterMap:
323 self.filtersList.setCurrentRow(0)
324
325 @pyqtSlot(QListWidgetItem, QListWidgetItem)
326 def on_filtersList_currentItemChanged(self, current, previous):
327 """
328 Private slot to update the attributes depending on the current filter.
329
330 @param current reference to the current item (QListWidgetitem)
331 @param previous reference to the previous current item
332 (QListWidgetItem)
333 """
334 checkedList = []
335 if current is not None:
336 checkedList = self.__filterMap[current.text()]
337 for index in range(0, self.attributesList.topLevelItemCount()):
338 itm = self.attributesList.topLevelItem(index)
339 if itm.text(0) in checkedList:
340 itm.setCheckState(0, Qt.CheckState.Checked)
341 else:
342 itm.setCheckState(0, Qt.CheckState.Unchecked)
343
344 @pyqtSlot()
345 def on_filtersList_itemSelectionChanged(self):
346 """
347 Private slot handling a change of selected filters.
348 """
349 self.removeFiltersButton.setEnabled(
350 len(self.filtersList.selectedItems()) > 0)
351
352 @pyqtSlot(QTreeWidgetItem, int)
353 def on_attributesList_itemChanged(self, item, column):
354 """
355 Private slot to handle a change of an attribute.
356
357 @param item reference to the changed item (QTreeWidgetItem)
358 @param column column containing the change (integer)
359 """
360 if self.filtersList.currentItem() is None:
361 return
362
363 customFilter = self.filtersList.currentItem().text()
364 if customFilter not in self.__filterMap:
365 return
366
367 newAtts = []
368 for index in range(0, self.attributesList.topLevelItemCount()):
369 itm = self.attributesList.topLevelItem(index)
370 if itm.checkState(0) == Qt.CheckState.Checked:
371 newAtts.append(itm.text(0))
372 self.__filterMap[customFilter] = newAtts
373
374 @pyqtSlot()
375 def on_attributesList_itemSelectionChanged(self):
376 """
377 Private slot handling the selection of attributes.
378 """
379 self.removeAttributesButton.setEnabled(
380 len(self.attributesList.selectedItems()) != 0)
381
382 @pyqtSlot()
383 def on_addFilterButton_clicked(self):
384 """
385 Private slot to add a new filter.
386 """
387 customFilter, ok = QInputDialog.getText(
388 None,
389 self.tr("Add Filter"),
390 self.tr("Filter name:"),
391 QLineEdit.EchoMode.Normal)
392 if not customFilter:
393 return
394
395 if customFilter not in self.__filterMap:
396 self.__filterMap[customFilter] = []
397 self.filtersList.addItem(customFilter)
398
399 itm = self.filtersList.findItems(
400 customFilter, Qt.MatchFlag.MatchCaseSensitive)[0]
401 self.filtersList.setCurrentItem(itm)
402
403 @pyqtSlot()
404 def on_removeFiltersButton_clicked(self):
405 """
406 Private slot to remove the selected filters.
407 """
408 ok = E5MessageBox.yesNo(
409 self,
410 self.tr("Remove Filters"),
411 self.tr(
412 """Do you really want to remove the selected filters """
413 """from the database?"""))
414 if not ok:
415 return
416
417 items = self.filtersList.selectedItems()
418 for item in items:
419 itm = self.filtersList.takeItem(self.filtersList.row(item))
420 if itm is None:
421 continue
422
423 del self.__filterMap[itm.text()]
424 self.__removedFilters.append(itm.text())
425 del itm
426
427 if self.filtersList.count():
428 self.filtersList.setCurrentRow(
429 0, QItemSelectionModel.SelectionFlag.ClearAndSelect)
430
431 @pyqtSlot()
432 def on_removeAttributesButton_clicked(self):
433 """
434 Private slot to remove the selected filter attributes.
435 """
436 ok = E5MessageBox.yesNo(
437 self,
438 self.tr("Remove Attributes"),
439 self.tr(
440 """Do you really want to remove the selected attributes """
441 """from the database?"""))
442 if not ok:
443 return
444
445 items = self.attributesList.selectedItems()
446 for item in items:
447 itm = self.attributesList.takeTopLevelItem(
448 self.attributesList.indexOfTopLevelItem(item))
449 if itm is None:
450 continue
451
452 attr = itm.text(0)
453 self.__removedAttributes.append(attr)
454 for customFilter in self.__filterMap:
455 if attr in self.__filterMap[customFilter]:
456 self.__filterMap[customFilter].remove(attr)
457
458 del itm
459
460 @pyqtSlot()
461 def on_unusedAttributesButton_clicked(self):
462 """
463 Private slot to select all unused attributes.
464 """
465 # step 1: determine all used attributes
466 attributes = set()
467 for customFilter in self.__filterMap:
468 attributes |= set(self.__filterMap[customFilter])
469
470 # step 2: select all unused attribute items
471 self.attributesList.clearSelection()
472 for row in range(self.attributesList.topLevelItemCount()):
473 itm = self.attributesList.topLevelItem(row)
474 if itm.text(0) not in attributes:
475 itm.setSelected(True)
476
477 def __removeAttributes(self):
478 """
479 Private method to remove attributes from the Qt Help database.
480 """
481 with contextlib.suppress(sqlite3.DatabaseError):
482 self.__db = sqlite3.connect(self.__engine.collectionFile())
483
484 for attr in self.__removedAttributes:
485 self.__db.execute(
486 "DELETE FROM FilterAttributeTable WHERE Name = '{0}'" # secok
487 .format(attr))
488 self.__db.commit()
489 self.__db.close()
490
491 @pyqtSlot()
492 def on_applyFilterChangesButton_clicked(self):
493 """
494 Private slot to apply the filter changes.
495 """
496 if self.__hasChangedFilters():
497 for customFilter in self.__removedFilters:
498 self.__engine.removeCustomFilter(customFilter)
499 for customFilter in self.__filterMap:
500 self.__engine.addCustomFilter(
501 customFilter, self.__filterMap[customFilter])
502
503 if self.__removedAttributes:
504 self.__removeAttributes()
505
506 self.__initFiltersTab()
507
508 def __hasChangedFilters(self):
509 """
510 Private method to determine, if there are filter changes.
511
512 @return flag indicating the presence of filter changes
513 @rtype bool
514 """
515 filtersChanged = False
516 if len(self.__filterMapBackup) != len(self.__filterMap):
517 filtersChanged = True
518 else:
519 for customFilter in self.__filterMapBackup:
520 if customFilter not in self.__filterMap:
521 filtersChanged = True
522 else:
523 oldFilterAtts = self.__filterMapBackup[customFilter]
524 newFilterAtts = self.__filterMap[customFilter]
525 if len(oldFilterAtts) != len(newFilterAtts):
526 filtersChanged = True
527 else:
528 for attr in oldFilterAtts:
529 if attr not in newFilterAtts:
530 filtersChanged = True
531 break
532
533 if filtersChanged:
534 break
535
536 return filtersChanged
537
538 @pyqtSlot()
539 def on_resetFilterChangesButton_clicked(self):
540 """
541 Private slot to forget the filter changes and reset the tab.
542 """
543 self.__initFiltersTab()

eric ide

mercurial