eric7/WebBrowser/QtHelp/QtHelpDocumentationDialog.py

branch
eric7
changeset 8422
bb5da74c1b3f
parent 8421
cd4eee7f1d28
child 8423
398d2f98da85
equal deleted inserted replaced
8421:cd4eee7f1d28 8422:bb5da74c1b3f
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 PyQt6.QtCore import pyqtSlot, Qt, QItemSelectionModel
14 from PyQt6.QtWidgets import (
15 QDialog, QTreeWidgetItem, QListWidgetItem, QInputDialog, QLineEdit
16 )
17 from PyQt6.QtHelp import QHelpEngineCore
18
19 from EricWidgets import EricMessageBox, EricFileDialog
20 from EricWidgets.EricApplication import ericApp
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 = EricMessageBox.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 ericApp().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 = EricFileDialog.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 EricMessageBox.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 EricMessageBox.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 = EricMessageBox.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 = EricMessageBox.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=EricMessageBox.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 # TODO: change this to use the new QHelpFilterSettingsWidget class
265 def __initFiltersTab(self):
266 """
267 Private method to initialize the filters tab.
268 """
269 self.removeFiltersButton.setEnabled(False)
270 self.removeAttributesButton.setEnabled(False)
271
272 # save the current and selected filters
273 currentFilter = self.filtersList.currentItem()
274 currentFilterText = currentFilter.text() if currentFilter else ""
275 selectedFiltersText = [
276 itm.text() for itm in self.filtersList.selectedItems()]
277
278 # save the selected attributes
279 selectedAttributesText = [
280 itm.text(0) for itm in self.attributesList.selectedItems()]
281
282 self.filtersList.clear()
283 self.attributesList.clear()
284
285 helpEngineCore = QHelpEngineCore(self.__engine.collectionFile())
286 helpFilterEngine = helpEngineCore.filterEngine()
287
288 self.__removedFilters = []
289 self.__filterMap = {}
290 self.__filterMapBackup = {}
291 self.__removedAttributes = []
292
293 for filterName in helpFilterEngine.filters():
294 filterData = helpFilterEngine.filterData(filterName)
295 self.__filterMapBackup[filterName] = filterData
296 if filterName not in self.__filterMap:
297 self.__filterMap[filterName] = filterData
298
299 self.filtersList.addItems(sorted(self.__filterMap.keys()))
300 for component in helpFilterEngine.availableComponents():
301 QTreeWidgetItem(self.attributesList, [component])
302 self.attributesList.sortItems(0, Qt.SortOrder.AscendingOrder)
303
304 if selectedFiltersText or currentFilterText or selectedAttributesText:
305 # restore the selected filters
306 for txt in selectedFiltersText:
307 items = self.filtersList.findItems(
308 txt, Qt.MatchFlag.MatchExactly)
309 for itm in items:
310 itm.setSelected(True)
311 # restore the current filter
312 if currentFilterText:
313 items = self.filtersList.findItems(currentFilterText,
314 Qt.MatchFlag.MatchExactly)
315 if items:
316 self.filtersList.setCurrentItem(
317 items[0], QItemSelectionModel.SelectionFlag.NoUpdate)
318 # restore the selected attributes
319 for txt in selectedAttributesText:
320 items = self.attributesList.findItems(
321 txt, Qt.MatchFlag.MatchExactly, 0)
322 for itm in items:
323 itm.setSelected(True)
324 elif self.__filterMap:
325 self.filtersList.setCurrentRow(0)
326
327 @pyqtSlot(QListWidgetItem, QListWidgetItem)
328 def on_filtersList_currentItemChanged(self, current, previous):
329 """
330 Private slot to update the attributes depending on the current filter.
331
332 @param current reference to the current item (QListWidgetitem)
333 @param previous reference to the previous current item
334 (QListWidgetItem)
335 """
336 checkedList = []
337 if current is not None:
338 checkedList = self.__filterMap[current.text()]
339 for index in range(0, self.attributesList.topLevelItemCount()):
340 itm = self.attributesList.topLevelItem(index)
341 if itm.text(0) in checkedList:
342 itm.setCheckState(0, Qt.CheckState.Checked)
343 else:
344 itm.setCheckState(0, Qt.CheckState.Unchecked)
345
346 @pyqtSlot()
347 def on_filtersList_itemSelectionChanged(self):
348 """
349 Private slot handling a change of selected filters.
350 """
351 self.removeFiltersButton.setEnabled(
352 len(self.filtersList.selectedItems()) > 0)
353
354 @pyqtSlot(QTreeWidgetItem, int)
355 def on_attributesList_itemChanged(self, item, column):
356 """
357 Private slot to handle a change of an attribute.
358
359 @param item reference to the changed item (QTreeWidgetItem)
360 @param column column containing the change (integer)
361 """
362 if self.filtersList.currentItem() is None:
363 return
364
365 customFilter = self.filtersList.currentItem().text()
366 if customFilter not in self.__filterMap:
367 return
368
369 newAtts = []
370 for index in range(0, self.attributesList.topLevelItemCount()):
371 itm = self.attributesList.topLevelItem(index)
372 if itm.checkState(0) == Qt.CheckState.Checked:
373 newAtts.append(itm.text(0))
374 self.__filterMap[customFilter] = newAtts
375
376 @pyqtSlot()
377 def on_attributesList_itemSelectionChanged(self):
378 """
379 Private slot handling the selection of attributes.
380 """
381 self.removeAttributesButton.setEnabled(
382 len(self.attributesList.selectedItems()) != 0)
383
384 @pyqtSlot()
385 def on_addFilterButton_clicked(self):
386 """
387 Private slot to add a new filter.
388 """
389 customFilter, ok = QInputDialog.getText(
390 None,
391 self.tr("Add Filter"),
392 self.tr("Filter name:"),
393 QLineEdit.EchoMode.Normal)
394 if not customFilter:
395 return
396
397 if customFilter not in self.__filterMap:
398 self.__filterMap[customFilter] = []
399 self.filtersList.addItem(customFilter)
400
401 itm = self.filtersList.findItems(
402 customFilter, Qt.MatchFlag.MatchCaseSensitive)[0]
403 self.filtersList.setCurrentItem(itm)
404
405 @pyqtSlot()
406 def on_removeFiltersButton_clicked(self):
407 """
408 Private slot to remove the selected filters.
409 """
410 ok = EricMessageBox.yesNo(
411 self,
412 self.tr("Remove Filters"),
413 self.tr(
414 """Do you really want to remove the selected filters """
415 """from the database?"""))
416 if not ok:
417 return
418
419 items = self.filtersList.selectedItems()
420 for item in items:
421 itm = self.filtersList.takeItem(self.filtersList.row(item))
422 if itm is None:
423 continue
424
425 del self.__filterMap[itm.text()]
426 self.__removedFilters.append(itm.text())
427 del itm
428
429 if self.filtersList.count():
430 self.filtersList.setCurrentRow(
431 0, QItemSelectionModel.SelectionFlag.ClearAndSelect)
432
433 @pyqtSlot()
434 def on_removeAttributesButton_clicked(self):
435 """
436 Private slot to remove the selected filter attributes.
437 """
438 ok = EricMessageBox.yesNo(
439 self,
440 self.tr("Remove Attributes"),
441 self.tr(
442 """Do you really want to remove the selected attributes """
443 """from the database?"""))
444 if not ok:
445 return
446
447 items = self.attributesList.selectedItems()
448 for item in items:
449 itm = self.attributesList.takeTopLevelItem(
450 self.attributesList.indexOfTopLevelItem(item))
451 if itm is None:
452 continue
453
454 attr = itm.text(0)
455 self.__removedAttributes.append(attr)
456 for customFilter in self.__filterMap:
457 if attr in self.__filterMap[customFilter]:
458 self.__filterMap[customFilter].remove(attr)
459
460 del itm
461
462 @pyqtSlot()
463 def on_unusedAttributesButton_clicked(self):
464 """
465 Private slot to select all unused attributes.
466 """
467 # step 1: determine all used attributes
468 attributes = set()
469 for customFilter in self.__filterMap:
470 attributes |= set(self.__filterMap[customFilter])
471
472 # step 2: select all unused attribute items
473 self.attributesList.clearSelection()
474 for row in range(self.attributesList.topLevelItemCount()):
475 itm = self.attributesList.topLevelItem(row)
476 if itm.text(0) not in attributes:
477 itm.setSelected(True)
478
479 def __removeAttributes(self):
480 """
481 Private method to remove attributes from the Qt Help database.
482 """
483 with contextlib.suppress(sqlite3.DatabaseError):
484 self.__db = sqlite3.connect(self.__engine.collectionFile())
485
486 for attr in self.__removedAttributes:
487 self.__db.execute(
488 "DELETE FROM FilterAttributeTable WHERE Name = '{0}'" # secok
489 .format(attr))
490 self.__db.commit()
491 self.__db.close()
492
493 @pyqtSlot()
494 def on_applyFilterChangesButton_clicked(self):
495 """
496 Private slot to apply the filter changes.
497 """
498 if self.__hasChangedFilters():
499 for customFilter in self.__removedFilters:
500 self.__engine.removeCustomFilter(customFilter)
501 for customFilter in self.__filterMap:
502 self.__engine.addFilterData(
503 customFilter, self.__filterMap[customFilter])
504
505 if self.__removedAttributes:
506 self.__removeAttributes()
507
508 self.__initFiltersTab()
509
510 def __hasChangedFilters(self):
511 """
512 Private method to determine, if there are filter changes.
513
514 @return flag indicating the presence of filter changes
515 @rtype bool
516 """
517 filtersChanged = False
518 if len(self.__filterMapBackup) != len(self.__filterMap):
519 filtersChanged = True
520 else:
521 for customFilter in self.__filterMapBackup:
522 if customFilter not in self.__filterMap:
523 filtersChanged = True
524 else:
525 oldFilterAtts = self.__filterMapBackup[customFilter]
526 newFilterAtts = self.__filterMap[customFilter]
527 if len(oldFilterAtts) != len(newFilterAtts):
528 filtersChanged = True
529 else:
530 for attr in oldFilterAtts:
531 if attr not in newFilterAtts:
532 filtersChanged = True
533 break
534
535 if filtersChanged:
536 break
537
538 return filtersChanged
539
540 @pyqtSlot()
541 def on_resetFilterChangesButton_clicked(self):
542 """
543 Private slot to forget the filter changes and reset the tab.
544 """
545 self.__initFiltersTab()

eric ide

mercurial