--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Project/ProjectBaseBrowser.py Mon Dec 28 16:03:33 2009 +0000 @@ -0,0 +1,646 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2002 - 2009 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the baseclass for the various project browsers. +""" + +import os +import sys + +from PyQt4.QtCore import * +from PyQt4.QtGui import * + +from E4Gui.E4Application import e4App + +from UI.Browser import * + +from ProjectBrowserModel import * +from ProjectBrowserSortFilterProxyModel import ProjectBrowserSortFilterProxyModel + +import UI.PixmapCache +import Preferences + +class ProjectBaseBrowser(Browser): + """ + Baseclass implementing common functionality for the various project browsers. + """ + 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.SelectionFlags(\ + QItemSelectionModel.Select | QItemSelectionModel.Rows) + self.DeselectFlags = QItemSelectionModel.SelectionFlags(\ + QItemSelectionModel.Deselect | QItemSelectionModel.Rows) + + self.setContextMenuPolicy(Qt.CustomContextMenu) + self.connect(self, SIGNAL("customContextMenuRequested(const QPoint &)"), + self._contextMenuRequested) + self.connect(self, SIGNAL("activated(const QModelIndex &)"), self._openItem) + self.connect(self._model, SIGNAL("rowsInserted(const QModelIndex &, int, int)"), + self.__modelRowsInserted) + self._connectExpandedCollapsed() + + self._createPopupMenus() + + self.currentItemName = None + + self._init() # perform common initialization tasks + + self._initHookMethods() # perform initialization of the hooks + self.hooksMenuEntries = {} + + def _connectExpandedCollapsed(self): + """ + Protected method to connect the expanded and collapsed signals. + """ + self.connect(self, SIGNAL("expanded(const QModelIndex &)"), + self._resizeColumns) + self.connect(self, SIGNAL("collapsed(const QModelIndex &)"), + self._resizeColumns) + + def _disconnectExpandedCollapsed(self): + """ + Protected method to disconnect the expanded and collapsed signals. + """ + self.disconnect(self, SIGNAL("expanded(const QModelIndex &)"), + self._resizeColumns) + self.disconnect(self, SIGNAL("collapsed(const QModelIndex &)"), + self._resizeColumns) + + def _createPopupMenus(self): + """ + Protected overloaded method to generate the popup menus. + """ + # create the popup menu for source files + self.sourceMenu = QMenu(self) + self.sourceMenu.addAction( + QApplication.translate('ProjectBaseBrowser', 'Open'), + self._openItem) + + # create the popup menu for general use + self.menu = QMenu(self) + self.menu.addAction( + QApplication.translate('ProjectBaseBrowser', 'Open'), + self._openItem) + + # create the menu for multiple selected files + self.multiMenu = QMenu(self) + self.multiMenu.addAction(QApplication.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) + flags = QItemSelectionModel.SelectionFlags(\ + QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) + self.selectionModel().select(index, flags) + + 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) + + 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) + + 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 __modelDataChanged(self, startIndex, endIndex): + """ + Private slot called after data has been changed in the model. + + @param startIndex start index of the changed data (QModelIndex) + @param endIndex end index of the changed data (QModelIndex) + """ + self._resizeColumns(startIndex) + + def _projectClosed(self): + """ + Protected slot to handle the projectClosed signal. + """ + self.layoutDisplay() + if self.backMenu is not None: + self.backMenu.setEnabled(False) + + self._createPopupMenus() + + def _projectOpened(self): + """ + Protected slot to handle the projectOpened signal. + """ + self.layoutDisplay() + self.sortByColumn(0, Qt.DescendingOrder) + self.sortByColumn(0, Qt.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.vcsHelper.addVCSMenus(self.mainMenu, self.multiMenu, + self.backMenu, self.dirMenu, self.dirMultiMenu) + + def _newProject(self): + """ + Protected slot to handle the newProject signal. + """ + self.layoutDisplay() + self.sortByColumn(0, Qt.DescendingOrder) + self.sortByColumn(0, Qt.AscendingOrder) + + 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.vcsHelper.addVCSMenus(self.mainMenu, self.multiMenu, + self.backMenu, self.dirMenu, self.dirMultiMenu) + + 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.emit(SIGNAL('closeSourceWindow'), fn) + self.project.removeFile(fn) + + def _removeDir(self): + """ + Protected method to remove a (single) directory from the project. + """ + itmList = self.getSelectedItems(\ + [ProjectBrowserSimpleDirectoryItem, ProjectBrowserDirectoryItem]) + for itm in itmList[:]: + dn = itm.dirName() + self.project.removeDirectory(dn) + + def _renameFile(self): + """ + Protected method to rename a file of the project. + """ + 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. + """ + itm = self.model().item(self.currentIndex()) + try: + fn = itm.fileName() + except AttributeError: + try: + 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) + newfn = newfn.replace(self.project.ppath + os.sep, '') + sindex = self._model.itemIndexByName(newfn) + if sindex.isValid(): + index = self.model().mapFromSource(sindex) + if index.isValid(): + self._selectSingleItem(index) + self.scrollTo(index, QAbstractItemView.PositionAtTop) + + def _expandAllDirs(self): + """ + Protected slot to handle the 'Expand all directories' menu action. + """ + self._disconnectExpandedCollapsed() + QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) + QApplication.processEvents() + index = self.model().index(0, 0) + while index.isValid(): + itm = self.model().item(index) + if (isinstance(itm, ProjectBrowserSimpleDirectoryItem) or \ + isinstance(itm, ProjectBrowserDirectoryItem)) and \ + not self.isExpanded(index): + self.expand(index) + index = self.indexBelow(index) + self.layoutDisplay() + self._connectExpandedCollapsed() + QApplication.restoreOverrideCursor() + + def _collapseAllDirs(self): + """ + Protected slot to handle the 'Collapse all directories' menu action. + """ + self._disconnectExpandedCollapsed() + QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) + QApplication.processEvents() + # step 1: find last valid index + vindex = QModelIndex() + index = self.model().index(0, 0) + 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) or \ + isinstance(itm, ProjectBrowserDirectoryItem)) and \ + self.isExpanded(index): + self.collapse(index) + index = self.indexAbove(index) + self.layoutDisplay() + self._connectExpandedCollapsed() + QApplication.restoreOverrideCursor() + + 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: + for act in self.menuActions: + 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: + for act in self.multiMenuActions: + 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: + for act in self.dirMenuActions: + 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) + + 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, filter = 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 filter list of classes to check against + """ + if self.project.vcs is None: + return + + if local: + compareString = QApplication.translate('ProjectBaseBrowser', "local") + else: + compareString = self.project.vcs.vcsName() + + # expand all directories in order to iterate over all entries + self._expandAllDirs() + + QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) + QApplication.processEvents() + self.selectionModel().clear() + QApplication.processEvents() + + # now iterate over all entries + startIndex = None + endIndex = None + selectedEntries = 0 + index = self.model().index(0, 0) + while index.isValid(): + itm = self.model().item(index) + if self.wantedItem(itm, filter) and \ + compareString == itm.data(1): + if startIndex is not None and \ + startIndex.parent() != index.parent(): + self._setItemRangeSelected(startIndex, endIndex, True) + startIndex = None + selectedEntries += 1 + if startIndex is None: + startIndex = index + endIndex = index + else: + if startIndex is not None: + self._setItemRangeSelected(startIndex, endIndex, True) + startIndex = None + index = self.indexBelow(index) + if startIndex is not None: + self._setItemRangeSelected(startIndex, endIndex, True) + QApplication.restoreOverrideCursor() + QApplication.processEvents() + + if selectedEntries == 0: + QMessageBox.information(None, + QApplication.translate('ProjectBaseBrowser', "Select entries"), + QApplication.translate('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, filter = [ProjectBrowserFileItem]) + + def selectVCSEntries(self): + """ + Public slot to handle the select VCS files context menu entries + """ + self._selectEntries(local = False, filter = [ProjectBrowserFileItem]) + + def selectLocalDirEntries(self): + """ + Public slot to handle the select local directories context menu entries + """ + self._selectEntries(local = True, + filter=[ProjectBrowserSimpleDirectoryItem, ProjectBrowserDirectoryItem]) + + def selectVCSDirEntries(self): + """ + Public slot to handle the select VCS directories context menu entries + """ + self._selectEntries(local = False, + filter=[ProjectBrowserSimpleDirectoryItem, ProjectBrowserDirectoryItem]) + + def _prepareRepopulateItem(self, name): + """ + Protected slot to handle the prepareRepopulateItem signal. + + @param name relative name of file item to be repopulated (string) + """ + QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) + QApplication.processEvents() + itm = self.currentItem() + if itm is not None: + self.currentItemName = itm.data(0) + self.expandedNames = [] + 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)) + 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) + if sindex.isValid(): + index = self.model().mapFromSource(sindex) + if index.isValid(): + childIndex = self.indexBelow(index) + while childIndex.isValid(): + if 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._selectSingleItem(childIndex) + if itemData in self.expandedNames: + self.setExpanded(childIndex, True) + childIndex = self.indexBelow(childIndex) + self.expandedNames = [] + self.currentItemName = None + QApplication.restoreOverrideCursor() + QApplication.processEvents() + 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 + + ############################################################################ + ## 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 + """ + 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. + """ + e4App().getObject("UserInterface").showPreferences("projectBrowserPage")