--- a/src/eric7/Project/ProjectBaseBrowser.py Wed Jul 13 11:16:20 2022 +0200 +++ b/src/eric7/Project/ProjectBaseBrowser.py Wed Jul 13 14:55:47 2022 +0200 @@ -11,12 +11,15 @@ import contextlib from PyQt6.QtCore import ( - QModelIndex, pyqtSignal, Qt, QCoreApplication, QItemSelectionModel, - QItemSelection, QElapsedTimer + QModelIndex, + pyqtSignal, + Qt, + QCoreApplication, + QItemSelectionModel, + QItemSelection, + QElapsedTimer, ) -from PyQt6.QtWidgets import ( - QTreeView, QApplication, QMenu, QDialog, QAbstractItemView -) +from PyQt6.QtWidgets import QTreeView, QApplication, QMenu, QDialog, QAbstractItemView from EricWidgets.EricApplication import ericApp from EricWidgets import EricMessageBox @@ -26,92 +29,92 @@ from UI.BrowserModel import BrowserDirectoryItem, BrowserFileItem from .ProjectBrowserModel import ( - ProjectBrowserSimpleDirectoryItem, ProjectBrowserDirectoryItem, - ProjectBrowserFileItem + ProjectBrowserSimpleDirectoryItem, + ProjectBrowserDirectoryItem, + ProjectBrowserFileItem, ) -from .ProjectBrowserSortFilterProxyModel import ( - ProjectBrowserSortFilterProxyModel -) +from .ProjectBrowserSortFilterProxyModel import ProjectBrowserSortFilterProxyModel class ProjectBaseBrowser(Browser): """ Baseclass implementing common functionality for the various project browsers. - + @signal closeSourceWindow(str) emitted to close a source file """ + closeSourceWindow = pyqtSignal(str) - + def __init__(self, project, type_, parent=None): """ Constructor - + @param project reference to the project object @param type_ project browser type (string) @param parent parent widget of this browser """ QTreeView.__init__(self, parent) - + self.project = project - + self._model = project.getModel() self._sortModel = ProjectBrowserSortFilterProxyModel(type_) self._sortModel.setSourceModel(self._model) self.setModel(self._sortModel) - + self.selectedItemsFilter = [ProjectBrowserFileItem] - + # contains codes for special menu entries # 1 = specials for Others browser self.specialMenuEntries = [] self.isTranslationsBrowser = False self.expandedNames = [] - + self.SelectFlags = ( - QItemSelectionModel.SelectionFlag.Select | - QItemSelectionModel.SelectionFlag.Rows + QItemSelectionModel.SelectionFlag.Select + | QItemSelectionModel.SelectionFlag.Rows ) self.DeselectFlags = ( - QItemSelectionModel.SelectionFlag.Deselect | - QItemSelectionModel.SelectionFlag.Rows + QItemSelectionModel.SelectionFlag.Deselect + | QItemSelectionModel.SelectionFlag.Rows ) - + self._activating = False - + self.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self.customContextMenuRequested.connect(self._contextMenuRequested) self.activated.connect(self._openItem) self._model.rowsInserted.connect(self.__modelRowsInserted) self._connectExpandedCollapsed() - + self._createPopupMenus() - + self.currentItemName = None - - self._init() # perform common initialization tasks - + + self._init() # perform common initialization tasks + self._keyboardSearchString = "" self._keyboardSearchTimer = QElapsedTimer() self._keyboardSearchTimer.invalidate() - - self._initHookMethods() # perform initialization of the hooks + + self._initHookMethods() # perform initialization of the hooks self.hooksMenuEntries = {} - + def _connectExpandedCollapsed(self): """ Protected method to connect the expanded and collapsed signals. """ self.expanded.connect(self._resizeColumns) self.collapsed.connect(self._resizeColumns) - + def _disconnectExpandedCollapsed(self): """ Protected method to disconnect the expanded and collapsed signals. """ self.expanded.disconnect(self._resizeColumns) self.collapsed.disconnect(self._resizeColumns) - + def _createPopupMenus(self): """ Protected overloaded method to generate the popup menus. @@ -119,104 +122,106 @@ # create the popup menu for source files self.sourceMenu = QMenu(self) self.sourceMenu.addAction( - QCoreApplication.translate('ProjectBaseBrowser', 'Open'), - self._openItem) - + QCoreApplication.translate("ProjectBaseBrowser", "Open"), self._openItem + ) + # create the popup menu for general use self.menu = QMenu(self) self.menu.addAction( - QCoreApplication.translate('ProjectBaseBrowser', 'Open'), - self._openItem) + QCoreApplication.translate("ProjectBaseBrowser", "Open"), self._openItem + ) # create the menu for multiple selected files self.multiMenu = QMenu(self) self.multiMenu.addAction( - QCoreApplication.translate('ProjectBaseBrowser', 'Open'), - self._openItem) - + QCoreApplication.translate("ProjectBaseBrowser", "Open"), self._openItem + ) + # create the background menu self.backMenu = None - + # create the directories menu self.dirMenu = None - + # create the directory for multiple selected directories self.dirMultiMenu = None - + self.menuActions = [] self.multiMenuActions = [] self.dirMenuActions = [] self.dirMultiMenuActions = [] - + self.mainMenu = None - + def _contextMenuRequested(self, coord): """ Protected slot to show the context menu. - + @param coord the position of the mouse pointer (QPoint) """ if not self.project.isOpen(): return - + cnt = self.getSelectedItemsCount() if cnt > 1: self.multiMenu.popup(self.mapToGlobal(coord)) else: index = self.indexAt(coord) - + if index.isValid(): self.menu.popup(self.mapToGlobal(coord)) else: self.backMenu and self.backMenu.popup(self.mapToGlobal(coord)) - + def _selectSingleItem(self, index): """ Protected method to select a single item. - + @param index index of item to be selected (QModelIndex) """ if index.isValid(): self.setCurrentIndex(index) self.selectionModel().select( index, - QItemSelectionModel.SelectionFlag.ClearAndSelect | - QItemSelectionModel.SelectionFlag.Rows + QItemSelectionModel.SelectionFlag.ClearAndSelect + | QItemSelectionModel.SelectionFlag.Rows, ) - + def _setItemSelected(self, index, selected): """ Protected method to set the selection status of an item. - + @param index index of item to set (QModelIndex) @param selected flag giving the new selection status (boolean) """ if index.isValid(): self.selectionModel().select( - index, selected and self.SelectFlags or self.DeselectFlags) - + index, selected and self.SelectFlags or self.DeselectFlags + ) + def _setItemRangeSelected(self, startIndex, endIndex, selected): """ Protected method to set the selection status of a range of items. - + @param startIndex start index of range of items to set (QModelIndex) @param endIndex end index of range of items to set (QModelIndex) @param selected flag giving the new selection status (boolean) """ selection = QItemSelection(startIndex, endIndex) self.selectionModel().select( - selection, selected and self.SelectFlags or self.DeselectFlags) - + selection, selected and self.SelectFlags or self.DeselectFlags + ) + def __modelRowsInserted(self, parent, start, end): """ Private slot called after rows have been inserted into the model. - + @param parent parent index of inserted rows (QModelIndex) @param start start row number (integer) @param end end row number (integer) """ self._resizeColumns(parent) - + def _projectClosed(self): """ Protected slot to handle the projectClosed signal. @@ -224,9 +229,9 @@ self.layoutDisplay() if self.backMenu is not None: self.backMenu.setEnabled(False) - + self._createPopupMenus() - + def _projectOpened(self): """ Protected slot to handle the projectOpened signal. @@ -235,58 +240,64 @@ self.sortByColumn(0, Qt.SortOrder.DescendingOrder) self.sortByColumn(0, Qt.SortOrder.AscendingOrder) self._initMenusAndVcs() - + def _initMenusAndVcs(self): """ Protected slot to initialize the menus and the Vcs interface. """ self._createPopupMenus() - + if self.backMenu is not None: self.backMenu.setEnabled(True) - + if self.project.vcs is not None: self.vcsHelper = self.project.vcs.vcsGetProjectBrowserHelper( - self, self.project, self.isTranslationsBrowser) + self, self.project, self.isTranslationsBrowser + ) self.vcsHelper.addVCSMenus( - self.mainMenu, self.multiMenu, self.backMenu, - self.dirMenu, self.dirMultiMenu) - + self.mainMenu, + self.multiMenu, + self.backMenu, + self.dirMenu, + self.dirMultiMenu, + ) + def _newProject(self): """ Protected slot to handle the newProject signal. """ # default to perform same actions as opening a project self._projectOpened() - + def _removeFile(self): """ Protected method to remove a file or files from the project. """ itmList = self.getSelectedItems() - + for itm in itmList[:]: fn = itm.fileName() self.closeSourceWindow.emit(fn) self.project.removeFile(fn) - + def _removeDir(self): """ Protected method to remove a (single) directory from the project. """ itmList = self.getSelectedItems( - [ProjectBrowserSimpleDirectoryItem, ProjectBrowserDirectoryItem]) + [ProjectBrowserSimpleDirectoryItem, ProjectBrowserDirectoryItem] + ) for itm in itmList[:]: dn = itm.dirName() self.project.removeDirectory(dn) - + def _deleteDirectory(self): """ Protected method to delete the selected directory from the project data area. """ itmList = self.getSelectedItems() - + dirs = [] fullNames = [] for itm in itmList: @@ -294,24 +305,23 @@ fullNames.append(dn) dn = self.project.getRelativePath(dn) dirs.append(dn) - - from UI.DeleteFilesConfirmationDialog import ( - DeleteFilesConfirmationDialog - ) + + from UI.DeleteFilesConfirmationDialog import DeleteFilesConfirmationDialog + dlg = DeleteFilesConfirmationDialog( self.parent(), - QCoreApplication.translate( - "ProjectBaseBrowser", "Delete directories"), + QCoreApplication.translate("ProjectBaseBrowser", "Delete directories"), QCoreApplication.translate( "ProjectBaseBrowser", - "Do you really want to delete these directories from" - " the project?"), - dirs) - + "Do you really want to delete these directories from" " the project?", + ), + dirs, + ) + if dlg.exec() == QDialog.DialogCode.Accepted: for dn in fullNames: self.project.deleteDirectory(dn) - + def _renameFile(self): """ Protected method to rename a file of the project. @@ -319,7 +329,7 @@ itm = self.model().item(self.currentIndex()) fn = itm.fileName() self.project.renameFile(fn) - + def _copyToClipboard(self): """ Protected method to copy the path of an entry to the clipboard. @@ -332,14 +342,14 @@ fn = itm.dirName() except AttributeError: fn = "" - + cb = QApplication.clipboard() cb.setText(fn) - + def selectFile(self, fn): """ Public method to highlight a node given its filename. - + @param fn filename of file to be highlighted (string) """ newfn = os.path.abspath(fn) @@ -349,13 +359,12 @@ index = self.model().mapFromSource(sindex) if index.isValid(): self._selectSingleItem(index) - self.scrollTo(index, - QAbstractItemView.ScrollHint.PositionAtTop) - + self.scrollTo(index, QAbstractItemView.ScrollHint.PositionAtTop) + def selectFileLine(self, fn, lineno): """ Public method to highlight a node given its filename. - + @param fn filename of file to be highlighted (string) @param lineno one based line number of the item (integer) """ @@ -367,7 +376,7 @@ if index.isValid(): self._selectSingleItem(index) self.scrollTo(index) - + def _expandAllDirs(self): """ Protected slot to handle the 'Expand all directories' menu action. @@ -377,18 +386,15 @@ index = self.model().index(0, 0) while index.isValid(): itm = self.model().item(index) - if ( - isinstance( - itm, - (ProjectBrowserSimpleDirectoryItem, - ProjectBrowserDirectoryItem)) and - not self.isExpanded(index) - ): + if isinstance( + itm, + (ProjectBrowserSimpleDirectoryItem, ProjectBrowserDirectoryItem), + ) and not self.isExpanded(index): self.expand(index) index = self.indexBelow(index) self.layoutDisplay() self._connectExpandedCollapsed() - + def _collapseAllDirs(self): """ Protected slot to handle the 'Collapse all directories' menu action. @@ -401,30 +407,27 @@ while index.isValid(): vindex = index index = self.indexBelow(index) - + # step 2: go up collapsing all directory items index = vindex while index.isValid(): itm = self.model().item(index) - if ( - isinstance( - itm, - (ProjectBrowserSimpleDirectoryItem, - ProjectBrowserDirectoryItem)) and - self.isExpanded(index) - ): + if isinstance( + itm, + (ProjectBrowserSimpleDirectoryItem, ProjectBrowserDirectoryItem), + ) and self.isExpanded(index): self.collapse(index) index = self.indexAbove(index) self.layoutDisplay() self._connectExpandedCollapsed() - + def _showContextMenu(self, menu): """ Protected slot called before the context menu is shown. - + It enables/disables the VCS menu entries depending on the overall VCS status and the file status. - + @param menu reference to the menu to be shown (QMenu) """ if self.project.vcs is None: @@ -432,15 +435,15 @@ act.setEnabled(True) else: self.vcsHelper.showContextMenu(menu, self.menuActions) - + def _showContextMenuMulti(self, menu): """ Protected slot called before the context menu (multiple selections) is shown. - + It enables/disables the VCS menu entries depending on the overall VCS status and the files status. - + @param menu reference to the menu to be shown (QMenu) """ if self.project.vcs is None: @@ -448,14 +451,14 @@ act.setEnabled(True) else: self.vcsHelper.showContextMenuMulti(menu, self.multiMenuActions) - + def _showContextMenuDir(self, menu): """ Protected slot called before the context menu is shown. - + It enables/disables the VCS menu entries depending on the overall VCS status and the directory status. - + @param menu reference to the menu to be shown (QMenu) """ if self.project.vcs is None: @@ -463,54 +466,53 @@ act.setEnabled(True) else: self.vcsHelper.showContextMenuDir(menu, self.dirMenuActions) - + def _showContextMenuDirMulti(self, menu): """ Protected slot called before the context menu is shown. - + It enables/disables the VCS menu entries depending on the overall VCS status and the directory status. - + @param menu reference to the menu to be shown (QMenu) """ if self.project.vcs is None: for act in self.dirMultiMenuActions: act.setEnabled(True) else: - self.vcsHelper.showContextMenuDirMulti( - menu, self.dirMultiMenuActions) - + self.vcsHelper.showContextMenuDirMulti(menu, self.dirMultiMenuActions) + def _showContextMenuBack(self, menu): """ Protected slot called before the context menu is shown. - + @param menu reference to the menu to be shown (QMenu) """ # nothing to do for now return - + def _selectEntries(self, local=True, filterList=None): """ Protected method to select entries based on their VCS status. - + @param local flag indicating local (i.e. non VCS controlled) file/directory entries should be selected (boolean) @param filterList list of classes to check against """ if self.project.vcs is None: return - + compareString = ( - QCoreApplication.translate('ProjectBaseBrowser', "local") - if local else - self.project.vcs.vcsName() + QCoreApplication.translate("ProjectBaseBrowser", "local") + if local + else self.project.vcs.vcsName() ) - + # expand all directories in order to iterate over all entries self._expandAllDirs() - + self.selectionModel().clear() - + with EricOverrideCursor(): # now iterate over all entries startIndex = None @@ -519,14 +521,8 @@ index = self.model().index(0, 0) while index.isValid(): itm = self.model().item(index) - if ( - self.wantedItem(itm, filterList) and - compareString == itm.data(1) - ): - if ( - startIndex is not None and - startIndex.parent() != index.parent() - ): + if self.wantedItem(itm, filterList) and compareString == itm.data(1): + if startIndex is not None and startIndex.parent() != index.parent(): self._setItemRangeSelected(startIndex, endIndex, True) startIndex = None selectedEntries += 1 @@ -540,28 +536,28 @@ index = self.indexBelow(index) if startIndex is not None: self._setItemRangeSelected(startIndex, endIndex, True) - + if selectedEntries == 0: EricMessageBox.information( self, - QCoreApplication.translate( - 'ProjectBaseBrowser', "Select entries"), + QCoreApplication.translate("ProjectBaseBrowser", "Select entries"), QCoreApplication.translate( - 'ProjectBaseBrowser', - """There were no matching entries found.""")) - + "ProjectBaseBrowser", """There were no matching entries found.""" + ), + ) + def selectLocalEntries(self): """ Public slot to handle the select local files context menu entries. """ self._selectEntries(local=True, filterList=[ProjectBrowserFileItem]) - + def selectVCSEntries(self): """ Public slot to handle the select VCS files context menu entries. """ self._selectEntries(local=False, filterList=[ProjectBrowserFileItem]) - + def selectLocalDirEntries(self): """ Public slot to handle the select local directories context menu @@ -569,41 +565,40 @@ """ self._selectEntries( local=True, - filterList=[ProjectBrowserSimpleDirectoryItem, - ProjectBrowserDirectoryItem]) - + filterList=[ProjectBrowserSimpleDirectoryItem, ProjectBrowserDirectoryItem], + ) + def selectVCSDirEntries(self): """ Public slot to handle the select VCS directories context menu entries. """ self._selectEntries( local=False, - filterList=[ProjectBrowserSimpleDirectoryItem, - ProjectBrowserDirectoryItem]) - + filterList=[ProjectBrowserSimpleDirectoryItem, ProjectBrowserDirectoryItem], + ) + def getExpandedItemNames(self): """ Public method to get the file/directory names of all expanded items. - + @return list of expanded items names (list of string) """ expandedNames = [] - + childIndex = self.model().index(0, 0) while childIndex.isValid(): if self.isExpanded(childIndex): with contextlib.suppress(AttributeError): - expandedNames.append( - self.model().item(childIndex).name()) + expandedNames.append(self.model().item(childIndex).name()) # only items defining the name() method are returned childIndex = self.indexBelow(childIndex) - + return expandedNames - + def expandItemsByName(self, names): """ Public method to expand items given their names. - + @param names list of item names to be expanded (list of string) """ model = self.model() @@ -616,11 +611,11 @@ break # ignore items not supporting this method childIndex = self.indexBelow(childIndex) - + def _prepareRepopulateItem(self, name): """ Protected slot to handle the prepareRepopulateItem signal. - + @param name relative name of file item to be repopulated (string) """ itm = self.currentItem() @@ -630,24 +625,23 @@ sindex = self._model.itemIndexByName(name) if not sindex.isValid(): return - + index = self.model().mapFromSource(sindex) if not index.isValid(): return - + childIndex = self.indexBelow(index) while childIndex.isValid(): if childIndex.parent() == index.parent(): break if self.isExpanded(childIndex): - self.expandedNames.append( - self.model().item(childIndex).data(0)) + self.expandedNames.append(self.model().item(childIndex).data(0)) childIndex = self.indexBelow(childIndex) - + def _completeRepopulateItem(self, name): """ Protected slot to handle the completeRepopulateItem signal. - + @param name relative name of file item to be repopulated (string) """ sindex = self._model.itemIndexByName(name) @@ -658,16 +652,16 @@ childIndex = self.indexBelow(index) while childIndex.isValid(): if ( - not childIndex.isValid() or - childIndex.parent() == index.parent() + not childIndex.isValid() + or childIndex.parent() == index.parent() ): break itm = self.model().item(childIndex) if itm is not None: itemData = itm.data(0) if ( - self.currentItemName and - self.currentItemName == itemData + self.currentItemName + and self.currentItemName == itemData ): self._selectSingleItem(childIndex) if itemData in self.expandedNames: @@ -678,96 +672,101 @@ self.expandedNames = [] self.currentItemName = None self._resort() - + def currentItem(self): """ Public method to get a reference to the current item. - + @return reference to the current item """ itm = self.model().item(self.currentIndex()) return itm - + def _keyboardSearchType(self, item): """ Protected method to check, if the item is of the correct type. - + @param item reference to the item @type BrowserItem @return flag indicating a correct type @rtype bool """ return isinstance( - item, (BrowserDirectoryItem, BrowserFileItem, - ProjectBrowserSimpleDirectoryItem, - ProjectBrowserDirectoryItem, ProjectBrowserFileItem)) - + item, + ( + BrowserDirectoryItem, + BrowserFileItem, + ProjectBrowserSimpleDirectoryItem, + ProjectBrowserDirectoryItem, + ProjectBrowserFileItem, + ), + ) + ########################################################################### ## Support for hooks below ########################################################################### - + def _initHookMethods(self): """ Protected method to initialize the hooks dictionary. - + This method should be overridden by subclasses. All supported hook methods should be initialized with a None value. The keys must be strings. """ self.hooks = {} - + def __checkHookKey(self, key): """ Private method to check a hook key. - + @param key key of the hook to check (string) @exception KeyError raised to indicate an invalid hook """ if len(self.hooks) == 0: raise KeyError("Hooks are not initialized.") - + if key not in self.hooks: raise KeyError(key) - + def addHookMethod(self, key, method): """ Public method to add a hook method to the dictionary. - + @param key for the hook method (string) @param method reference to the hook method (method object) """ self.__checkHookKey(key) self.hooks[key] = method - + def addHookMethodAndMenuEntry(self, key, method, menuEntry): """ Public method to add a hook method to the dictionary. - + @param key for the hook method (string) @param method reference to the hook method (method object) @param menuEntry entry to be shown in the context menu (string) """ self.addHookMethod(key, method) self.hooksMenuEntries[key] = menuEntry - + def removeHookMethod(self, key): """ Public method to remove a hook method from the dictionary. - + @param key for the hook method (string) """ self.__checkHookKey(key) self.hooks[key] = None if key in self.hooksMenuEntries: del self.hooksMenuEntries[key] - + ################################################################## ## Configure method below ################################################################## - + def _configure(self): """ Protected method to open the configuration dialog. """ - ericApp().getObject("UserInterface").showPreferences( - "projectBrowserPage") + ericApp().getObject("UserInterface").showPreferences("projectBrowserPage")