--- a/eric7/WebBrowser/QtHelp/QtHelpDocumentationDialog.py Thu Jun 10 19:32:22 2021 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,545 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (c) 2009 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> -# - -""" -Module implementing a dialog to manage the QtHelp documentation database. -""" - -import sqlite3 -import contextlib - -from PyQt6.QtCore import pyqtSlot, Qt, QItemSelectionModel -from PyQt6.QtWidgets import ( - QDialog, QTreeWidgetItem, QListWidgetItem, QInputDialog, QLineEdit -) -from PyQt6.QtHelp import QHelpEngineCore - -from EricWidgets import EricMessageBox, EricFileDialog -from EricWidgets.EricApplication import ericApp - -from .Ui_QtHelpDocumentationDialog import Ui_QtHelpDocumentationDialog - - -class QtHelpDocumentationDialog(QDialog, Ui_QtHelpDocumentationDialog): - """ - Class implementing a dialog to manage the QtHelp documentation database. - """ - def __init__(self, engine, parent): - """ - Constructor - - @param engine reference to the help engine (QHelpEngine) - @param parent reference to the parent widget (QWidget) - """ - super().__init__(parent) - self.setupUi(self) - - self.__engine = engine - self.__mw = parent - - self.__initDocumentsTab() - self.__initFiltersTab() - - self.tabWidget.setCurrentIndex(0) - - @pyqtSlot(int) - def on_tabWidget_currentChanged(self, index): - """ - Private slot handling a change of the current tab. - - @param index index of the current tab - @type int - """ - if ( - index != 1 and - (self.__hasChangedFilters() or self.__removedAttributes) - ): - yes = EricMessageBox.yesNo( - self, - self.tr("Unsaved Filter Changes"), - self.tr("""The page contains unsaved changes. Shall they be""" - """ saved?"""), - yesDefault=True) - if yes: - self.on_applyFilterChangesButton_clicked() - - ################################################################## - ## Documentations Tab - ################################################################## - - def __initDocumentsTab(self): - """ - Private method to initialize the documents tab. - """ - self.documentsList.clear() - self.removeDocumentsButton.setEnabled(False) - - docs = self.__engine.registeredDocumentations() - self.documentsList.addItems(docs) - - self.__registeredDocs = [] - self.__unregisteredDocs = [] - self.__tabsToClose = [] - - try: - self.__pluginHelpDocuments = ( - ericApp().getObject("PluginManager").getPluginQtHelpFiles() - ) - except KeyError: - from PluginManager.PluginManager import PluginManager - pluginManager = PluginManager(self, doLoadPlugins=False) - pluginManager.loadDocumentationSetPlugins() - pluginManager.activatePlugins() - self.__pluginHelpDocuments = pluginManager.getPluginQtHelpFiles() - self.addPluginButton.setEnabled(bool(self.__pluginHelpDocuments)) - - @pyqtSlot() - def on_documentsList_itemSelectionChanged(self): - """ - Private slot handling a change of the documents selection. - """ - self.removeDocumentsButton.setEnabled( - len(self.documentsList.selectedItems()) != 0) - - @pyqtSlot() - def on_addDocumentsButton_clicked(self): - """ - Private slot to add QtHelp documents to the help database. - """ - fileNames = EricFileDialog.getOpenFileNames( - self, - self.tr("Add Documentation"), - "", - self.tr("Qt Compressed Help Files (*.qch)")) - if not fileNames: - return - - self.__registerDocumentations(fileNames) - - @pyqtSlot() - def on_addPluginButton_clicked(self): - """ - Private slot to add QtHelp documents provided by plug-ins to - the help database. - """ - from .QtHelpDocumentationSelectionDialog import ( - QtHelpDocumentationSelectionDialog - ) - dlg = QtHelpDocumentationSelectionDialog( - self.__pluginHelpDocuments, - QtHelpDocumentationSelectionDialog.AddMode, - self) - if dlg.exec() == QDialog.DialogCode.Accepted: - documents = dlg.getData() - if not documents: - return - - self.__registerDocumentations(documents) - - @pyqtSlot() - def on_managePluginButton_clicked(self): - """ - Private slot to manage the QtHelp documents provided by plug-ins. - """ - from .QtHelpDocumentationSelectionDialog import ( - QtHelpDocumentationSelectionDialog - ) - dlg = QtHelpDocumentationSelectionDialog( - self.__pluginHelpDocuments, - QtHelpDocumentationSelectionDialog.ManageMode, - self) - dlg.exec() - - def __registerDocumentations(self, fileNames): - """ - Private method to register a given list of documentations. - - @param fileNames list of documentation files to be registered - @type list of str - """ - for fileName in fileNames: - ns = QHelpEngineCore.namespaceName(fileName) - if not ns: - EricMessageBox.warning( - self, - self.tr("Add Documentation"), - self.tr( - """The file <b>{0}</b> is not a valid""" - """ Qt Help File.""").format(fileName) - ) - continue - - if len(self.documentsList.findItems( - ns, Qt.MatchFlag.MatchFixedString - )): - EricMessageBox.warning( - self, - self.tr("Add Documentation"), - self.tr( - """The namespace <b>{0}</b> is already registered.""") - .format(ns) - ) - continue - - self.__engine.registerDocumentation(fileName) - self.documentsList.addItem(ns) - self.__registeredDocs.append(ns) - if ns in self.__unregisteredDocs: - self.__unregisteredDocs.remove(ns) - - self.__initFiltersTab() - - @pyqtSlot() - def on_removeDocumentsButton_clicked(self): - """ - Private slot to remove a document from the help database. - """ - res = EricMessageBox.yesNo( - self, - self.tr("Remove Documentation"), - self.tr( - """Do you really want to remove the selected documentation """ - """sets from the database?""")) - if not res: - return - - openedDocs = self.__mw.getSourceFileList() - - items = self.documentsList.selectedItems() - for item in items: - ns = item.text() - if ns in list(openedDocs.values()): - res = EricMessageBox.yesNo( - self, - self.tr("Remove Documentation"), - self.tr( - """Some documents currently opened reference the """ - """documentation you are attempting to remove. """ - """Removing the documentation will close those """ - """documents. Remove anyway?"""), - icon=EricMessageBox.Warning) - if not res: - return - self.__unregisteredDocs.append(ns) - for docId in openedDocs: - if openedDocs[docId] == ns and docId not in self.__tabsToClose: - self.__tabsToClose.append(docId) - itm = self.documentsList.takeItem(self.documentsList.row(item)) - del itm - - self.__engine.unregisterDocumentation(ns) - - if self.documentsList.count(): - self.documentsList.setCurrentRow( - 0, QItemSelectionModel.SelectionFlag.ClearAndSelect) - - def hasDocumentationChanges(self): - """ - Public slot to test the dialog for changes of configured QtHelp - documents. - - @return flag indicating presence of changes - @rtype bool - """ - return ( - len(self.__registeredDocs) > 0 or - len(self.__unregisteredDocs) > 0 - ) - - def getTabsToClose(self): - """ - Public method to get the list of tabs to close. - - @return list of tab ids to be closed - @rtype list of int - """ - return self.__tabsToClose - - ################################################################## - ## Filters Tab - ################################################################## - - # TODO: change this to use the new QHelpFilterSettingsWidget class - def __initFiltersTab(self): - """ - Private method to initialize the filters tab. - """ - self.removeFiltersButton.setEnabled(False) - self.removeAttributesButton.setEnabled(False) - - # save the current and selected filters - currentFilter = self.filtersList.currentItem() - currentFilterText = currentFilter.text() if currentFilter else "" - selectedFiltersText = [ - itm.text() for itm in self.filtersList.selectedItems()] - - # save the selected attributes - selectedAttributesText = [ - itm.text(0) for itm in self.attributesList.selectedItems()] - - self.filtersList.clear() - self.attributesList.clear() - - helpEngineCore = QHelpEngineCore(self.__engine.collectionFile()) - helpFilterEngine = helpEngineCore.filterEngine() - - self.__removedFilters = [] - self.__filterMap = {} - self.__filterMapBackup = {} - self.__removedAttributes = [] - - for filterName in helpFilterEngine.filters(): - filterData = helpFilterEngine.filterData(filterName) - self.__filterMapBackup[filterName] = filterData - if filterName not in self.__filterMap: - self.__filterMap[filterName] = filterData - - self.filtersList.addItems(sorted(self.__filterMap.keys())) - for component in helpFilterEngine.availableComponents(): - QTreeWidgetItem(self.attributesList, [component]) - self.attributesList.sortItems(0, Qt.SortOrder.AscendingOrder) - - if selectedFiltersText or currentFilterText or selectedAttributesText: - # restore the selected filters - for txt in selectedFiltersText: - items = self.filtersList.findItems( - txt, Qt.MatchFlag.MatchExactly) - for itm in items: - itm.setSelected(True) - # restore the current filter - if currentFilterText: - items = self.filtersList.findItems(currentFilterText, - Qt.MatchFlag.MatchExactly) - if items: - self.filtersList.setCurrentItem( - items[0], QItemSelectionModel.SelectionFlag.NoUpdate) - # restore the selected attributes - for txt in selectedAttributesText: - items = self.attributesList.findItems( - txt, Qt.MatchFlag.MatchExactly, 0) - for itm in items: - itm.setSelected(True) - elif self.__filterMap: - self.filtersList.setCurrentRow(0) - - @pyqtSlot(QListWidgetItem, QListWidgetItem) - def on_filtersList_currentItemChanged(self, current, previous): - """ - Private slot to update the attributes depending on the current filter. - - @param current reference to the current item (QListWidgetitem) - @param previous reference to the previous current item - (QListWidgetItem) - """ - checkedList = [] - if current is not None: - checkedList = self.__filterMap[current.text()] - for index in range(0, self.attributesList.topLevelItemCount()): - itm = self.attributesList.topLevelItem(index) - if itm.text(0) in checkedList: - itm.setCheckState(0, Qt.CheckState.Checked) - else: - itm.setCheckState(0, Qt.CheckState.Unchecked) - - @pyqtSlot() - def on_filtersList_itemSelectionChanged(self): - """ - Private slot handling a change of selected filters. - """ - self.removeFiltersButton.setEnabled( - len(self.filtersList.selectedItems()) > 0) - - @pyqtSlot(QTreeWidgetItem, int) - def on_attributesList_itemChanged(self, item, column): - """ - Private slot to handle a change of an attribute. - - @param item reference to the changed item (QTreeWidgetItem) - @param column column containing the change (integer) - """ - if self.filtersList.currentItem() is None: - return - - customFilter = self.filtersList.currentItem().text() - if customFilter not in self.__filterMap: - return - - newAtts = [] - for index in range(0, self.attributesList.topLevelItemCount()): - itm = self.attributesList.topLevelItem(index) - if itm.checkState(0) == Qt.CheckState.Checked: - newAtts.append(itm.text(0)) - self.__filterMap[customFilter] = newAtts - - @pyqtSlot() - def on_attributesList_itemSelectionChanged(self): - """ - Private slot handling the selection of attributes. - """ - self.removeAttributesButton.setEnabled( - len(self.attributesList.selectedItems()) != 0) - - @pyqtSlot() - def on_addFilterButton_clicked(self): - """ - Private slot to add a new filter. - """ - customFilter, ok = QInputDialog.getText( - None, - self.tr("Add Filter"), - self.tr("Filter name:"), - QLineEdit.EchoMode.Normal) - if not customFilter: - return - - if customFilter not in self.__filterMap: - self.__filterMap[customFilter] = [] - self.filtersList.addItem(customFilter) - - itm = self.filtersList.findItems( - customFilter, Qt.MatchFlag.MatchCaseSensitive)[0] - self.filtersList.setCurrentItem(itm) - - @pyqtSlot() - def on_removeFiltersButton_clicked(self): - """ - Private slot to remove the selected filters. - """ - ok = EricMessageBox.yesNo( - self, - self.tr("Remove Filters"), - self.tr( - """Do you really want to remove the selected filters """ - """from the database?""")) - if not ok: - return - - items = self.filtersList.selectedItems() - for item in items: - itm = self.filtersList.takeItem(self.filtersList.row(item)) - if itm is None: - continue - - del self.__filterMap[itm.text()] - self.__removedFilters.append(itm.text()) - del itm - - if self.filtersList.count(): - self.filtersList.setCurrentRow( - 0, QItemSelectionModel.SelectionFlag.ClearAndSelect) - - @pyqtSlot() - def on_removeAttributesButton_clicked(self): - """ - Private slot to remove the selected filter attributes. - """ - ok = EricMessageBox.yesNo( - self, - self.tr("Remove Attributes"), - self.tr( - """Do you really want to remove the selected attributes """ - """from the database?""")) - if not ok: - return - - items = self.attributesList.selectedItems() - for item in items: - itm = self.attributesList.takeTopLevelItem( - self.attributesList.indexOfTopLevelItem(item)) - if itm is None: - continue - - attr = itm.text(0) - self.__removedAttributes.append(attr) - for customFilter in self.__filterMap: - if attr in self.__filterMap[customFilter]: - self.__filterMap[customFilter].remove(attr) - - del itm - - @pyqtSlot() - def on_unusedAttributesButton_clicked(self): - """ - Private slot to select all unused attributes. - """ - # step 1: determine all used attributes - attributes = set() - for customFilter in self.__filterMap: - attributes |= set(self.__filterMap[customFilter]) - - # step 2: select all unused attribute items - self.attributesList.clearSelection() - for row in range(self.attributesList.topLevelItemCount()): - itm = self.attributesList.topLevelItem(row) - if itm.text(0) not in attributes: - itm.setSelected(True) - - def __removeAttributes(self): - """ - Private method to remove attributes from the Qt Help database. - """ - with contextlib.suppress(sqlite3.DatabaseError): - self.__db = sqlite3.connect(self.__engine.collectionFile()) - - for attr in self.__removedAttributes: - self.__db.execute( - "DELETE FROM FilterAttributeTable WHERE Name = '{0}'" # secok - .format(attr)) - self.__db.commit() - self.__db.close() - - @pyqtSlot() - def on_applyFilterChangesButton_clicked(self): - """ - Private slot to apply the filter changes. - """ - if self.__hasChangedFilters(): - for customFilter in self.__removedFilters: - self.__engine.removeCustomFilter(customFilter) - for customFilter in self.__filterMap: - self.__engine.addFilterData( - customFilter, self.__filterMap[customFilter]) - - if self.__removedAttributes: - self.__removeAttributes() - - self.__initFiltersTab() - - def __hasChangedFilters(self): - """ - Private method to determine, if there are filter changes. - - @return flag indicating the presence of filter changes - @rtype bool - """ - filtersChanged = False - if len(self.__filterMapBackup) != len(self.__filterMap): - filtersChanged = True - else: - for customFilter in self.__filterMapBackup: - if customFilter not in self.__filterMap: - filtersChanged = True - else: - oldFilterAtts = self.__filterMapBackup[customFilter] - newFilterAtts = self.__filterMap[customFilter] - if len(oldFilterAtts) != len(newFilterAtts): - filtersChanged = True - else: - for attr in oldFilterAtts: - if attr not in newFilterAtts: - filtersChanged = True - break - - if filtersChanged: - break - - return filtersChanged - - @pyqtSlot() - def on_resetFilterChangesButton_clicked(self): - """ - Private slot to forget the filter changes and reset the tab. - """ - self.__initFiltersTab()