eric7/VCS/StatusWidget.py

Mon, 20 Sep 2021 19:47:18 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Mon, 20 Sep 2021 19:47:18 +0200
branch
eric7
changeset 8620
84f7f7867b5f
parent 8619
2dc55ddafc68
child 8621
8c9f41115c04
permissions
-rw-r--r--

Continued implementing the VCS status widget for the left side.

# -*- coding: utf-8 -*-

# Copyright (c) 2021 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing a VCS Status widget for the sidebar/toolbar.
"""

import contextlib
import os

from PyQt6.QtCore import pyqtSlot, Qt
from PyQt6.QtWidgets import (
    QWidget, QVBoxLayout, QHBoxLayout, QLabel, QSizePolicy, QListView,
    QListWidget, QListWidgetItem, QToolButton, QAbstractItemView
)

from EricWidgets.EricApplication import ericApp
from EricWidgets import EricMessageBox

import Preferences
import UI.PixmapCache


class StatusWidget(QWidget):
    """
    Class implementing a VCS Status widget for the sidebar/toolbox.
    """
    StatusDataRole = Qt.ItemDataRole.UserRole + 1
    
    def __init__(self, project, parent=None):
        """
        Constructor
        
        @param project reference to the project object
        @type Project
        @param parent reference to the parent widget (defaults to None)
        @type QWidget (optional)
        """
        super().__init__(parent)
        self.setObjectName("VcsStatusWidget")
        
        self.__project = project
        
        self.__layout = QVBoxLayout()
        self.__layout.setObjectName("MainLayout")
        self.__layout.setContentsMargins(0, 3, 0, 0)
        self.__topLayout = QHBoxLayout()
        self.__topLayout.setObjectName("topLayout")
        
        # Create the top row
        self.__infoLabel = QLabel(self)
        self.__infoLabel.setSizePolicy(
            QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred)
        self.__topLayout.addWidget(self.__infoLabel)
        
        self.__commitToggleButton = QToolButton(self)
        self.__commitToggleButton.setIcon(UI.PixmapCache.getIcon("check"))
        self.__commitToggleButton.setToolTip(
            self.tr("Press to toggle the commit markers"))
        self.__commitToggleButton.clicked.connect(self.__toggleCheckMark)
        self.__topLayout.addWidget(self.__commitToggleButton)
        
        self.__commitButton = QToolButton(self)
        self.__commitButton.setIcon(UI.PixmapCache.getIcon("vcsCommit"))
        self.__commitButton.setToolTip(
            self.tr("Press to commit the marked entries"))
        self.__commitButton.clicked.connect(self.__commit)
        self.__topLayout.addWidget(self.__commitButton)
        
        self.__addButton = QToolButton(self)
        self.__addButton.setIcon(UI.PixmapCache.getIcon("vcsAdd"))
        self.__addButton.setToolTip(
            self.tr("Press to add the selected, untracked entries"))
        self.__addButton.clicked.connect(self.__addUntracked)
        self.__topLayout.addWidget(self.__addButton)
        
        self.__reloadButton = QToolButton(self)
        self.__reloadButton.setIcon(UI.PixmapCache.getIcon("reload"))
        self.__reloadButton.setToolTip(
            self.tr("Press to reload the status list"))
        self.__reloadButton.clicked.connect(self.__reload)
        self.__topLayout.addWidget(self.__reloadButton)
        
        self.__layout.addLayout(self.__topLayout)
        
        self.__statusList = QListWidget(self)
        self.__statusList.setAlternatingRowColors(True)
        self.__statusList.setSortingEnabled(True)
        self.__statusList.setViewMode(QListView.ViewMode.ListMode)
        self.__statusList.setTextElideMode(Qt.TextElideMode.ElideLeft)
        self.__statusList.setSelectionMode(
            QAbstractItemView.SelectionMode.ExtendedSelection)
        self.__layout.addWidget(self.__statusList)
        
        self.setLayout(self.__layout)
        
        self.__statusIcons = {
            "A": "vcs-added",    # added
            "M": "vcs-modified",    # modified
            "O": "vcs-removed",    # removed
            "R": "vcs-renamed",    # renamed
            "U": "vcs-update-required",    # update needed
            "Z": "vcs-conflicting",    # conflict
            "?": "vcs-untracked",    # not tracked
            "!": "vcs-missing",    # missing
        }
        self.__statusTexts = {
            "A": self.tr("added"),
            "M": self.tr("modified"),
            "O": self.tr("removed"),
            "R": self.tr("renamed"),
            "U": self.tr("needs update"),
            "Z": self.tr("conflict"),
            "?": self.tr("not tracked"),
            "!": self.tr("missing"),
        }
        
        if self.__project.isOpen():
            self.__projectOpened()
        else:
            self.__projectClosed()
        
        self.__project.projectOpened.connect(self.__projectOpened)
        self.__project.projectClosed.connect(self.__projectClosed)
        self.__project.vcsCommitted.connect(self.__committed)
        self.__project.vcsStatusMonitorInfo.connect(self.__setInfoText)
        self.__project.vcsStatusMonitorAllData.connect(
            self.__processStatusData)
    
    @pyqtSlot()
    def __projectOpened(self):
        """
        Private slot to handle the opening of a project.
        """
        self.__reloadButton.setEnabled(True)
    
    @pyqtSlot()
    def __projectClosed(self):
        """
        Private slot to handle the closing of a project.
        """
        self.__infoLabel.setText(self.tr("No project open."))
        
        self.__reloadButton.setEnabled(False)
        
        self.__statusList.clear()
    
    @pyqtSlot(str)
    def __setInfoText(self, info):
        """
        Private slot to set the info label text.
        
        @param info text to be shown
        @type str
        """
        self.__infoLabel.setText(info)
    
    @pyqtSlot()
    def __reload(self):
        """
        Private slot to reload the status list.
        """
        self.__project.checkVCSStatus()
    
    @pyqtSlot(dict)
    def __processStatusData(self, data):
        """
        Private slot to process the status data emitted by the project.
        
        Each entry of the status data consists of a status flag and and the
        path relative to the project directory starting with the third column.
        The known status flags are:
        <ul>
            <li>"A" path was added but not yet committed</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 not tracked</li>
            <li>"!" path is missing</li>
            <li>" " path is back at normal</li>
        </ul>
        
        @param data dictionary containing the status data
        @type dict
        """
        self.__statusList.clear()
        
        for name, status in data.items():
            if status:
                itm = QListWidgetItem(name, self.__statusList)
                with contextlib.suppress(KeyError):
                    itm.setToolTip(self.__statusTexts[status])
                    itm.setIcon(UI.PixmapCache.getIcon(
                        self.__statusIcons[status]))
                    itm.setData(self.StatusDataRole, status)
                if status in "AMOR":
                    itm.setFlags(
                        itm.flags() | Qt.ItemFlag.ItemIsUserCheckable)
                    itm.setCheckState(Qt.CheckState.Checked)
                else:
                    itm.setFlags(
                        itm.flags() & ~Qt.ItemFlag.ItemIsUserCheckable)
        
        self.__statusList.sortItems(Qt.SortOrder.AscendingOrder)
    
    @pyqtSlot()
    def __toggleCheckMark(self):
        """
        Private slot to toggle the check marks.
        """
        for row in range(self.__statusList.count()):
            itm = self.__statusList.item(row)
            if (
                itm.flags() & Qt.ItemFlag.ItemIsUserCheckable ==
                Qt.ItemFlag.ItemIsUserCheckable
            ):
                if itm.checkState() == Qt.CheckState.Unchecked:
                    itm.setCheckState(Qt.CheckState.Checked)
                else:
                    itm.setCheckState(Qt.CheckState.Unchecked)
    
    @pyqtSlot()
    def __commit(self):
        """
        Private slot to handle the commit button.
        """
        projectPath = self.__project.getProjectPath()
        names = []
        
        for row in range(self.__statusList.count()):
            itm = self.__statusList.item(row)
            if itm.checkState() == Qt.CheckState.Checked:
                names.append(os.path.join(projectPath, itm.text()))
        
        if not names:
            EricMessageBox.information(
                self,
                self.tr("Commit"),
                self.tr("""There are no entries selected to be"""
                        """ committed."""))
            return
        
        if Preferences.getVCS("AutoSaveFiles"):
            vm = ericApp().getObject("ViewManager")
            for name in names:
                vm.saveEditor(name)
        vcs = self.__project.getVcs()
        vcs and vcs.vcsCommit(names, '')
    
    @pyqtSlot()
    def __committed(self):
        """
        Private slot called after the commit has been completed.
        """
        self.__reload()
    
    @pyqtSlot()
    def __addUntracked(self):
        """
        Private slot to add the selected untracked entries.
        """
        projectPath = self.__project.getProjectPath()
        
        names = [
            os.path.join(projectPath, itm.text())
            for itm in self.__statusList.selectedItems()
            if itm.data(self.StatusDataRole) == "?"
        ]
        
        if not names:
            EricMessageBox.information(
                self,
                self.tr("Add"),
                self.tr("""There are no unversioned entries"""
                        """ available/selected."""))
            return
        
        vcs = self.__project.getVcs()
        vcs and vcs.vcsAdd(names)
        self.__reload()

eric ide

mercurial