コード例 #1
0
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.SelectionFlags(
            QItemSelectionModel.Select | QItemSelectionModel.Rows)
        self.DeselectFlags = QItemSelectionModel.SelectionFlags(
            QItemSelectionModel.Deselect | QItemSelectionModel.Rows)

        self._activating = False

        self.setContextMenuPolicy(Qt.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._keyboardSearchString = ""
        self._keyboardSearchTimer = QElapsedTimer()
        self._keyboardSearchTimer.invalidate()

        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.
        """
        # create the popup menu for source files
        self.sourceMenu = QMenu(self)
        self.sourceMenu.addAction(
            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)

        # create the menu for multiple selected files
        self.multiMenu = QMenu(self)
        self.multiMenu.addAction(
            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)
            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 _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.
        """
        # 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])
        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:
            dn = itm.dirName()
            fullNames.append(dn)
            dn = self.project.getRelativePath(dn)
            dirs.append(dn)

        from UI.DeleteFilesConfirmationDialog import (
            DeleteFilesConfirmationDialog)
        dlg = DeleteFilesConfirmationDialog(
            self.parent(),
            QCoreApplication.translate("ProjectBaseBrowser",
                                       "Delete directories"),
            QCoreApplication.translate(
                "ProjectBaseBrowser",
                "Do you really want to delete these directories from"
                " the project?"), dirs)

        if dlg.exec_() == QDialog.Accepted:
            for dn in fullNames:
                self.project.deleteDirectory(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 = self.project.getRelativePath(newfn)
        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 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)
        """
        newfn = os.path.abspath(fn)
        newfn = self.project.getRelativePath(newfn)
        sindex = self._model.itemIndexByNameAndLine(newfn, lineno)
        if sindex.isValid():
            index = self.model().mapFromSource(sindex)
            if index.isValid():
                self._selectSingleItem(index)
                self.scrollTo(index)

    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,
                                 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,
                                 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, 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

        if local:
            compareString = QCoreApplication.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, 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
                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:
            E5MessageBox.information(
                self,
                QCoreApplication.translate('ProjectBaseBrowser',
                                           "Select entries"),
                QCoreApplication.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, 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
        entries.
        """
        self._selectEntries(local=True,
                            filterList=[
                                ProjectBrowserSimpleDirectoryItem,
                                ProjectBrowserDirectoryItem
                            ])

    def selectVCSDirEntries(self):
        """
        Public slot to handle the select VCS directories context menu entries.
        """
        self._selectEntries(local=False,
                            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):
                try:
                    expandedNames.append(self.model().item(childIndex).name())
                except AttributeError:
                    # only items defining the name() method are returned
                    pass
            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()
        for name in names:
            childIndex = model.index(0, 0)
            while childIndex.isValid():
                try:
                    if model.item(childIndex).name() == name:
                        self.setExpanded(childIndex, True)
                        break
                except AttributeError:
                    # ignore items not supporting this method
                    pass
                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)
        """
        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():
                if self.isExpanded(index):
                    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)
                else:
                    self._selectSingleItem(index)
                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

    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))

    ###########################################################################
    ## 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.
        """
        e5App().getObject("UserInterface").showPreferences(
            "projectBrowserPage")
コード例 #2
0
class Browser(QTreeView):
    """
    Class used to display a file system tree.
    
    Via the context menu that
    is displayed by a right click the user can select various actions on
    the selected file.
    
    @signal sourceFile(filename) emitted to open a Python file at a line (str)
    @signal sourceFile(filename, lineno) emitted to open a Python file at a
        line (str, int)
    @signal sourceFile(filename, lineno, type) emitted to open a Python file
        at a line giving an explicit file type (str, int, str)
    @signal sourceFile(filename, linenos) emitted to open a Python file giving
        a list of lines(str, list)
    @signal designerFile(filename) emitted to open a Qt-Designer file (str)
    @signal linguistFile(filename) emitted to open a Qt-Linguist (*.ts)
        file (str)
    @signal trpreview(filenames) emitted to preview Qt-Linguist (*.qm)
        files (list of str)
    @signal trpreview(filenames, ignore) emitted to preview Qt-Linguist (*.qm)
        files indicating whether non-existent files shall be ignored
        (list of str, bool)
    @signal projectFile(filename) emitted to open an eric project file (str)
    @signal multiProjectFile(filename) emitted to open an eric multi project
        file (str)
    @signal pixmapFile(filename) emitted to open a pixmap file (str)
    @signal pixmapEditFile(filename) emitted to edit a pixmap file (str)
    @signal svgFile(filename) emitted to open a SVG file (str)
    @signal binaryFile(filename) emitted to open a file as binary (str)
    @signal unittestOpen(filename) emitted to open a Python file for a
        unit test (str)
    """
    sourceFile = pyqtSignal((str, ), (str, int), (str, list), (str, int, str))
    designerFile = pyqtSignal(str)
    linguistFile = pyqtSignal(str)
    trpreview = pyqtSignal((list, ), (list, bool))
    projectFile = pyqtSignal(str)
    multiProjectFile = pyqtSignal(str)
    pixmapFile = pyqtSignal(str)
    pixmapEditFile = pyqtSignal(str)
    svgFile = pyqtSignal(str)
    binaryFile = pyqtSignal(str)
    unittestOpen = pyqtSignal(str)

    def __init__(self, parent=None):
        """
        Constructor
        
        @param parent parent widget (QWidget)
        """
        super(Browser, self).__init__(parent)

        self.setWindowTitle(
            QCoreApplication.translate('Browser', 'File-Browser'))
        self.setWindowIcon(UI.PixmapCache.getIcon("eric.png"))

        self.__model = BrowserModel()
        self.__sortModel = BrowserSortFilterProxyModel()
        self.__sortModel.setSourceModel(self.__model)
        self.setModel(self.__sortModel)

        self.selectedItemsFilter = [BrowserFileItem]

        self._activating = False

        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self._contextMenuRequested)
        self.activated.connect(self._openItem)
        self.expanded.connect(self._resizeColumns)
        self.collapsed.connect(self._resizeColumns)

        self.setWhatsThis(
            QCoreApplication.translate(
                'Browser', """<b>The Browser Window</b>"""
                """<p>This allows you to easily navigate the hierarchy of"""
                """ directories and files on your system, identify the Python"""
                """ programs and open them up in a Source Viewer window. The"""
                """ window displays several separate hierarchies.</p>"""
                """<p>The first hierarchy is only shown if you have opened a"""
                """ program for debugging and its root is the directory"""
                """ containing that program. Usually all of the separate files"""
                """ that make up a Python application are held in the same"""
                """ directory, so this hierarchy gives you easy access to most"""
                """ of what you will need.</p>"""
                """<p>The next hierarchy is used to easily navigate the"""
                """ directories that are specified in the Python"""
                """ <tt>sys.path</tt> variable.</p>"""
                """<p>The remaining hierarchies allow you navigate your system"""
                """ as a whole. On a UNIX system there will be a hierarchy with"""
                """ <tt>/</tt> at its root and another with the user home"""
                """ directory. On a Windows system there will be a hierarchy for"""
                """ each drive on the"""
                """ system.</p>"""
                """<p>Python programs (i.e. those with a <tt>.py</tt> file name"""
                """ suffix) are identified in the hierarchies with a Python"""
                """ icon. The right mouse button will popup a menu which lets"""
                """ you open the file in a Source Viewer window, open the file"""
                """ for debugging or use it for a unittest run.</p>"""
                """<p>The context menu of a class, function or method allows you"""
                """ to open the file defining this class, function or method and"""
                """ will ensure, that the correct source line is visible.</p>"""
                """<p>Qt-Designer files (i.e. those with a <tt>.ui</tt> file"""
                """ name suffix) are shown with a Designer icon. The context"""
                """ menu of these files allows you to start Qt-Designer with"""
                """ that file.</p>"""
                """<p>Qt-Linguist files (i.e. those with a <tt>.ts</tt> file"""
                """ name suffix) are shown with a Linguist icon. The context"""
                """ menu of these files allows you to start Qt-Linguist with"""
                """ that file.</p>"""))

        self.__createPopupMenus()

        self._init()  # perform common initialization tasks

        self._keyboardSearchString = ""
        self._keyboardSearchTimer = QElapsedTimer()
        self._keyboardSearchTimer.invalidate()

    def _init(self):
        """
        Protected method to perform initialization tasks common to this
        base class and all derived classes.
        """
        self.setRootIsDecorated(True)
        self.setAlternatingRowColors(True)

        header = self.header()
        header.setSortIndicator(0, Qt.AscendingOrder)
        header.setSortIndicatorShown(True)
        header.setSectionsClickable(True)

        self.setSortingEnabled(True)

        self.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.setSelectionBehavior(QAbstractItemView.SelectRows)

        self.header().setStretchLastSection(True)
        self.headerSize0 = 0
        self.layoutDisplay()

    def layoutDisplay(self):
        """
        Public slot to perform a layout operation.
        """
        self._resizeColumns(QModelIndex())
        self._resort()

    def _resizeColumns(self, index):
        """
        Protected slot to resize the view when items get expanded or collapsed.
        
        @param index index of item (QModelIndex)
        """
        w = max(100, self.sizeHintForColumn(0))
        if w != self.headerSize0:
            self.header().resizeSection(0, w)
            self.headerSize0 = w

    def _resort(self):
        """
        Protected slot to resort the tree.
        """
        self.model().sort(self.header().sortIndicatorSection(),
                          self.header().sortIndicatorOrder())

    def __createPopupMenus(self):
        """
        Private method to generate the various popup menus.
        """
        # create the popup menu for source files
        self.sourceMenu = QMenu(self)
        self.sourceMenu.addAction(
            QCoreApplication.translate('Browser', 'Open'), self._openItem)
        self.unittestAct = self.sourceMenu.addAction(
            QCoreApplication.translate('Browser', 'Run unittest...'),
            self.handleUnittest)
        self.sourceMenu.addSeparator()
        self.mimeTypeAct = self.sourceMenu.addAction(
            QCoreApplication.translate('Browser', 'Show Mime-Type'),
            self.__showMimeType)
        self.sourceMenu.addSeparator()
        self.sourceMenu.addAction(
            QCoreApplication.translate('Browser', 'Copy Path to Clipboard'),
            self._copyToClipboard)

        # create the popup menu for general use
        self.menu = QMenu(self)
        self.menu.addAction(QCoreApplication.translate('Browser', 'Open'),
                            self._openItem)
        self.menu.addAction(
            QCoreApplication.translate('Browser', 'Open in Hex Editor'),
            self._openHexEditor)
        self.editPixmapAct = self.menu.addAction(
            QCoreApplication.translate('Browser', 'Open in Icon Editor'),
            self._editPixmap)
        self.menu.addSeparator()
        self.mimeTypeAct = self.menu.addAction(
            QCoreApplication.translate('Browser', 'Show Mime-Type'),
            self.__showMimeType)
        self.menu.addSeparator()
        self.menu.addAction(
            QCoreApplication.translate('Browser', 'Copy Path to Clipboard'),
            self._copyToClipboard)

        # create the menu for multiple selected files
        self.multiMenu = QMenu(self)
        self.multiMenu.addAction(QCoreApplication.translate('Browser', 'Open'),
                                 self._openItem)

        # create the directory menu
        self.dirMenu = QMenu(self)
        self.dirMenu.addAction(
            QCoreApplication.translate('Browser', 'New toplevel directory...'),
            self.__newToplevelDir)
        self.addAsTopLevelAct = self.dirMenu.addAction(
            QCoreApplication.translate('Browser', 'Add as toplevel directory'),
            self.__addAsToplevelDir)
        self.removeFromToplevelAct = self.dirMenu.addAction(
            QCoreApplication.translate('Browser', 'Remove from toplevel'),
            self.__removeToplevel)
        self.dirMenu.addSeparator()
        self.dirMenu.addAction(
            QCoreApplication.translate('Browser', 'Refresh directory'),
            self.__refreshDirectory)
        self.dirMenu.addSeparator()
        self.dirMenu.addAction(
            QCoreApplication.translate('Browser', 'Find in this directory'),
            self.__findInDirectory)
        self.dirMenu.addAction(
            QCoreApplication.translate('Browser',
                                       'Find && Replace in this directory'),
            self.__replaceInDirectory)
        self.dirMenu.addAction(
            QCoreApplication.translate('Browser', 'Copy Path to Clipboard'),
            self._copyToClipboard)

        # create the attribute menu
        self.gotoMenu = QMenu(QCoreApplication.translate('Browser', "Goto"),
                              self)
        self.gotoMenu.aboutToShow.connect(self._showGotoMenu)
        self.gotoMenu.triggered.connect(self._gotoAttribute)

        self.attributeMenu = QMenu(self)
        self.attributeMenu.addAction(
            QCoreApplication.translate('Browser', 'New toplevel directory...'),
            self.__newToplevelDir)
        self.attributeMenu.addSeparator()
        self.attributeMenu.addMenu(self.gotoMenu)

        # create the background menu
        self.backMenu = QMenu(self)
        self.backMenu.addAction(
            QCoreApplication.translate('Browser', 'New toplevel directory...'),
            self.__newToplevelDir)

    def mouseDoubleClickEvent(self, mouseEvent):
        """
        Protected method of QAbstractItemView.
        
        Reimplemented to disable expanding/collapsing of items when
        double-clicking. Instead the double-clicked entry is opened.
        
        @param mouseEvent the mouse event (QMouseEvent)
        """
        index = self.indexAt(mouseEvent.pos())
        if index.isValid():
            itm = self.model().item(index)
            if isinstance(itm, (BrowserDirectoryItem, BrowserImportsItem,
                                ProjectBrowserSimpleDirectoryItem,
                                BrowserSysPathItem, BrowserGlobalsItem)):
                self.setExpanded(index, not self.isExpanded(index))
            else:
                self._openItem()

    def _contextMenuRequested(self, coord):
        """
        Protected slot to show the context menu of the listview.
        
        @param coord the position of the mouse pointer (QPoint)
        """
        categories = self.getSelectedItemsCountCategorized([
            BrowserDirectoryItem, BrowserFileItem, BrowserClassItem,
            BrowserMethodItem
        ])
        cnt = categories["sum"]
        bfcnt = categories[str(BrowserFileItem)]
        if cnt > 1 and cnt == bfcnt:
            self.multiMenu.popup(self.mapToGlobal(coord))
        else:
            index = self.indexAt(coord)

            if index.isValid():
                self.setCurrentIndex(index)
                flags = QItemSelectionModel.SelectionFlags(
                    QItemSelectionModel.ClearAndSelect
                    | QItemSelectionModel.Rows)
                self.selectionModel().select(index, flags)

                itm = self.model().item(index)
                coord = self.mapToGlobal(coord)
                if isinstance(itm, BrowserFileItem):
                    if itm.isPython3File():
                        if itm.fileName().endswith('.py'):
                            self.unittestAct.setEnabled(True)
                        else:
                            self.unittestAct.setEnabled(False)
                        self.sourceMenu.popup(coord)
                    else:
                        self.editPixmapAct.setVisible(itm.isPixmapFile())
                        self.menu.popup(coord)
                elif isinstance(
                        itm,
                    (BrowserClassItem, BrowserMethodItem, BrowserImportItem)):
                    self.editPixmapAct.setVisible(False)
                    self.menu.popup(coord)
                elif isinstance(itm, BrowserClassAttributeItem):
                    self.attributeMenu.popup(coord)
                elif isinstance(itm, BrowserDirectoryItem):
                    if not index.parent().isValid():
                        self.removeFromToplevelAct.setEnabled(True)
                        self.addAsTopLevelAct.setEnabled(False)
                    else:
                        self.removeFromToplevelAct.setEnabled(False)
                        self.addAsTopLevelAct.setEnabled(True)
                    self.dirMenu.popup(coord)
                else:
                    self.backMenu.popup(coord)
            else:
                self.backMenu.popup(self.mapToGlobal(coord))

    def _showGotoMenu(self):
        """
        Protected slot to prepare the goto submenu of the attribute menu.
        """
        self.gotoMenu.clear()

        itm = self.model().item(self.currentIndex())
        linenos = itm.linenos()
        fileName = itm.fileName()

        for lineno in sorted(linenos):
            act = self.gotoMenu.addAction(
                QCoreApplication.translate('Browser',
                                           "Line {0}").format(lineno))
            act.setData([fileName, lineno])

    def _gotoAttribute(self, act):
        """
        Protected slot to handle the selection of the goto menu.
        
        @param act reference to the action (E5Action)
        """
        fileName, lineno = act.data()
        self.sourceFile[str, int].emit(fileName, lineno)

    def handlePreferencesChanged(self):
        """
        Public slot used to handle the preferencesChanged signal.
        """
        self.model().preferencesChanged()
        self._resort()

    def _openItem(self):
        """
        Protected slot to handle the open popup menu entry.
        """
        itmList = self.getSelectedItems([
            BrowserFileItem, BrowserClassItem, BrowserMethodItem,
            BrowserClassAttributeItem, BrowserImportItem
        ])

        if not self._activating:
            self._activating = True
            for itm in itmList:
                if isinstance(itm, BrowserFileItem):
                    if itm.isPython2File():
                        self.sourceFile[str].emit(itm.fileName())
                    elif itm.isPython3File():
                        self.sourceFile[str].emit(itm.fileName())
                    elif itm.isRubyFile():
                        self.sourceFile[str, int,
                                        str].emit(itm.fileName(), -1, "Ruby")
                    elif itm.isDFile():
                        self.sourceFile[str, int,
                                        str].emit(itm.fileName(), -1, "D")
                    elif itm.isDesignerFile():
                        self.designerFile.emit(itm.fileName())
                    elif itm.isLinguistFile():
                        if itm.fileExt() == '.ts':
                            self.linguistFile.emit(itm.fileName())
                        else:
                            self.trpreview.emit([itm.fileName()])
                    elif itm.isProjectFile():
                        self.projectFile.emit(itm.fileName())
                    elif itm.isMultiProjectFile():
                        self.multiProjectFile.emit(itm.fileName())
                    elif itm.isIdlFile():
                        self.sourceFile[str].emit(itm.fileName())
                    elif itm.isProtobufFile():
                        self.sourceFile[str].emit(itm.fileName())
                    elif itm.isResourcesFile():
                        self.sourceFile[str].emit(itm.fileName())
                    elif itm.isSvgFile():
                        self.svgFile.emit(itm.fileName())
                    elif itm.isPixmapFile():
                        self.pixmapFile.emit(itm.fileName())
                    else:
                        if Utilities.MimeTypes.isTextFile(itm.fileName()):
                            self.sourceFile[str].emit(itm.fileName())
                        else:
                            QDesktopServices.openUrl(QUrl(itm.fileName()))
                elif isinstance(itm, BrowserClassItem):
                    self.sourceFile[str, int].emit(itm.fileName(),
                                                   itm.classObject().lineno)
                elif isinstance(itm, BrowserMethodItem):
                    self.sourceFile[str, int].emit(itm.fileName(),
                                                   itm.functionObject().lineno)
                elif isinstance(itm, BrowserClassAttributeItem):
                    self.sourceFile[str,
                                    int].emit(itm.fileName(),
                                              itm.attributeObject().lineno)
                elif isinstance(itm, BrowserImportItem):
                    self.sourceFile[str, list].emit(itm.fileName(),
                                                    itm.linenos())
            self._activating = False

    def __showMimeType(self):
        """
        Private slot to show the mime type of the selected entry.
        """
        itmList = self.getSelectedItems([
            BrowserFileItem, BrowserClassItem, BrowserMethodItem,
            BrowserClassAttributeItem, BrowserImportItem
        ])
        if itmList:
            mimetype = Utilities.MimeTypes.mimeType(itmList[0].fileName())
            if mimetype is None:
                E5MessageBox.warning(
                    self,
                    QCoreApplication.translate('Browser', "Show Mime-Type"),
                    QCoreApplication.translate(
                        'Browser', """The mime type of the file could not be"""
                        """ determined."""))
            elif mimetype.split("/")[0] == "text":
                E5MessageBox.information(
                    self,
                    QCoreApplication.translate('Browser', "Show Mime-Type"),
                    QCoreApplication.translate(
                        'Browser',
                        """The file has the mime type <b>{0}</b>.""").format(
                            mimetype))
            else:
                textMimeTypesList = Preferences.getUI("TextMimeTypes")
                if mimetype in textMimeTypesList:
                    E5MessageBox.information(
                        self,
                        QCoreApplication.translate('Browser',
                                                   "Show Mime-Type"),
                        QCoreApplication.translate(
                            'Browser',
                            """The file has the mime type <b>{0}</b>.""").
                        format(mimetype))
                else:
                    ok = E5MessageBox.yesNo(
                        self,
                        QCoreApplication.translate('Browser',
                                                   "Show Mime-Type"),
                        QCoreApplication.translate(
                            'Browser',
                            """The file has the mime type <b>{0}</b>."""
                            """<br/> Shall it be added to the list of"""
                            """ text mime types?""").format(mimetype))
                    if ok:
                        textMimeTypesList.append(mimetype)
                        Preferences.setUI("TextMimeTypes", textMimeTypesList)

    def _editPixmap(self):
        """
        Protected slot to handle the open in icon editor popup menu entry.
        """
        itmList = self.getSelectedItems([BrowserFileItem])

        for itm in itmList:
            if isinstance(itm, BrowserFileItem):
                if itm.isPixmapFile():
                    self.pixmapEditFile.emit(itm.fileName())

    def _openHexEditor(self):
        """
        Protected slot to handle the open in hex editor popup menu entry.
        """
        itmList = self.getSelectedItems([BrowserFileItem])

        for itm in itmList:
            if isinstance(itm, BrowserFileItem):
                self.binaryFile.emit(itm.fileName())

    def _copyToClipboard(self):
        """
        Protected method to copy the text shown for an entry to the clipboard.
        """
        itm = self.model().item(self.currentIndex())
        try:
            fn = itm.fileName()
        except AttributeError:
            try:
                fn = itm.dirName()
            except AttributeError:
                fn = ""

        if fn:
            cb = QApplication.clipboard()
            cb.setText(fn)

    def handleUnittest(self):
        """
        Public slot to handle the unittest popup menu entry.
        """
        try:
            index = self.currentIndex()
            itm = self.model().item(index)
            pyfn = itm.fileName()
        except AttributeError:
            pyfn = None

        if pyfn is not None:
            self.unittestOpen.emit(pyfn)

    def __newToplevelDir(self):
        """
        Private slot to handle the New toplevel directory popup menu entry.
        """
        dname = E5FileDialog.getExistingDirectory(
            None,
            QCoreApplication.translate('Browser', "New toplevel directory"),
            "", E5FileDialog.Options(E5FileDialog.ShowDirsOnly))
        if dname:
            dname = os.path.abspath(Utilities.toNativeSeparators(dname))
            self.__model.addTopLevelDir(dname)

    def __removeToplevel(self):
        """
        Private slot to handle the Remove from toplevel popup menu entry.
        """
        index = self.currentIndex()
        sindex = self.model().mapToSource(index)
        self.__model.removeToplevelDir(sindex)

    def __addAsToplevelDir(self):
        """
        Private slot to handle the Add as toplevel directory popup menu entry.
        """
        index = self.currentIndex()
        dname = self.model().item(index).dirName()
        self.__model.addTopLevelDir(dname)

    def __refreshDirectory(self):
        """
        Private slot to refresh a directory entry.
        """
        index = self.currentIndex()
        refreshDir = self.model().item(index).dirName()
        self.__model.directoryChanged(refreshDir)

    def __findInDirectory(self):
        """
        Private slot to handle the Find in directory popup menu entry.
        """
        index = self.currentIndex()
        searchDir = self.model().item(index).dirName()

        e5App().getObject("UserInterface").showFindFilesDialog(
            searchDir=searchDir)

    def __replaceInDirectory(self):
        """
        Private slot to handle the Find&Replace in directory popup menu entry.
        """
        index = self.currentIndex()
        searchDir = self.model().item(index).dirName()

        e5App().getObject("UserInterface").showReplaceFilesDialog(
            searchDir=searchDir)

    def handleProgramChange(self, fn):
        """
        Public slot to handle the programChange signal.
        
        @param fn file name (string)
        """
        self.__model.programChange(os.path.dirname(fn))

    def handleInterpreterChanged(self, interpreter):
        """
        Public slot to handle a change of the debug client's interpreter.
        
        @param interpreter interpreter of the debug client (string)
        """
        self.__model.interpreterChanged(interpreter)

    def wantedItem(self, itm, filterList=None):
        """
        Public method to check type of an item.
        
        @param itm the item to check (BrowserItem)
        @param filterList list of classes to check against
        @return flag indicating item is a valid type (boolean)
        """
        if filterList is None:
            filterList = self.selectedItemsFilter
        for typ in filterList:
            if isinstance(itm, typ):
                return True
        return False

    def getSelectedItems(self, filterList=None):
        """
        Public method to get the selected items.
        
        @param filterList list of classes to check against
        @return list of selected items (list of BroweserItem)
        """
        selectedItems = []
        indexes = self.selectedIndexes()
        for index in indexes:
            if index.column() == 0:
                itm = self.model().item(index)
                if self.wantedItem(itm, filterList):
                    selectedItems.append(itm)
        return selectedItems

    def getSelectedItemsCount(self, filterList=None):
        """
        Public method to get the count of items selected.
        
        @param filterList list of classes to check against
        @return count of items selected (integer)
        """
        count = 0
        indexes = self.selectedIndexes()
        for index in indexes:
            if index.column() == 0:
                itm = self.model().item(index)
                if self.wantedItem(itm, filterList):
                    count += 1
        return count

    def getSelectedItemsCountCategorized(self, filterList=None):
        """
        Public method to get a categorized count of selected items.
        
        @param filterList list of classes to check against
        @return a dictionary containing the counts of items belonging
            to the individual filter classes. The keys of the dictionary
            are the string representation of the classes given in the
            filter (i.e. str(filterClass)). The dictionary contains
            an additional entry with key "sum", that stores the sum of
            all selected entries fulfilling the filter criteria.
        """
        if filterList is None:
            filterList = self.selectedItemsFilter
        categories = {}
        categories["sum"] = 0
        for typ in filterList:
            categories[str(typ)] = 0

        indexes = self.selectedIndexes()
        for index in indexes:
            if index.column() == 0:
                itm = self.model().item(index)
                for typ in filterList:
                    if isinstance(itm, typ):
                        categories["sum"] += 1
                        categories[str(typ)] += 1

        return categories

    def saveToplevelDirs(self):
        """
        Public slot to save the toplevel directories.
        """
        self.__model.saveToplevelDirs()

    def keyboardSearch(self, search):
        """
        Public function to search the tree via the keyboard.
        
        @param search the character entered via the keyboard
        @type str
        """
        if self.model().rowCount() == 0:
            return

        if self.currentIndex().isValid():
            startIndex = self.currentIndex()
        else:
            startIndex = self.model().index(0, 0)

        keyboardSearchTimeWasValid = self._keyboardSearchTimer.isValid()
        keyboardSearchTimeElapsed = self._keyboardSearchTimer.restart()
        if (not search or not keyboardSearchTimeWasValid
                or keyboardSearchTimeElapsed >
                QApplication.keyboardInputInterval()):
            self._keyboardSearchString = search.lower()
        else:
            self._keyboardSearchString += search.lower()

        index = startIndex
        found = False
        while True:
            name = self.model().data(index)
            if (name.lower().startswith(self._keyboardSearchString)
                    and self._keyboardSearchType(self.model().item(index))):
                found = True
                break

            index = self.indexBelow(index)
            if not index.isValid():
                index = self.model().index(0, 0)
            if index == startIndex:
                break

        if found:
            self.setCurrentIndex(index)

    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, BrowserSysPathItem))