eric7/VCS/StatusWidget.py

Wed, 06 Oct 2021 18:39:50 +0200

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Wed, 06 Oct 2021 18:39:50 +0200
branch
eric7
changeset 8676
3348ac06e4bb
parent 8670
12e7fbf2445a
child 8677
2e3d02a0f0b6
permissions
-rw-r--r--

Added code to the VCS Status widget to commit by pressing Ctrl+Enter or Ctrl+Return in the Quick Commit entry.

# -*- 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, QEvent
from PyQt6.QtWidgets import (
    QWidget, QVBoxLayout, QHBoxLayout, QLabel, QSizePolicy, QListView,
    QListWidget, QListWidgetItem, QToolButton, QAbstractItemView, QMenu,
    QGroupBox, QDialog
)

from EricWidgets.EricApplication import ericApp
from EricWidgets import EricMessageBox
from EricWidgets.EricSpellCheckedTextEdit import EricSpellCheckedTextEdit
from EricWidgets.EricListSelectionDialog import EricListSelectionDialog

import Preferences
import UI.PixmapCache
import Utilities


class StatusWidget(QWidget):
    """
    Class implementing a VCS Status widget for the sidebar/toolbox.
    """
    StatusDataRole = Qt.ItemDataRole.UserRole + 1
    
    def __init__(self, project, viewmanager, parent=None):
        """
        Constructor
        
        @param project reference to the project object
        @type Project
        @param viewmanager reference to the viewmanager object
        @type ViewManager
        @param parent reference to the parent widget (defaults to None)
        @type QWidget (optional)
        """
        super().__init__(parent)
        self.setObjectName("VcsStatusWidget")
        
        self.__project = project
        self.__vm = viewmanager
        
        self.__layout = QVBoxLayout()
        self.__layout.setObjectName("MainLayout")
        self.__layout.setContentsMargins(0, 3, 0, 0)
        self.__topLayout = QHBoxLayout()
        self.__topLayout.setObjectName("topLayout")
        
        # Create the top area
        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 with options"))
        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.__actionsButton = QToolButton(self)
        self.__actionsButton.setIcon(
            UI.PixmapCache.getIcon("actionsToolButton"))
        self.__actionsButton.setToolTip(
            self.tr("Select action from menu"))
        self.__actionsButton.setPopupMode(
            QToolButton.ToolButtonPopupMode.InstantPopup)
        self.__topLayout.addWidget(self.__actionsButton)
        
        self.__layout.addLayout(self.__topLayout)
        ###################################################################
        
        # Create the middle part
        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.__statusList.itemSelectionChanged.connect(
            self.__updateEnabledStates)
        self.__statusList.itemDoubleClicked.connect(self.__itemDoubleClicked)
        self.__statusList.itemChanged.connect(self.__updateEnabledStates)
        self.__layout.addWidget(self.__statusList)
        ###################################################################
        
        # create the Quick Commit area
        self.__quickCommitGroup = QGroupBox(self.tr("Quick Commit"), self)
        self.__quickCommitLayout = QVBoxLayout()
        self.__quickCommitEdit = EricSpellCheckedTextEdit(self)
        self.__quickCommitEdit.setSizePolicy(
            QSizePolicy.Policy.Expanding,
            QSizePolicy.Policy.Preferred)
        self.__quickCommitEdit.setMaximumHeight(100)
        self.__quickCommitEdit.setTabChangesFocus(True)
        self.__quickCommitEdit.installEventFilter(self)
        self.__quickCommitEdit.textChanged.connect(
            self.__quickCommitEditTextChanged)
        self.__quickCommitLayout.addWidget(self.__quickCommitEdit)
        
        self.__quickCommitLayout2 = QHBoxLayout()
        self.__quickCommitLayout2.addStretch()
        
        self.__quickCommitHistoryButton = QToolButton(self)
        self.__quickCommitHistoryButton.setIcon(
            UI.PixmapCache.getIcon("history"))
        self.__quickCommitHistoryButton.setToolTip(
            self.tr("Select commit message from previous commits"))
        self.__quickCommitHistoryButton.clicked.connect(
            self.__selectQuickCommitMessage)
        self.__quickCommitLayout2.addWidget(self.__quickCommitHistoryButton)
        
        self.__quickCommitHistoryClearButton = QToolButton(self)
        self.__quickCommitHistoryClearButton.setIcon(
            UI.PixmapCache.getIcon("historyClear"))
        self.__quickCommitHistoryClearButton.setToolTip(
            self.tr("Clear the list of saved commit messages"))
        self.__quickCommitHistoryClearButton.clicked.connect(
            self.__clearCommitMessages)
        self.__quickCommitLayout2.addWidget(
            self.__quickCommitHistoryClearButton)
        
        self.__quickCommitButton = QToolButton(self)
        self.__quickCommitButton.setIcon(
            UI.PixmapCache.getIcon("vcsCommit"))
        self.__quickCommitButton.setToolTip(
            self.tr("Press to commit the marked entries"))
        self.__quickCommitButton.clicked.connect(self.__quickCommit)
        self.__quickCommitLayout2.addWidget(self.__quickCommitButton)
        
        self.__quickCommitLayout.addLayout(self.__quickCommitLayout2)
        self.__quickCommitGroup.setLayout(self.__quickCommitLayout)
        self.__layout.addWidget(self.__quickCommitGroup)
        ###################################################################
        
        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"),
        }
        
        self.__initActionsMenu()
        
        self.__reset()
        
        if self.__project.isOpen():
            self.__projectOpened()
        else:
            self.__projectClosed()
        
        self.__project.projectOpened.connect(self.__projectOpened)
        self.__project.projectClosed.connect(self.__projectClosed)
        self.__project.projectPropertiesChanged.connect(
            self.__setProjectSpellCheckData)
        self.__project.vcsCommitted.connect(self.__committed)
        self.__project.vcsStatusMonitorInfo.connect(self.__setInfoText)
        self.__project.vcsStatusMonitorAllData.connect(
            self.__processStatusData)
    
    def __initActionsMenu(self):
        """
        Private method to initialize the actions menu.
        """
        self.__actionsMenu = QMenu()
        self.__actionsMenu.setToolTipsVisible(True)
        self.__actionsMenu.aboutToShow.connect(self.__showActionsMenu)
        
        self.__commitAct = self.__actionsMenu.addAction(
            UI.PixmapCache.getIcon("vcsCommit"),
            self.tr("Commit"), self.__commit)
        self.__commitAct.setToolTip(self.tr(
            "Commit the marked entries with options"))
        self.__commitSelectAct = self.__actionsMenu.addAction(
            self.tr("Select all for commit"), self.__commitSelectAll)
        self.__commitDeselectAct = self.__actionsMenu.addAction(
            self.tr("Unselect all from commit"), self.__commitDeselectAll)
        
        self.__actionsMenu.addSeparator()
        
        self.__addAct = self.__actionsMenu.addAction(
            UI.PixmapCache.getIcon("vcsAdd"),
            self.tr("Add"), self.__addUntracked)
        self.__addAct.setToolTip(self.tr(
            "Add the selected, untracked entries"))
        self.__addAllAct = self.__actionsMenu.addAction(
            self.tr("Add All"), self.__addAllUntracked)
        self.__addAllAct.setToolTip(self.tr(
            "Add all untracked entries"))
        
        self.__actionsMenu.addSeparator()
        
        self.__diffAct = self.__actionsMenu.addAction(
            UI.PixmapCache.getIcon("vcsDiff"),
            self.tr("Differences"), self.__diff)
        self.__diffAct.setToolTip(self.tr(
            "Shows the differences of the selected entry in a"
            " separate dialog"))
        self.__sbsDiffAct = self.__actionsMenu.addAction(
            UI.PixmapCache.getIcon("vcsSbsDiff"),
            self.tr("Differences Side-By-Side"), self.__sbsDiff)
        self.__sbsDiffAct.setToolTip(self.tr(
            "Shows the differences of the selected entry side-by-side in"
            " a separate dialog"))
        self.__diffAllAct = self.__actionsMenu.addAction(
            self.tr("All Differences"), self.__diffAll)
        self.__diffAllAct.setToolTip(self.tr(
            "Shows the differences of all entries in a separate dialog"))
        
        self.__actionsMenu.addSeparator()
        
        self.__revertAct = self.__actionsMenu.addAction(
            UI.PixmapCache.getIcon("vcsRevert"),
            self.tr("Revert"), self.__revert)
        self.__revertAct.setToolTip(self.tr(
            "Reverts the changes of the selected files"))
        
        self.__actionsMenu.addSeparator()
        
        self.__forgetAct = self.__actionsMenu.addAction(
            self.tr("Forget Missing"), self.__forgetMissing)
        self.__forgetAct.setToolTip(self.tr(
            "Forgets about the selected missing files"))
        self.__restoreAct = self.__actionsMenu.addAction(
            self.tr("Restore Missing"), self.__restoreMissing)
        self.__restoreAct.setToolTip(self.tr(
            "Restores the selected missing files"))
        self.__actionsMenu.addSeparator()
        
        self.__editAct = self.__actionsMenu.addAction(
            UI.PixmapCache.getIcon("open"),
            self.tr("Edit Conflict"), self.__editConflict)
        self.__editAct.setToolTip(self.tr(
            "Edit the selected conflicting file"))
        self.__resolvedAct = self.__actionsMenu.addAction(
            UI.PixmapCache.getIcon("vcsResolved"),
            self.tr("Conflict Resolved"), self.__conflictResolved)
        self.__resolvedAct.setToolTip(self.tr(
            "Mark the selected conflicting file as resolved"))
        
        self.__actionsButton.setMenu(self.__actionsMenu)
    
    @pyqtSlot()
    def __projectOpened(self):
        """
        Private slot to handle the opening of a project.
        """
        self.__reloadButton.setEnabled(True)
        self.__setProjectSpellCheckData()
    
    @pyqtSlot()
    def __setProjectSpellCheckData(self):
        """
        Private slot to set the spell check properties of the
        quick commit area.
        """
        pwl, pel = self.__project.getProjectDictionaries()
        language = self.__project.getProjectSpellLanguage()
        self.__quickCommitEdit.setLanguageWithPWL(
            language, pwl or None, pel or None)
    
    @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.__reset()
    
    @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()
    
    def __reset(self):
        """
        Private method to reset the widget to default.
        """
        self.__statusList.clear()
        
        self.__commitToggleButton.setEnabled(False)
        self.__commitButton.setEnabled(False)
        self.__addButton.setEnabled(False)
        
        self.__quickCommitEdit.clear()
        self.__quickCommitGroup.setEnabled(False)
    
    def __updateEnabledStates(self):
        """
        Private method to set the enabled states depending on the list state.
        """
        modified = len(self.__getModifiedItems())
        unversioned = len(self.__getSelectedUnversionedItems())
        commitable = len(self.__getCommitableItems())
        
        self.__commitToggleButton.setEnabled(modified)
        self.__commitButton.setEnabled(commitable)
        self.__addButton.setEnabled(unversioned)
        
        self.__quickCommitGroup.setEnabled(commitable)
    
    @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
        """
        # step 1: remember all currently checked entries
        checkedEntries = [itm.text() for itm in self.__getCommitableItems()]
        selectedEntries = [itm.text()
                           for itm in self.__statusList.selectedItems()]
        knownEntries = [self.__statusList.item(row).text()
                        for row in range(self.__statusList.count())]
        
        # step 2: clear the list and re-populate it with new data
        self.__statusList.clear()
        
        block = self.__statusList.blockSignals(True)
        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)
                    if name in checkedEntries or name not in knownEntries:
                        itm.setCheckState(Qt.CheckState.Checked)
                    else:
                        itm.setCheckState(Qt.CheckState.Unchecked)
                else:
                    itm.setFlags(
                        itm.flags() & ~Qt.ItemFlag.ItemIsUserCheckable)
                itm.setSelected(name in selectedEntries)
        
        self.__statusList.sortItems(Qt.SortOrder.AscendingOrder)
        self.__statusList.blockSignals(block)
        
        self.__updateEnabledStates()
    
    @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)
    
    def __setCheckMark(self, checked):
        """
        Private method to set or unset all check marks.
        
        @param checked check mark state to be set
        @type bool
        """
        for row in range(self.__statusList.count()):
            itm = self.__statusList.item(row)
            if (
                itm.flags() & Qt.ItemFlag.ItemIsUserCheckable ==
                Qt.ItemFlag.ItemIsUserCheckable
            ):
                if checked:
                    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 __commitSelectAll(self):
        """
        Private slot to select all entries for commit.
        """
        self.__setCheckMark(True)
    
    @pyqtSlot()
    def __commitDeselectAll(self):
        """
        Private slot to deselect all entries from commit.
        """
        self.__setCheckMark(False)
    
    @pyqtSlot()
    def __addUntracked(self, allItems=False):
        """
        Private slot to add the selected untracked entries.
        
        @param allItems flag indicating to show the differences of all files
            (defaults to False)
        @type bool (optional)
        """
        projectPath = self.__project.getProjectPath()
        
        names = [
            os.path.join(projectPath, itm.text())
            for itm in self.__getUnversionedItems()
        ] if allItems else [
            os.path.join(projectPath, itm.text())
            for itm in self.__getSelectedUnversionedItems()
        ]
        
        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()
    
    @pyqtSlot(QListWidgetItem)
    def __itemDoubleClicked(self, itm):
        """
        Private slot to handle double clicking an item.
        
        @param itm reference to the double clicked item
        @type QListWidgetItem
        """
        projectPath = self.__project.getProjectPath()
        
        if itm.data(self.StatusDataRole) in "MZ":
            # modified and conflicting items
            name = os.path.join(projectPath, itm.text())
            vcs = self.__project.getVcs()
            vcs and vcs.vcsDiff(name)
    
    ###########################################################################
    ## Menu handling methods
    ###########################################################################
    
    def __showActionsMenu(self):
        """
        Private slot to prepare the actions button menu before it is shown.
        """
        modified = len(self.__getSelectedModifiedItems())
        allModified = len(self.__getModifiedItems())
        unversioned = len(self.__getSelectedUnversionedItems())
        allUnversioned = len(self.__getUnversionedItems())
        missing = len(self.__getMissingItems())
        commitable = len(self.__getCommitableItems())
        commitableUnselected = len(self.__getCommitableUnselectedItems())
        conflicting = len(self.__getSelectedConflictingItems())

        self.__addAct.setEnabled(unversioned)
        self.__addAllAct.setEnabled(allUnversioned)
        self.__diffAct.setEnabled(modified)
        self.__sbsDiffAct.setEnabled(modified == 1)
        self.__diffAllAct.setEnabled(allModified)
        self.__revertAct.setEnabled(modified)
        self.__forgetAct.setEnabled(missing)
        self.__restoreAct.setEnabled(missing)
        self.__commitAct.setEnabled(commitable)
        self.__commitSelectAct.setEnabled(commitableUnselected)
        self.__commitDeselectAct.setEnabled(commitable)
        self.__editAct.setEnabled(conflicting == 1)
        self.__resolvedAct.setEnabled(conflicting)
    
    def __getCommitableItems(self):
        """
        Private method to retrieve all entries the user wants to commit.
        
        @return list of all items, the user has checked
        @rtype list of QListWidgetItem
        """
        commitableItems = []
        for row in range(self.__statusList.count()):
            itm = self.__statusList.item(row)
            if (
                itm.checkState() == Qt.CheckState.Checked
            ):
                commitableItems.append(itm)
        return commitableItems
    
    def __getCommitableUnselectedItems(self):
        """
        Private method to retrieve all entries the user may commit but hasn't
        selected.
        
        @return list of all items, the user has checked
        @rtype list of QListWidgetItem
        """
        items = []
        for row in range(self.__statusList.count()):
            itm = self.__statusList.item(row)
            if (
                (itm.flags() & Qt.ItemFlag.ItemIsUserCheckable ==
                 Qt.ItemFlag.ItemIsUserCheckable) and
                itm.checkState() == Qt.CheckState.Unchecked
            ):
                items.append(itm)
        return items
    
    def __getModifiedItems(self):
        """
        Private method to retrieve all entries, that have a modified status.
        
        @return list of all items with a modified status
        @rtype list of QListWidgetItem
        """
        items = []
        for row in range(self.__statusList.count()):
            itm = self.__statusList.item(row)
            if itm.data(self.StatusDataRole) in "AMOR":
                items.append(itm)
        return items
    
    def __getSelectedModifiedItems(self):
        """
        Private method to retrieve all selected entries, that have a modified
        status.
        
        @return list of all selected entries with a modified status
        @rtype list of QListWidgetItem
        """
        return [itm for itm in self.__statusList.selectedItems()
                if itm.data(self.StatusDataRole) in "AMOR"]
    
    def __getUnversionedItems(self):
        """
        Private method to retrieve all entries, that have an unversioned
        status.
        
        @return list of all items with an unversioned status
        @rtype list of QListWidgetItem
        """
        items = []
        for row in range(self.__statusList.count()):
            itm = self.__statusList.item(row)
            if itm.data(self.StatusDataRole) == "?":
                items.append(itm)
        return items
    
    def __getSelectedUnversionedItems(self):
        """
        Private method to retrieve all selected entries, that have an
        unversioned status.
        
        @return list of all items with an unversioned status
        @rtype list of QListWidgetItem
        """
        return [itm for itm in self.__statusList.selectedItems()
                if itm.data(self.StatusDataRole) == "?"]
    
    def __getMissingItems(self):
        """
        Private method to retrieve all entries, that have a missing status.
        
        @return list of all items with a missing status
        @rtype list of QListWidgetItem
        """
        return [itm for itm in self.__statusList.selectedItems()
                if itm.data(self.StatusDataRole) == "!"]
    
    def __getSelectedConflictingItems(self):
        """
        Private method to retrieve all selected entries, that have a conflict
        status.
        
        @return list of all selected entries with a conflict status
        @rtype list of QListWidgetItem
        """
        return [itm for itm in self.__statusList.selectedItems()
                if itm.data(self.StatusDataRole) == "Z"]
    
    @pyqtSlot()
    def __addAllUntracked(self):
        """
        Private slot to handle the Add All action menu entry.
        """
        self.__addUntracked(allItems=True)
    
    @pyqtSlot()
    def __diff(self, allItems=False):
        """
        Private slot to handle the Differences action menu entry.
        
        @param allItems flag indicating to show the differences of all files
            (defaults to False)
        @type bool (optional)
        """
        projectPath = self.__project.getProjectPath()
        
        names = [
            os.path.join(projectPath, itm.text())
            for itm in self.__getModifiedItems()
        ] if allItems else [
            os.path.join(projectPath, itm.text())
            for itm in self.__getSelectedModifiedItems()
        ]
        if not names:
            EricMessageBox.information(
                self,
                self.tr("Differences"),
                self.tr("""There are no uncommitted changes"""
                        """ available/selected."""))
            return
        
        vcs = self.__project.getVcs()
        vcs and vcs.vcsDiff(names)
    
    @pyqtSlot()
    def __diffAll(self):
        """
        Private slot to handle the All Differences action menu entry.
        """
        self.__diff(allItems=True)
    
    @pyqtSlot()
    def __sbsDiff(self):
        """
        Private slot to handle the Side-By-Side Differences action menu entry.
        """
        projectPath = self.__project.getProjectPath()
        
        names = [os.path.join(projectPath, itm.text())
                 for itm in self.__getSelectedModifiedItems()]
        if not names:
            EricMessageBox.information(
                self,
                self.tr("Differences Side-By-Side"),
                self.tr("""There are no uncommitted changes"""
                        """ available/selected."""))
            return
        elif len(names) > 1:
            EricMessageBox.information(
                self,
                self.tr("Differences Side-By-Side"),
                self.tr("""Only one file with uncommitted changes"""
                        """ must be selected."""))
            return
        
        vcs = self.__project.getVcs()
        vcs and vcs.vcsSbsDiff(names[0])
    
    @pyqtSlot()
    def __revert(self):
        """
        Private slot to handle the Revert action menu entry.
        """
        projectPath = self.__project.getProjectPath()
        
        names = [os.path.join(projectPath, itm.text())
                 for itm in self.__getSelectedModifiedItems()]
        if not names:
            EricMessageBox.information(
                self,
                self.tr("Revert"),
                self.tr("""There are no uncommitted changes"""
                        """ available/selected."""))
            return
        
        vcs = self.__project.getVcs()
        vcs and vcs.vcsRevert(names)
        self.__reload()
    
    @pyqtSlot()
    def __forgetMissing(self):
        """
        Private slot to handle the Forget action menu entry.
        """
        projectPath = self.__project.getProjectPath()
        
        names = [os.path.join(projectPath, itm.text())
                 for itm in self.__getMissingItems()]
        if not names:
            EricMessageBox.information(
                self,
                self.tr("Forget Missing"),
                self.tr("""There are no missing entries"""
                        """ available/selected."""))
            return
        
        vcs = self.__project.getVcs()
        vcs and vcs.vcsForget(names)
        self.__reload()
    
    @pyqtSlot()
    def __restoreMissing(self):
        """
        Private slot to handle the Restore Missing context menu entry.
        """
        projectPath = self.__project.getProjectPath()
        
        names = [os.path.join(projectPath, itm.text())
                 for itm in self.__getMissingItems()]
        if not names:
            EricMessageBox.information(
                self,
                self.tr("Restore Missing"),
                self.tr("""There are no missing entries"""
                        """ available/selected."""))
            return
        
        vcs = self.__project.getVcs()
        vcs and vcs.vcsRevert(names)
        self.__reload()
    
    @pyqtSlot()
    def __editConflict(self):
        """
        Private slot to handle the Edit Conflict action menu entry.
        """
        projectPath = self.__project.getProjectPath()
        
        itm = self.__getSelectedConflictingItems()[0]
        filename = os.path.join(projectPath, itm.text())
        if Utilities.MimeTypes.isTextFile(filename):
            self.__vm.getEditor(filename)
    
    @pyqtSlot()
    def __conflictResolved(self):
        """
        Private slot to handle the Conflict Resolved action menu entry.
        """
        projectPath = self.__project.getProjectPath()
        
        names = [os.path.join(projectPath, itm.text())
                 for itm in self.__getSelectedConflictingItems()]
        if not names:
            EricMessageBox.information(
                self,
                self.tr("Conflict Resolved"),
                self.tr("""There are no conflicting entries"""
                        """ available/selected."""))
            return
        
        vcs = self.__project.getVcs()
        vcs and vcs.vcsResolved(names)
        self.__reload()
    
    #######################################################################
    ## Quick Commit handling methods
    #######################################################################
    
    @pyqtSlot()
    def __selectQuickCommitMessage(self):
        """
        Private slot to select a commit message from the list of
        saved messages.
        """
        vcs = self.__project.getVcs()
        if vcs:
            commitMessages = vcs.vcsCommitMessages()
            dlg = EricListSelectionDialog(
                commitMessages,
                selectionMode=QAbstractItemView.SelectionMode.SingleSelection,
                title=self.tr("Quick Commit"),
                message=self.tr("Select your commit message:"),
                doubleClickOk=True,
                parent=self
            )
            if dlg.exec() == QDialog.DialogCode.Accepted:
                selection = dlg.getSelection()
                if selection:
                    self.__quickCommitEdit.setPlainText(selection[0])
    
    @pyqtSlot()
    def __clearCommitMessages(self):
        """
        Private slot to clear the list of saved commit messages.
        """
        vcs = self.__project.getVcs()
        vcs and vcs.vcsClearCommitMessages()
    
    @pyqtSlot()
    def __quickCommit(self):
        """
        Private slot to commit all marked entries with the entered
        commit message.
        """
        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)
        
        commitMessage = self.__quickCommitEdit.toPlainText()
        vcs = self.__project.getVcs()
        if vcs:
            vcs.vcsCommit(names, commitMessage, noDialog=True)
            vcs.vcsAddCommitMessage(commitMessage)
            self.__quickCommitEdit.clear()
    
    @pyqtSlot()
    def __quickCommitEditTextChanged(self):
        """
        Private slot to react upon changes of the quick commit text.
        """
        self.__quickCommitButton.setEnabled(bool(
            self.__quickCommitEdit.toPlainText()))
    
    def eventFilter(self, obj, evt):
        """
        Public method to process some events for the Commit edit.
        
        @param obj reference to the object the event was meant for
        @type QObject
        @param evt reference to the event object
        @type QEvent
        @return flag to indicate that the event was handled
        @rtype bool
        """
        if (
            obj is self.__quickCommitEdit and
            evt.type() == QEvent.Type.KeyPress and
            evt.key() in (Qt.Key.Key_Return, Qt.Key.Key_Enter) and
            evt.modifiers() == Qt.KeyboardModifier.ControlModifier
        ):
            # Ctrl-Enter or Ctrl-Return => commit
            self.__quickCommitButton.animateClick()
            return True
        else:
            # standard event processing
            return super().eventFilter(obj, evt)

eric ide

mercurial