Ejemplo n.º 1
0
 def __init__(self, parent):
     """
     Constructor
     
     @param parent reference to parent object (Project.Project)
     """
     super(ProjectBrowserModel, self).__init__(parent, nopopulate=True)
     
     rootData = self.tr("Name")
     self.rootItem = BrowserItem(None, rootData)
     self.rootItem.itemData.append(self.tr("VCS Status"))
     
     self.progDir = None
     self.project = parent
     
     self.watchedItems = {}
     self.watcher = QFileSystemWatcher(self)
     self.watcher.directoryChanged.connect(self.directoryChanged)
     
     self.inRefresh = False
     
     self.projectBrowserTypes = {
         "SOURCES": ProjectBrowserSourceType,
         "FORMS": ProjectBrowserFormType,
         "RESOURCES": ProjectBrowserResourceType,
         "INTERFACES": ProjectBrowserInterfaceType,
         "PROTOCOLS": ProjectBrowserProtocolsType,
         "TRANSLATIONS": ProjectBrowserTranslationType,
         "OTHERS": ProjectBrowserOthersType,
     }
     
     self.colorNames = {
         "A": "VcsAdded",
         "M": "VcsModified",
         "O": "VcsRemoved",
         "R": "VcsReplaced",
         "U": "VcsUpdate",
         "Z": "VcsConflict",
     }
     self.itemBackgroundColors = {
         " ": QColor(),
         "A": Preferences.getProjectBrowserColour(self.colorNames["A"]),
         "M": Preferences.getProjectBrowserColour(self.colorNames["M"]),
         "O": Preferences.getProjectBrowserColour(self.colorNames["O"]),
         "R": Preferences.getProjectBrowserColour(self.colorNames["R"]),
         "U": Preferences.getProjectBrowserColour(self.colorNames["U"]),
         "Z": Preferences.getProjectBrowserColour(self.colorNames["Z"]),
     }
     
     self.highLightColor = Preferences.getProjectBrowserColour(
         "Highlighted")
     # needed by preferencesChanged()
     
     self.vcsStatusReport = {}
Ejemplo n.º 2
0
 def __init__(self, parent):
     """
     Constructor
     
     @param parent reference to parent object (Project.Project)
     """
     super(ProjectBrowserModel, self).__init__(parent, nopopulate=True)
     
     rootData = self.tr("Name")
     self.rootItem = BrowserItem(None, rootData)
     self.rootItem.itemData.append(self.tr("VCS Status"))
     
     self.progDir = None
     self.project = parent
     
     self.watchedItems = {}
     self.watcher = QFileSystemWatcher(self)
     self.watcher.directoryChanged.connect(self.directoryChanged)
     
     self.inRefresh = False
     
     self.projectBrowserTypes = {
         "SOURCES": ProjectBrowserSourceType,
         "FORMS": ProjectBrowserFormType,
         "RESOURCES": ProjectBrowserResourceType,
         "INTERFACES": ProjectBrowserInterfaceType,
         "TRANSLATIONS": ProjectBrowserTranslationType,
         "OTHERS": ProjectBrowserOthersType,
     }
     
     self.colorNames = {
         "A": "VcsAdded",
         "M": "VcsModified",
         "O": "VcsRemoved",
         "R": "VcsReplaced",
         "U": "VcsUpdate",
         "Z": "VcsConflict",
     }
     self.itemBackgroundColors = {
         " ": QColor(),
         "A": Preferences.getProjectBrowserColour(self.colorNames["A"]),
         "M": Preferences.getProjectBrowserColour(self.colorNames["M"]),
         "O": Preferences.getProjectBrowserColour(self.colorNames["O"]),
         "R": Preferences.getProjectBrowserColour(self.colorNames["R"]),
         "U": Preferences.getProjectBrowserColour(self.colorNames["U"]),
         "Z": Preferences.getProjectBrowserColour(self.colorNames["Z"]),
     }
     
     self.highLightColor = \
         Preferences.getProjectBrowserColour("Highlighted")
     # needed by preferencesChanged()
     
     self.vcsStatusReport = {}
Ejemplo n.º 3
0
    def __init__(self, parent, projectType, text, path=""):
        """
        Constructor
        
        @param parent parent item
        @param projectType type of file/directory in the project
        @param text text to be displayed (string)
        @param path path of the directory (string)
        """
        BrowserItem.__init__(self, parent, text)
        ProjectBrowserItemMixin.__init__(self, projectType)

        self._dirName = path
        if not os.path.isdir(self._dirName):
            self._dirName = os.path.dirname(self._dirName)

        self.type_ = ProjectBrowserItemSimpleDirectory
        if os.path.lexists(self._dirName) and os.path.islink(self._dirName):
            self.symlink = True
            self.icon = UI.PixmapCache.getSymlinkIcon("dirClosed.png")
        else:
            self.icon = UI.PixmapCache.getIcon("dirClosed.png")
Ejemplo n.º 4
0
 def __init__(self, parent, projectType, text, path=""):
     """
     Constructor
     
     @param parent parent item
     @param projectType type of file/directory in the project
     @param text text to be displayed (string)
     @param path path of the directory (string)
     """
     BrowserItem.__init__(self, parent, text)
     ProjectBrowserItemMixin.__init__(self, projectType)
     
     self._dirName = path
     if not os.path.isdir(self._dirName):
         self._dirName = os.path.dirname(self._dirName)
     
     self.type_ = ProjectBrowserItemSimpleDirectory
     if os.path.lexists(self._dirName) and os.path.islink(self._dirName):
         self.symlink = True
         self.icon = UI.PixmapCache.getSymlinkIcon("dirClosed.png")
     else:
         self.icon = UI.PixmapCache.getIcon("dirClosed.png")
Ejemplo n.º 5
0
    def lessThan(self, other, column, order):
        """
        Public method to check, if the item is less than the other one.
        
        @param other reference to item to compare against (BrowserItem)
        @param column column number to use for the comparison (integer)
        @param order sort order (Qt.SortOrder) (for special sorting)
        @return true, if this item is less than other (boolean)
        """
        if issubclass(other.__class__, BrowserFileItem):
            if Preferences.getUI("BrowsersListFoldersFirst"):
                return order == Qt.AscendingOrder

        return BrowserItem.lessThan(self, other, column, order)
Ejemplo n.º 6
0
 def lessThan(self, other, column, order):
     """
     Public method to check, if the item is less than the other one.
     
     @param other reference to item to compare against (BrowserItem)
     @param column column number to use for the comparison (integer)
     @param order sort order (Qt.SortOrder) (for special sorting)
     @return true, if this item is less than other (boolean)
     """
     if issubclass(other.__class__, BrowserFileItem):
         if Preferences.getUI("BrowsersListFoldersFirst"):
             return order == Qt.AscendingOrder
     
     return BrowserItem.lessThan(self, other, column, order)
Ejemplo n.º 7
0
class ProjectBrowserModel(BrowserModel):
    """
    Class implementing the project browser model.
    
    @signal vcsStateChanged(str) emitted after the VCS state has changed
    """
    vcsStateChanged = pyqtSignal(str)

    def __init__(self, parent):
        """
        Constructor
        
        @param parent reference to parent object (Project.Project)
        """
        super(ProjectBrowserModel, self).__init__(parent, nopopulate=True)

        rootData = self.tr("Name")
        self.rootItem = BrowserItem(None, rootData)
        self.rootItem.itemData.append(self.tr("VCS Status"))

        self.progDir = None
        self.project = parent

        self.watchedItems = {}
        self.watcher = QFileSystemWatcher(self)
        self.watcher.directoryChanged.connect(self.directoryChanged)

        self.inRefresh = False

        self.projectBrowserTypes = {
            "SOURCES": ProjectBrowserSourceType,
            "FORMS": ProjectBrowserFormType,
            "RESOURCES": ProjectBrowserResourceType,
            "INTERFACES": ProjectBrowserInterfaceType,
            "TRANSLATIONS": ProjectBrowserTranslationType,
            "OTHERS": ProjectBrowserOthersType,
        }

        self.colorNames = {
            "A": "VcsAdded",
            "M": "VcsModified",
            "O": "VcsRemoved",
            "R": "VcsReplaced",
            "U": "VcsUpdate",
            "Z": "VcsConflict",
        }
        self.itemBackgroundColors = {
            " ": QColor(),
            "A": Preferences.getProjectBrowserColour(self.colorNames["A"]),
            "M": Preferences.getProjectBrowserColour(self.colorNames["M"]),
            "O": Preferences.getProjectBrowserColour(self.colorNames["O"]),
            "R": Preferences.getProjectBrowserColour(self.colorNames["R"]),
            "U": Preferences.getProjectBrowserColour(self.colorNames["U"]),
            "Z": Preferences.getProjectBrowserColour(self.colorNames["Z"]),
        }

        self.highLightColor = \
            Preferences.getProjectBrowserColour("Highlighted")
        # needed by preferencesChanged()

        self.vcsStatusReport = {}

    def data(self, index, role):
        """
        Public method to get data of an item.
        
        @param index index of the data to retrieve (QModelIndex)
        @param role role of data (Qt.ItemDataRole)
        @return requested data
        """
        if not index.isValid():
            return None

        if role == Qt.TextColorRole:
            if index.column() == 0:
                try:
                    return index.internalPointer().getTextColor()
                except AttributeError:
                    return None
        elif role == Qt.BackgroundColorRole:
            try:
                col = self.itemBackgroundColors[
                    index.internalPointer().vcsState]
                if col.isValid():
                    return col
                else:
                    return None
            except AttributeError:
                return None
            except KeyError:
                return None

        return BrowserModel.data(self, index, role)

    def populateItem(self, parentItem, repopulate=False):
        """
        Public method to populate an item's subtree.
        
        @param parentItem reference to the item to be populated
        @param repopulate flag indicating a repopulation (boolean)
        """
        if parentItem.type() == ProjectBrowserItemSimpleDirectory:
            return  # nothing to do
        elif parentItem.type() == ProjectBrowserItemDirectory:
            self.populateProjectDirectoryItem(parentItem, repopulate)
        elif parentItem.type() == ProjectBrowserItemFile:
            self.populateFileItem(parentItem, repopulate)
        else:
            BrowserModel.populateItem(self, parentItem, repopulate)

    def populateProjectDirectoryItem(self, parentItem, repopulate=False):
        """
        Public method to populate a directory item's subtree.
        
        @param parentItem reference to the directory item to be populated
        @param repopulate flag indicating a repopulation (boolean)
        """
        self._addWatchedItem(parentItem)

        qdir = QDir(parentItem.dirName())

        if Preferences.getUI("BrowsersListHiddenFiles"):
            filter = QDir.Filters(QDir.AllEntries | QDir.Hidden
                                  | QDir.NoDotAndDotDot)
        else:
            filter = QDir.Filters(QDir.AllEntries | QDir.NoDot | QDir.NoDotDot)
        entryInfoList = qdir.entryInfoList(filter)

        if len(entryInfoList) > 0:
            if repopulate:
                self.beginInsertRows(
                    self.createIndex(parentItem.row(), 0, parentItem), 0,
                    len(entryInfoList) - 1)
            states = {}
            if self.project.vcs is not None:
                for f in entryInfoList:
                    fname = f.absoluteFilePath()
                    states[os.path.normcase(fname)] = 0
                dname = parentItem.dirName()
                self.project.vcs.clearStatusCache()
                states = self.project.vcs.vcsAllRegisteredStates(states, dname)

            for f in entryInfoList:
                if f.isDir():
                    node = ProjectBrowserDirectoryItem(
                        parentItem,
                        Utilities.toNativeSeparators(f.absoluteFilePath()),
                        parentItem.getProjectTypes()[0], False)
                else:
                    node = ProjectBrowserFileItem(
                        parentItem,
                        Utilities.toNativeSeparators(f.absoluteFilePath()),
                        parentItem.getProjectTypes()[0])
                if self.project.vcs is not None:
                    fname = f.absoluteFilePath()
                    if states[os.path.normcase(fname)] == \
                            self.project.vcs.canBeCommitted:
                        node.addVcsStatus(self.project.vcs.vcsName())
                        self.project.clearStatusMonitorCachedState(
                            f.absoluteFilePath())
                    else:
                        node.addVcsStatus(self.tr("local"))
                self._addItem(node, parentItem)
            if repopulate:
                self.endInsertRows()

    def projectClosed(self):
        """
        Public method called after a project has been closed.
        """
        self.__vcsStatus = {}

        self.watchedItems = {}
        watchedDirs = self.watcher.directories()
        if watchedDirs:
            self.watcher.removePaths(watchedDirs)

        self.rootItem.removeChildren()
        self.beginResetModel()
        self.endResetModel()

        # reset the module parser cache
        Utilities.ModuleParser.resetParsedModules()

    def projectOpened(self):
        """
        Public method used to populate the model after a project has been
        opened.
        """
        self.__vcsStatus = {}
        states = {}
        keys = list(self.projectBrowserTypes.keys())[:]

        if self.project.vcs is not None:
            for key in keys:
                for fn in self.project.pdata[key]:
                    states[os.path.normcase(
                        os.path.join(self.project.ppath, fn))] = 0

            self.project.vcs.clearStatusCache()
            states = self.project.vcs.vcsAllRegisteredStates(
                states, self.project.ppath)

        self.inRefresh = True
        for key in keys:
            # Show the entry in bold in the others browser to make it more
            # distinguishable
            if key == "OTHERS":
                bold = True
            else:
                bold = False

            if key == "SOURCES":
                sourceLanguage = self.project.pdata["PROGLANGUAGE"][0]
            else:
                sourceLanguage = ""

            for fn in self.project.pdata[key]:
                fname = os.path.join(self.project.ppath, fn)
                parentItem, dt = self.findParentItemByName(
                    self.projectBrowserTypes[key], fn)
                if os.path.isdir(fname):
                    itm = ProjectBrowserDirectoryItem(
                        parentItem, fname, self.projectBrowserTypes[key],
                        False, bold)
                else:
                    itm = ProjectBrowserFileItem(parentItem,
                                                 fname,
                                                 self.projectBrowserTypes[key],
                                                 False,
                                                 bold,
                                                 sourceLanguage=sourceLanguage)
                self._addItem(itm, parentItem)
                if self.project.vcs is not None:
                    if states[os.path.normcase(fname)] == \
                            self.project.vcs.canBeCommitted:
                        itm.addVcsStatus(self.project.vcs.vcsName())
                    else:
                        itm.addVcsStatus(self.tr("local"))
                else:
                    itm.addVcsStatus("")
        self.inRefresh = False
        self.beginResetModel()
        self.endResetModel()

    def findParentItemByName(self, type_, name, dontSplit=False):
        """
        Public method to find an item given its name.
        
        <b>Note</b>: This method creates all necessary parent items, if they
        don't exist.
        
        @param type_ type of the item
        @param name name of the item (string)
        @param dontSplit flag indicating the name should not be split (boolean)
        @return reference to the item found and the new display name (string)
        """
        if dontSplit:
            pathlist = []
            pathlist.append(name)
            pathlist.append("ignore_me")
        else:
            pathlist = re.split(r'/|\\', name)

        if len(pathlist) > 1:
            olditem = self.rootItem
            path = self.project.ppath
            for p in pathlist[:-1]:
                itm = self.findChildItem(p, 0, olditem)
                path = os.path.join(path, p)
                if itm is None:
                    itm = ProjectBrowserSimpleDirectoryItem(
                        olditem, type_, p, path)
                    self.__addVCSStatus(itm, path)
                    if self.inRefresh:
                        self._addItem(itm, olditem)
                    else:
                        if olditem == self.rootItem:
                            oldindex = QModelIndex()
                        else:
                            oldindex = self.createIndex(
                                olditem.row(), 0, olditem)
                        self.addItem(itm, oldindex)
                else:
                    if type_ and type_ not in itm.getProjectTypes():
                        itm.addProjectType(type_)
                        index = self.createIndex(itm.row(), 0, itm)
                        self.dataChanged.emit(index, index)
                olditem = itm
            return (itm, pathlist[-1])
        else:
            return (self.rootItem, name)

    def findChildItem(self, text, column, parentItem=None):
        """
        Public method to find a child item given some text.
        
        @param text text to search for (string)
        @param column column to search in (integer)
        @param parentItem reference to parent item
        @return reference to the item found
        """
        if parentItem is None:
            parentItem = self.rootItem

        for itm in parentItem.children():
            if itm.data(column) == text:
                return itm

        return None

    def addNewItem(self, typeString, name, additionalTypeStrings=[]):
        """
        Public method to add a new item to the model.
        
        @param typeString string denoting the type of the new item (string)
        @param name name of the new item (string)
        @param additionalTypeStrings names of additional types (list of string)
        """
        # Show the entry in bold in the others browser to make it more
        # distinguishable
        if typeString == "OTHERS":
            bold = True
        else:
            bold = False

        fname = os.path.join(self.project.ppath, name)
        parentItem, dt = self.findParentItemByName(
            self.projectBrowserTypes[typeString], name)
        if parentItem == self.rootItem:
            parentIndex = QModelIndex()
        else:
            parentIndex = self.createIndex(parentItem.row(), 0, parentItem)
        if os.path.isdir(fname):
            itm = ProjectBrowserDirectoryItem(
                parentItem, fname, self.projectBrowserTypes[typeString], False,
                bold)
        else:
            if typeString == "SOURCES":
                sourceLanguage = self.project.pdata["PROGLANGUAGE"][0]
            else:
                sourceLanguage = ""
            itm = ProjectBrowserFileItem(parentItem,
                                         fname,
                                         self.projectBrowserTypes[typeString],
                                         False,
                                         bold,
                                         sourceLanguage=sourceLanguage)
        self.__addVCSStatus(itm, fname)
        if additionalTypeStrings:
            for additionalTypeString in additionalTypeStrings:
                type_ = self.projectBrowserTypes[additionalTypeString]
                itm.addProjectType(type_)
        self.addItem(itm, parentIndex)

    def renameItem(self, name, newFilename):
        """
        Public method to rename an item.
        
        @param name the old display name (string)
        @param newFilename new filename of the item (string)
        """
        itm = self.findItem(name)
        if itm is None:
            return

        index = self.createIndex(itm.row(), 0, itm)
        itm.setName(newFilename)
        self.dataChanged.emit(index, index)
        self.repopulateItem(newFilename)

    def findItem(self, name):
        """
        Public method to find an item given its name.
        
        @param name name of the item (string)
        @return reference to the item found
        """
        if QDir.isAbsolutePath(name):
            name = self.project.getRelativePath(name)
        pathlist = re.split(r'/|\\', name)
        if len(pathlist) > 0:
            olditem = self.rootItem
            for p in pathlist:
                itm = self.findChildItem(p, 0, olditem)
                if itm is None:
                    return None
                olditem = itm
            return itm
        else:
            return None

    def itemIndexByName(self, name):
        """
        Public method to find an item's index given its name.
        
        @param name name of the item (string)
        @return index of the item found (QModelIndex)
        """
        itm = self.findItem(name)
        if itm is None:
            index = QModelIndex()
        else:
            index = self.createIndex(itm.row(), 0, itm)
        return index

    def itemIndexByNameAndLine(self, name, lineno):
        """
        Public method to find an item's index given its name.
        
        @param name name of the item (string)
        @param lineno one based line number of the item (integer)
        @return index of the item found (QModelIndex)
        """
        index = QModelIndex()
        itm = self.findItem(name)
        if itm is not None and \
           isinstance(itm, ProjectBrowserFileItem):
            olditem = itm
            autoPopulate = Preferences.getProject("AutoPopulateItems")
            while itm is not None:
                if not itm.isPopulated():
                    if itm.isLazyPopulated() and autoPopulate:
                        self.populateItem(itm)
                    else:
                        break
                for child in itm.children():
                    try:
                        start, end = child.boundaries()
                        if end == -1:
                            end = 1000000  # assume end of file
                        if start <= lineno <= end:
                            itm = child
                            break
                    except AttributeError:
                        pass
                else:
                    itm = None
                if itm:
                    olditem = itm
            index = self.createIndex(olditem.row(), 0, olditem)

        return index

    def directoryChanged(self, path):
        """
        Public slot to handle the directoryChanged signal of the watcher.
        
        @param path path of the directory (string)
        """
        if path not in self.watchedItems:
            # just ignore the situation we don't have a reference to the item
            return

        if Preferences.getUI("BrowsersListHiddenFiles"):
            filter = QDir.Filters(QDir.AllEntries | QDir.Hidden
                                  | QDir.NoDotAndDotDot)
        else:
            filter = QDir.Filters(QDir.AllEntries | QDir.NoDot | QDir.NoDotDot)

        for itm in self.watchedItems[path]:
            oldCnt = itm.childCount()

            qdir = QDir(itm.dirName())

            entryInfoList = qdir.entryInfoList(filter)

            # step 1: check for new entries
            children = itm.children()
            for f in entryInfoList:
                fpath = Utilities.toNativeSeparators(f.absoluteFilePath())
                childFound = False
                for child in children:
                    if child.name() == fpath:
                        childFound = True
                        children.remove(child)
                        break
                if childFound:
                    continue

                cnt = itm.childCount()
                self.beginInsertRows(self.createIndex(itm.row(), 0, itm), cnt,
                                     cnt)
                if f.isDir():
                    node = ProjectBrowserDirectoryItem(
                        itm,
                        Utilities.toNativeSeparators(f.absoluteFilePath()),
                        itm.getProjectTypes()[0], False)
                else:
                    node = ProjectBrowserFileItem(
                        itm,
                        Utilities.toNativeSeparators(f.absoluteFilePath()),
                        itm.getProjectTypes()[0])
                self._addItem(node, itm)
                if self.project.vcs is not None:
                    self.project.vcs.clearStatusCache()
                    state = self.project.vcs.vcsRegisteredState(node.name())
                    if state == self.project.vcs.canBeCommitted:
                        node.addVcsStatus(self.project.vcs.vcsName())
                    else:
                        node.addVcsStatus(self.tr("local"))
                self.endInsertRows()

            # step 2: check for removed entries
            if len(entryInfoList) != itm.childCount():
                for row in range(oldCnt - 1, -1, -1):
                    child = itm.child(row)
                    childname = Utilities.fromNativeSeparators(child.name())
                    entryFound = False
                    for f in entryInfoList:
                        if f.absoluteFilePath() == childname:
                            entryFound = True
                            entryInfoList.remove(f)
                            break
                    if entryFound:
                        continue

                    self._removeWatchedItem(child)
                    self.beginRemoveRows(self.createIndex(itm.row(), 0, itm),
                                         row, row)
                    itm.removeChild(child)
                    self.endRemoveRows()

    def __addVCSStatus(self, item, name):
        """
        Private method used to set the vcs status of a node.
        
        @param item item to work on
        @param name filename belonging to this item (string)
        """
        if self.project.vcs is not None:
            state = self.project.vcs.vcsRegisteredState(name)
            if state == self.project.vcs.canBeCommitted:
                item.addVcsStatus(self.project.vcs.vcsName())
            else:
                item.addVcsStatus(self.tr("local"))
        else:
            item.addVcsStatus("")

    def __updateVCSStatus(self, item, name, recursive=True):
        """
        Private method used to update the vcs status of a node.
        
        @param item item to work on
        @param name filename belonging to this item (string)
        @keyparam recursive flag indicating a recursive update (boolean)
        """
        if self.project.vcs is not None:
            self.project.vcs.clearStatusCache()
            state = self.project.vcs.vcsRegisteredState(name)
            if state == self.project.vcs.canBeCommitted:
                item.setVcsStatus(self.project.vcs.vcsName())
            else:
                item.setVcsStatus(self.tr("local"))
            if recursive:
                name = os.path.dirname(name)
                parentItem = item.parent()
                if name and parentItem is not self.rootItem:
                    self.__updateVCSStatus(parentItem, name, recursive)
        else:
            item.setVcsStatus("")

        index = self.createIndex(item.row(), 0, item)
        self.dataChanged.emit(index, index)

    def updateVCSStatus(self, name, recursive=True):
        """
        Public method used to update the vcs status of a node.
        
        @param name filename belonging to this item (string)
        @param recursive flag indicating a recursive update (boolean)
        """
        item = self.findItem(name)
        if item:
            self.__updateVCSStatus(item, name, recursive)

    def removeItem(self, name):
        """
        Public method to remove a named item.
        
        @param name file or directory name of the item (string).
        """
        fname = os.path.basename(name)
        parentItem = self.findParentItemByName(0, name)[0]
        if parentItem == self.rootItem:
            parentIndex = QModelIndex()
        else:
            parentIndex = self.createIndex(parentItem.row(), 0, parentItem)
        childItem = self.findChildItem(fname, 0, parentItem)
        if childItem is not None:
            self.beginRemoveRows(parentIndex, childItem.row(), childItem.row())
            parentItem.removeChild(childItem)
            self.endRemoveRows()

    def repopulateItem(self, name):
        """
        Public method to repopulate an item.
        
        @param name name of the file relative to the project root (string)
        """
        itm = self.findItem(name)
        if itm is None:
            return

        if itm.isLazyPopulated():
            if not itm.isPopulated():
                # item is not populated yet, nothing to do
                return

            if itm.childCount():
                index = self.createIndex(itm.row(), 0, itm)
                self.beginRemoveRows(index, 0, itm.childCount() - 1)
                itm.removeChildren()
                self.endRemoveRows()
            Utilities.ModuleParser.resetParsedModule(
                os.path.join(self.project.ppath, name))

            self.populateItem(itm, True)

    def projectPropertiesChanged(self):
        """
        Public method to react on a change of the project properties.
        """
        # nothing to do for now
        return

    def changeVCSStates(self, statesList):
        """
        Public slot to record the (non normal) VCS states.
        
        @param statesList list of VCS state entries (list of strings) giving
            the states in the first column and the path relative to the project
            directory starting with the third column. The allowed status flags
            are:
            <ul>
                <li>"A" path was added but not yet comitted</li>
                <li>"M" path has local changes</li>
                <li>"O" path was removed</li>
                <li>"R" path was deleted and then re-added</li>
                <li>"U" path needs an update</li>
                <li>"Z" path contains a conflict</li>
                <li>" " path is back at normal</li>
            </ul>
        """
        statesList.sort()
        lastHead = ""
        itemCache = {}
        if len(statesList) == 1 and statesList[0] == '--RESET--':
            statesList = []
            for name in list(self.__vcsStatus.keys()):
                statesList.append(" {0}".format(name))

        for name in statesList:
            state = name[0]
            name = name[1:].strip()
            if state == ' ':
                if name in self.__vcsStatus:
                    del self.__vcsStatus[name]
            else:
                self.__vcsStatus[name] = state

            try:
                itm = itemCache[name]
            except KeyError:
                itm = self.findItem(name)
                if itm:
                    itemCache[name] = itm
            if itm:
                itm.setVcsState(state)
                itm.setVcsStatus(self.project.vcs.vcsName())
                index1 = self.createIndex(itm.row(), 0, itm)
                index2 = self.createIndex(itm.row(),
                                          self.rootItem.columnCount(), itm)
                self.dataChanged.emit(index1, index2)

            head, tail = os.path.split(name)
            if head != lastHead:
                if lastHead:
                    self.__changeParentsVCSState(lastHead, itemCache)
                lastHead = head
        if lastHead:
            self.__changeParentsVCSState(lastHead, itemCache)
        try:
            globalVcsStatus = sorted(self.__vcsStatus.values())[-1]
        except IndexError:
            globalVcsStatus = ' '
        self.vcsStateChanged.emit(globalVcsStatus)

    def __changeParentsVCSState(self, path, itemCache):
        """
        Private method to recursively change the parents VCS state.
        
        @param path pathname of parent item (string)
        @param itemCache reference to the item cache used to store
            references to named items
        """
        while path:
            try:
                itm = itemCache[path]
            except KeyError:
                itm = self.findItem(path)
                if itm:
                    itemCache[path] = itm
            if itm:
                state = " "
                for id_ in itm.children():
                    if state < id_.vcsState:
                        state = id_.vcsState
                if state != itm.vcsState:
                    itm.setVcsState(state)
                    index1 = self.createIndex(itm.row(), 0, itm)
                    index2 = self.createIndex(itm.row(),
                                              self.rootItem.columnCount(), itm)
                    self.dataChanged.emit(index1, index2)
            path, tail = os.path.split(path)

    def preferencesChanged(self):
        """
        Public method used to handle a change in preferences.
        """
        for code in list(self.colorNames.keys()):
            color = Preferences.getProjectBrowserColour(self.colorNames[code])
            if color.name() == self.itemBackgroundColors[code].name():
                continue

            self.itemBackgroundColors[code] = color

        color = Preferences.getProjectBrowserColour("Highlighted")
        if self.highLightColor.name() != color.name():
            self.highLightColor = color
Ejemplo n.º 8
0
class ProjectBrowserModel(BrowserModel):
    """
    Class implementing the project browser model.
    
    @signal vcsStateChanged(str) emitted after the VCS state has changed
    """
    vcsStateChanged = pyqtSignal(str)
    
    def __init__(self, parent):
        """
        Constructor
        
        @param parent reference to parent object (Project.Project)
        """
        super(ProjectBrowserModel, self).__init__(parent, nopopulate=True)
        
        rootData = self.tr("Name")
        self.rootItem = BrowserItem(None, rootData)
        self.rootItem.itemData.append(self.tr("VCS Status"))
        
        self.progDir = None
        self.project = parent
        
        self.watchedItems = {}
        self.watcher = QFileSystemWatcher(self)
        self.watcher.directoryChanged.connect(self.directoryChanged)
        
        self.inRefresh = False
        
        self.projectBrowserTypes = {
            "SOURCES": ProjectBrowserSourceType,
            "FORMS": ProjectBrowserFormType,
            "RESOURCES": ProjectBrowserResourceType,
            "INTERFACES": ProjectBrowserInterfaceType,
            "TRANSLATIONS": ProjectBrowserTranslationType,
            "OTHERS": ProjectBrowserOthersType,
        }
        
        self.colorNames = {
            "A": "VcsAdded",
            "M": "VcsModified",
            "O": "VcsRemoved",
            "R": "VcsReplaced",
            "U": "VcsUpdate",
            "Z": "VcsConflict",
        }
        self.itemBackgroundColors = {
            " ": QColor(),
            "A": Preferences.getProjectBrowserColour(self.colorNames["A"]),
            "M": Preferences.getProjectBrowserColour(self.colorNames["M"]),
            "O": Preferences.getProjectBrowserColour(self.colorNames["O"]),
            "R": Preferences.getProjectBrowserColour(self.colorNames["R"]),
            "U": Preferences.getProjectBrowserColour(self.colorNames["U"]),
            "Z": Preferences.getProjectBrowserColour(self.colorNames["Z"]),
        }
        
        self.highLightColor = \
            Preferences.getProjectBrowserColour("Highlighted")
        # needed by preferencesChanged()
        
        self.vcsStatusReport = {}
    
    def data(self, index, role):
        """
        Public method to get data of an item.
        
        @param index index of the data to retrieve (QModelIndex)
        @param role role of data (Qt.ItemDataRole)
        @return requested data
        """
        if not index.isValid():
            return None
        
        if role == Qt.TextColorRole:
            if index.column() == 0:
                try:
                    return index.internalPointer().getTextColor()
                except AttributeError:
                    return None
        elif role == Qt.BackgroundColorRole:
            try:
                col = self.itemBackgroundColors[
                    index.internalPointer().vcsState]
                if col.isValid():
                    return col
                else:
                    return None
            except AttributeError:
                return None
            except KeyError:
                return None
        
        return BrowserModel.data(self, index, role)
    
    def populateItem(self, parentItem, repopulate=False):
        """
        Public method to populate an item's subtree.
        
        @param parentItem reference to the item to be populated
        @param repopulate flag indicating a repopulation (boolean)
        """
        if parentItem.type() == ProjectBrowserItemSimpleDirectory:
            return  # nothing to do
        elif parentItem.type() == ProjectBrowserItemDirectory:
            self.populateProjectDirectoryItem(parentItem, repopulate)
        elif parentItem.type() == ProjectBrowserItemFile:
            self.populateFileItem(parentItem, repopulate)
        else:
            BrowserModel.populateItem(self, parentItem, repopulate)

    def populateProjectDirectoryItem(self, parentItem, repopulate=False):
        """
        Public method to populate a directory item's subtree.
        
        @param parentItem reference to the directory item to be populated
        @param repopulate flag indicating a repopulation (boolean)
        """
        self._addWatchedItem(parentItem)
        
        qdir = QDir(parentItem.dirName())
        
        if Preferences.getUI("BrowsersListHiddenFiles"):
            filter = QDir.Filters(QDir.AllEntries |
                                  QDir.Hidden |
                                  QDir.NoDotAndDotDot)
        else:
            filter = QDir.Filters(QDir.AllEntries | QDir.NoDot | QDir.NoDotDot)
        entryInfoList = qdir.entryInfoList(filter)
        
        if len(entryInfoList) > 0:
            if repopulate:
                self.beginInsertRows(self.createIndex(
                    parentItem.row(), 0, parentItem),
                    0, len(entryInfoList) - 1)
            states = {}
            if self.project.vcs is not None:
                for f in entryInfoList:
                    fname = f.absoluteFilePath()
                    states[os.path.normcase(fname)] = 0
                dname = parentItem.dirName()
                self.project.vcs.clearStatusCache()
                states = self.project.vcs.vcsAllRegisteredStates(states, dname)
            
            for f in entryInfoList:
                if f.isDir():
                    node = ProjectBrowserDirectoryItem(
                        parentItem,
                        Utilities.toNativeSeparators(f.absoluteFilePath()),
                        parentItem.getProjectTypes()[0], False)
                else:
                    node = ProjectBrowserFileItem(
                        parentItem,
                        Utilities.toNativeSeparators(f.absoluteFilePath()),
                        parentItem.getProjectTypes()[0])
                if self.project.vcs is not None:
                    fname = f.absoluteFilePath()
                    if states[os.path.normcase(fname)] == \
                            self.project.vcs.canBeCommitted:
                        node.addVcsStatus(self.project.vcs.vcsName())
                        self.project.clearStatusMonitorCachedState(
                            f.absoluteFilePath())
                    else:
                        node.addVcsStatus(self.tr("local"))
                self._addItem(node, parentItem)
            if repopulate:
                self.endInsertRows()

    def projectClosed(self):
        """
        Public method called after a project has been closed.
        """
        self.__vcsStatus = {}
        
        self.watchedItems = {}
        watchedDirs = self.watcher.directories()
        if watchedDirs:
            self.watcher.removePaths(watchedDirs)
        
        self.rootItem.removeChildren()
        self.beginResetModel()
        self.endResetModel()
        
        # reset the module parser cache
        Utilities.ModuleParser.resetParsedModules()
        
    def projectOpened(self):
        """
        Public method used to populate the model after a project has been
        opened.
        """
        self.__vcsStatus = {}
        states = {}
        keys = list(self.projectBrowserTypes.keys())[:]
        
        if self.project.vcs is not None:
            for key in keys:
                for fn in self.project.pdata[key]:
                    states[os.path.normcase(
                        os.path.join(self.project.ppath, fn))] = 0
            
            self.project.vcs.clearStatusCache()
            states = self.project.vcs.vcsAllRegisteredStates(
                states, self.project.ppath)
        
        self.inRefresh = True
        for key in keys:
            # Show the entry in bold in the others browser to make it more
            # distinguishable
            if key == "OTHERS":
                bold = True
            else:
                bold = False
            
            if key == "SOURCES":
                sourceLanguage = self.project.pdata["PROGLANGUAGE"][0]
            else:
                sourceLanguage = ""
            
            for fn in self.project.pdata[key]:
                fname = os.path.join(self.project.ppath, fn)
                parentItem, dt = self.findParentItemByName(
                    self.projectBrowserTypes[key], fn)
                if os.path.isdir(fname):
                    itm = ProjectBrowserDirectoryItem(
                        parentItem, fname, self.projectBrowserTypes[key],
                        False, bold)
                else:
                    itm = ProjectBrowserFileItem(
                        parentItem, fname, self.projectBrowserTypes[key],
                        False, bold, sourceLanguage=sourceLanguage)
                self._addItem(itm, parentItem)
                if self.project.vcs is not None:
                    if states[os.path.normcase(fname)] == \
                            self.project.vcs.canBeCommitted:
                        itm.addVcsStatus(self.project.vcs.vcsName())
                    else:
                        itm.addVcsStatus(self.tr("local"))
                else:
                    itm.addVcsStatus("")
        self.inRefresh = False
        self.beginResetModel()
        self.endResetModel()

    def findParentItemByName(self, type_, name, dontSplit=False):
        """
        Public method to find an item given its name.
        
        <b>Note</b>: This method creates all necessary parent items, if they
        don't exist.
        
        @param type_ type of the item
        @param name name of the item (string)
        @param dontSplit flag indicating the name should not be split (boolean)
        @return reference to the item found and the new display name (string)
        """
        if dontSplit:
            pathlist = []
            pathlist.append(name)
            pathlist.append("ignore_me")
        else:
            pathlist = re.split(r'/|\\', name)
        
        if len(pathlist) > 1:
            olditem = self.rootItem
            path = self.project.ppath
            for p in pathlist[:-1]:
                itm = self.findChildItem(p, 0, olditem)
                path = os.path.join(path, p)
                if itm is None:
                    itm = ProjectBrowserSimpleDirectoryItem(
                        olditem, type_, p, path)
                    self.__addVCSStatus(itm, path)
                    if self.inRefresh:
                        self._addItem(itm, olditem)
                    else:
                        if olditem == self.rootItem:
                            oldindex = QModelIndex()
                        else:
                            oldindex = self.createIndex(
                                olditem.row(), 0, olditem)
                        self.addItem(itm, oldindex)
                else:
                    if type_ and type_ not in itm.getProjectTypes():
                        itm.addProjectType(type_)
                        index = self.createIndex(itm.row(), 0, itm)
                        self.dataChanged.emit(index, index)
                olditem = itm
            return (itm, pathlist[-1])
        else:
            return (self.rootItem, name)
    
    def findChildItem(self, text, column, parentItem=None):
        """
        Public method to find a child item given some text.
        
        @param text text to search for (string)
        @param column column to search in (integer)
        @param parentItem reference to parent item
        @return reference to the item found
        """
        if parentItem is None:
            parentItem = self.rootItem
        
        for itm in parentItem.children():
            if itm.data(column) == text:
                return itm
        
        return None
        
    def addNewItem(self, typeString, name, additionalTypeStrings=[]):
        """
        Public method to add a new item to the model.
        
        @param typeString string denoting the type of the new item (string)
        @param name name of the new item (string)
        @param additionalTypeStrings names of additional types (list of string)
        """
        # Show the entry in bold in the others browser to make it more
        # distinguishable
        if typeString == "OTHERS":
            bold = True
        else:
            bold = False
        
        fname = os.path.join(self.project.ppath, name)
        parentItem, dt = self.findParentItemByName(
            self.projectBrowserTypes[typeString], name)
        if parentItem == self.rootItem:
            parentIndex = QModelIndex()
        else:
            parentIndex = self.createIndex(parentItem.row(), 0, parentItem)
        if os.path.isdir(fname):
            itm = ProjectBrowserDirectoryItem(
                parentItem, fname, self.projectBrowserTypes[typeString],
                False, bold)
        else:
            if typeString == "SOURCES":
                sourceLanguage = self.project.pdata["PROGLANGUAGE"][0]
            else:
                sourceLanguage = ""
            itm = ProjectBrowserFileItem(
                parentItem, fname, self.projectBrowserTypes[typeString],
                False, bold, sourceLanguage=sourceLanguage)
        self.__addVCSStatus(itm, fname)
        if additionalTypeStrings:
            for additionalTypeString in additionalTypeStrings:
                type_ = self.projectBrowserTypes[additionalTypeString]
                itm.addProjectType(type_)
        self.addItem(itm, parentIndex)
    
    def renameItem(self, name, newFilename):
        """
        Public method to rename an item.
        
        @param name the old display name (string)
        @param newFilename new filename of the item (string)
        """
        itm = self.findItem(name)
        if itm is None:
            return
        
        index = self.createIndex(itm.row(), 0, itm)
        itm.setName(newFilename)
        self.dataChanged.emit(index, index)
        self.repopulateItem(newFilename)
    
    def findItem(self, name):
        """
        Public method to find an item given its name.
        
        @param name name of the item (string)
        @return reference to the item found
        """
        if QDir.isAbsolutePath(name):
            name = self.project.getRelativePath(name)
        pathlist = re.split(r'/|\\', name)
        if len(pathlist) > 0:
            olditem = self.rootItem
            for p in pathlist:
                itm = self.findChildItem(p, 0, olditem)
                if itm is None:
                    return None
                olditem = itm
            return itm
        else:
            return None
    
    def itemIndexByName(self, name):
        """
        Public method to find an item's index given its name.
        
        @param name name of the item (string)
        @return index of the item found (QModelIndex)
        """
        itm = self.findItem(name)
        if itm is None:
            index = QModelIndex()
        else:
            index = self.createIndex(itm.row(), 0, itm)
        return index
    
    def itemIndexByNameAndLine(self, name, lineno):
        """
        Public method to find an item's index given its name.
        
        @param name name of the item (string)
        @param lineno one based line number of the item (integer)
        @return index of the item found (QModelIndex)
        """
        index = QModelIndex()
        itm = self.findItem(name)
        if itm is not None and \
           isinstance(itm, ProjectBrowserFileItem):
            olditem = itm
            autoPopulate = Preferences.getProject("AutoPopulateItems")
            while itm is not None:
                if not itm.isPopulated():
                    if itm.isLazyPopulated() and autoPopulate:
                        self.populateItem(itm)
                    else:
                        break
                for child in itm.children():
                    try:
                        start, end = child.boundaries()
                        if end == -1:
                            end = 1000000   # assume end of file
                        if start <= lineno <= end:
                            itm = child
                            break
                    except AttributeError:
                        pass
                else:
                    itm = None
                if itm:
                    olditem = itm
            index = self.createIndex(olditem.row(), 0, olditem)
        
        return index
    
    def directoryChanged(self, path):
        """
        Public slot to handle the directoryChanged signal of the watcher.
        
        @param path path of the directory (string)
        """
        if path not in self.watchedItems:
            # just ignore the situation we don't have a reference to the item
            return
        
        if Preferences.getUI("BrowsersListHiddenFiles"):
            filter = QDir.Filters(QDir.AllEntries |
                                  QDir.Hidden |
                                  QDir.NoDotAndDotDot)
        else:
            filter = QDir.Filters(QDir.AllEntries | QDir.NoDot | QDir.NoDotDot)
        
        for itm in self.watchedItems[path]:
            oldCnt = itm.childCount()
            
            qdir = QDir(itm.dirName())
            
            entryInfoList = qdir.entryInfoList(filter)
            
            # step 1: check for new entries
            children = itm.children()
            for f in entryInfoList:
                fpath = Utilities.toNativeSeparators(f.absoluteFilePath())
                childFound = False
                for child in children:
                    if child.name() == fpath:
                        childFound = True
                        children.remove(child)
                        break
                if childFound:
                    continue
                
                cnt = itm.childCount()
                self.beginInsertRows(
                    self.createIndex(itm.row(), 0, itm), cnt, cnt)
                if f.isDir():
                    node = ProjectBrowserDirectoryItem(
                        itm,
                        Utilities.toNativeSeparators(f.absoluteFilePath()),
                        itm.getProjectTypes()[0],
                        False)
                else:
                    node = ProjectBrowserFileItem(
                        itm,
                        Utilities.toNativeSeparators(f.absoluteFilePath()),
                        itm.getProjectTypes()[0])
                self._addItem(node, itm)
                if self.project.vcs is not None:
                    self.project.vcs.clearStatusCache()
                    state = self.project.vcs.vcsRegisteredState(node.name())
                    if state == self.project.vcs.canBeCommitted:
                        node.addVcsStatus(self.project.vcs.vcsName())
                    else:
                        node.addVcsStatus(self.tr("local"))
                self.endInsertRows()
            
            # step 2: check for removed entries
            if len(entryInfoList) != itm.childCount():
                for row in range(oldCnt - 1, -1, -1):
                    child = itm.child(row)
                    childname = Utilities.fromNativeSeparators(child.name())
                    entryFound = False
                    for f in entryInfoList:
                        if f.absoluteFilePath() == childname:
                            entryFound = True
                            entryInfoList.remove(f)
                            break
                    if entryFound:
                        continue
                    
                    self._removeWatchedItem(child)
                    self.beginRemoveRows(
                        self.createIndex(itm.row(), 0, itm), row, row)
                    itm.removeChild(child)
                    self.endRemoveRows()
    
    def __addVCSStatus(self, item, name):
        """
        Private method used to set the vcs status of a node.
        
        @param item item to work on
        @param name filename belonging to this item (string)
        """
        if self.project.vcs is not None:
            state = self.project.vcs.vcsRegisteredState(name)
            if state == self.project.vcs.canBeCommitted:
                item.addVcsStatus(self.project.vcs.vcsName())
            else:
                item.addVcsStatus(self.tr("local"))
        else:
            item.addVcsStatus("")
    
    def __updateVCSStatus(self, item, name, recursive=True):
        """
        Private method used to update the vcs status of a node.
        
        @param item item to work on
        @param name filename belonging to this item (string)
        @keyparam recursive flag indicating a recursive update (boolean)
        """
        if self.project.vcs is not None:
            self.project.vcs.clearStatusCache()
            state = self.project.vcs.vcsRegisteredState(name)
            if state == self.project.vcs.canBeCommitted:
                item.setVcsStatus(self.project.vcs.vcsName())
            else:
                item.setVcsStatus(self.tr("local"))
            if recursive:
                name = os.path.dirname(name)
                parentItem = item.parent()
                if name and parentItem is not self.rootItem:
                    self.__updateVCSStatus(parentItem, name, recursive)
        else:
            item.setVcsStatus("")
        
        index = self.createIndex(item.row(), 0, item)
        self.dataChanged.emit(index, index)
    
    def updateVCSStatus(self, name, recursive=True):
        """
        Public method used to update the vcs status of a node.
        
        @param name filename belonging to this item (string)
        @param recursive flag indicating a recursive update (boolean)
        """
        item = self.findItem(name)
        if item:
            self.__updateVCSStatus(item, name, recursive)
    
    def removeItem(self, name):
        """
        Public method to remove a named item.
        
        @param name file or directory name of the item (string).
        """
        fname = os.path.basename(name)
        parentItem = self.findParentItemByName(0, name)[0]
        if parentItem == self.rootItem:
            parentIndex = QModelIndex()
        else:
            parentIndex = self.createIndex(parentItem.row(), 0, parentItem)
        childItem = self.findChildItem(fname, 0, parentItem)
        if childItem is not None:
            self.beginRemoveRows(parentIndex, childItem.row(), childItem.row())
            parentItem.removeChild(childItem)
            self.endRemoveRows()
    
    def repopulateItem(self, name):
        """
        Public method to repopulate an item.
        
        @param name name of the file relative to the project root (string)
        """
        itm = self.findItem(name)
        if itm is None:
            return
        
        if itm.isLazyPopulated():
            if not itm.isPopulated():
                # item is not populated yet, nothing to do
                return
            
            if itm.childCount():
                index = self.createIndex(itm.row(), 0, itm)
                self.beginRemoveRows(index, 0, itm.childCount() - 1)
                itm.removeChildren()
                self.endRemoveRows()
            Utilities.ModuleParser.resetParsedModule(
                os.path.join(self.project.ppath, name))
            
            self.populateItem(itm, True)
    
    def projectPropertiesChanged(self):
        """
        Public method to react on a change of the project properties.
        """
        # nothing to do for now
        return

    def changeVCSStates(self, statesList):
        """
        Public slot to record the (non normal) VCS states.
        
        @param statesList list of VCS state entries (list of strings) giving
            the states in the first column and the path relative to the project
            directory starting with the third column. The allowed status flags
            are:
            <ul>
                <li>"A" path was added but not yet comitted</li>
                <li>"M" path has local changes</li>
                <li>"O" path was removed</li>
                <li>"R" path was deleted and then re-added</li>
                <li>"U" path needs an update</li>
                <li>"Z" path contains a conflict</li>
                <li>" " path is back at normal</li>
            </ul>
        """
        statesList.sort()
        lastHead = ""
        itemCache = {}
        if len(statesList) == 1 and statesList[0] == '--RESET--':
            statesList = []
            for name in list(self.__vcsStatus.keys()):
                statesList.append(" {0}".format(name))
        
        for name in statesList:
            state = name[0]
            name = name[1:].strip()
            if state == ' ':
                if name in self.__vcsStatus:
                    del self.__vcsStatus[name]
            else:
                self.__vcsStatus[name] = state
            
            try:
                itm = itemCache[name]
            except KeyError:
                itm = self.findItem(name)
                if itm:
                    itemCache[name] = itm
            if itm:
                itm.setVcsState(state)
                itm.setVcsStatus(self.project.vcs.vcsName())
                index1 = self.createIndex(itm.row(), 0, itm)
                index2 = self.createIndex(
                    itm.row(), self.rootItem.columnCount(), itm)
                self.dataChanged.emit(index1, index2)
            
            head, tail = os.path.split(name)
            if head != lastHead:
                if lastHead:
                    self.__changeParentsVCSState(lastHead, itemCache)
                lastHead = head
        if lastHead:
            self.__changeParentsVCSState(lastHead, itemCache)
        try:
            globalVcsStatus = sorted(self.__vcsStatus.values())[-1]
        except IndexError:
            globalVcsStatus = ' '
        self.vcsStateChanged.emit(globalVcsStatus)

    def __changeParentsVCSState(self, path, itemCache):
        """
        Private method to recursively change the parents VCS state.
        
        @param path pathname of parent item (string)
        @param itemCache reference to the item cache used to store
            references to named items
        """
        while path:
            try:
                itm = itemCache[path]
            except KeyError:
                itm = self.findItem(path)
                if itm:
                    itemCache[path] = itm
            if itm:
                state = " "
                for id_ in itm.children():
                    if state < id_.vcsState:
                        state = id_.vcsState
                if state != itm.vcsState:
                    itm.setVcsState(state)
                    index1 = self.createIndex(itm.row(), 0, itm)
                    index2 = self.createIndex(
                        itm.row(), self.rootItem.columnCount(), itm)
                    self.dataChanged.emit(index1, index2)
            path, tail = os.path.split(path)
    
    def preferencesChanged(self):
        """
        Public method used to handle a change in preferences.
        """
        for code in list(self.colorNames.keys()):
            color = Preferences.getProjectBrowserColour(self.colorNames[code])
            if color.name() == self.itemBackgroundColors[code].name():
                continue
            
            self.itemBackgroundColors[code] = color
        
        color = Preferences.getProjectBrowserColour("Highlighted")
        if self.highLightColor.name() != color.name():
            self.highLightColor = color