--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/eric7/VCS/StatusWidget.py Thu Jul 07 11:23:56 2022 +0200 @@ -0,0 +1,974 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2021 - 2022 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.__addedItemsText = [] + + 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 or + name in self.__addedItemsText + ): + 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. + """ + itemList = ( + self.__statusList.selectedItems() + if len(self.__statusList.selectedItems()) else + [self.__statusList.item(row) + for row in range(self.__statusList.count())] + ) + for itm in itemList: + 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 + + self.__addedItemsText = [ + itm.text() for itm in self.__getUnversionedItems() + ] if allItems else [ + itm.text() for itm in self.__getSelectedUnversionedItems() + ] + + 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)