RefactoringRope/HistoryDialog.py

Tue, 10 Dec 2024 15:49:01 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Tue, 10 Dec 2024 15:49:01 +0100
branch
eric7
changeset 426
7592a1c052e8
parent 420
fa31c3a0df1d
permissions
-rw-r--r--

Updated copyright for 2025.

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

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

"""
Module implementing the History dialog.
"""

from PyQt6.QtCore import QItemSelectionModel, Qt, pyqtSlot
from PyQt6.QtGui import QBrush, QTextCursor
from PyQt6.QtWidgets import QAbstractButton, QDialog, QDialogButtonBox, QListWidgetItem

from eric7 import Preferences, Utilities
from eric7.EricWidgets import EricMessageBox
from eric7.EricWidgets.EricApplication import ericApp

try:
    from eric7.SystemUtilities.OSUtilities import isWindowsPlatform
except ImportError:
    # backward compatibility for eric < 23.1
    from eric7.Globals import isWindowsPlatform

from .Ui_HistoryDialog import Ui_HistoryDialog


class HistoryDialog(QDialog, Ui_HistoryDialog):
    """
    Class implementing the History dialog.
    """

    ChangeIDRole = Qt.ItemDataRole.UserRole

    def __init__(self, refactoring, filename="", parent=None):
        """
        Constructor

        @param refactoring reference to the main refactoring object
        @type RefactoringServer
        @param filename name of the file to show the history for
        @type str
        @param parent reference to the parent widget
        @type QWidget
        """
        QDialog.__init__(self, parent)
        self.setupUi(self)
        self.setWindowFlags(Qt.WindowType.Window)

        if isWindowsPlatform():
            self.previewEdit.setFontFamily("Lucida Console")
        else:
            self.previewEdit.setFontFamily("Monospace")

        self.formats = {}
        self.formats[" "] = self.previewEdit.currentCharFormat()

        charFormat = self.previewEdit.currentCharFormat()
        charFormat.setBackground(QBrush(Preferences.getDiffColour("AddedColor")))
        self.formats["+"] = charFormat

        charFormat = self.previewEdit.currentCharFormat()
        charFormat.setBackground(QBrush(Preferences.getDiffColour("RemovedColor")))
        self.formats["-"] = charFormat

        charFormat = self.previewEdit.currentCharFormat()
        charFormat.setBackground(QBrush(Preferences.getDiffColour("ReplacedColor")))
        self.formats["@"] = charFormat

        charFormat = self.previewEdit.currentCharFormat()
        charFormat.setBackground(QBrush(Preferences.getDiffColour("ContextColor")))
        self.formats["?"] = charFormat

        charFormat = self.previewEdit.currentCharFormat()
        charFormat.setBackground(QBrush(Preferences.getDiffColour("HeaderColor")))
        self.formats["="] = charFormat

        self.__refactoring = refactoring
        self.__filename = filename

        if not filename:
            self.header.setText(self.tr("<b>Project History</b>"))
        else:
            self.header.setText(self.tr("<b>File History: {0}</b>").format(filename))

        self.__undoButton = self.buttonBox.addButton(
            self.tr("&Undo"), QDialogButtonBox.ButtonRole.ActionRole
        )
        self.__redoButton = self.buttonBox.addButton(
            self.tr("&Redo"), QDialogButtonBox.ButtonRole.ActionRole
        )
        self.__refreshButton = self.buttonBox.addButton(
            self.tr("Re&fresh"), QDialogButtonBox.ButtonRole.ActionRole
        )
        self.__clearButton = self.buttonBox.addButton(
            self.tr("&Clear History"), QDialogButtonBox.ButtonRole.ActionRole
        )

        # populate the list
        self.__refreshHistories()

    def __appendText(self, txt, charFormat):
        """
        Private method to append text to the end of the preview pane.

        @param txt text to insert
        @type str
        @param charFormat text format to be used
        @type QTextCharFormat
        """
        tc = self.previewEdit.textCursor()
        tc.movePosition(QTextCursor.MoveOperation.End)
        self.previewEdit.setTextCursor(tc)
        self.previewEdit.setCurrentCharFormat(charFormat)
        self.previewEdit.insertPlainText(txt)

    @pyqtSlot(QAbstractButton)
    def on_buttonBox_clicked(self, button):
        """
        Private slot handling the selection of a dialog button.

        @param button reference to the button clicked
        @type QAbstractButton
        """
        if button == QDialogButtonBox.StandardButton.Close:
            self.close()
        elif button == self.__undoButton:
            self.__undoChanges()
        elif button == self.__redoButton:
            self.__redoChanges()
        elif button == self.__refreshButton:
            self.__refreshHistories()
        elif button == self.__clearButton:
            self.__clearHistory()

    def __currentItemChanged(self, current):
        """
        Private method to request change data of an item.

        @param current reference to the item to get change data for
        @type QListWidgetItem
        """
        if current is None:
            return

        self.previewEdit.clear()
        changeId = current.data(HistoryDialog.ChangeIDRole)
        self.__refactoring.sendJson(
            "History",
            {
                "Subcommand": "GetChange",
                "Id": changeId,
            },
        )

    @pyqtSlot(QListWidgetItem, QListWidgetItem)
    def on_redoChangesList_currentItemChanged(self, current, previous):  # noqa: U100
        """
        Private slot handling a change of the current redo change.

        @param current reference to the new current redo item
        @type QListWidgetItem
        @param previous reference to the previous current redo item
        @type QListWidgetItem
        """
        self.__redoButton.setEnabled(current is not None)
        self.__currentItemChanged(current)

    @pyqtSlot(QListWidgetItem)
    def on_redoChangesList_itemClicked(self, item):
        """
        Private slot handling a click on a redo entry.

        @param item reference to the clicked item
        @type QListWidgetItem
        """
        self.__currentItemChanged(item)

    @pyqtSlot(QListWidgetItem, QListWidgetItem)
    def on_undoChangesList_currentItemChanged(self, current, previous):  # noqa: U100
        """
        Private slot handling a change of the current undo change.

        @param current reference to the new current undo item
        @type QListWidgetItem
        @param previous reference to the previous current undo item
        @type QListWidgetItem
        """
        self.__undoButton.setEnabled(current is not None)
        self.__currentItemChanged(current)

    @pyqtSlot(QListWidgetItem)
    def on_undoChangesList_itemClicked(self, item):
        """
        Private slot handling a click on an undo entry.

        @param item reference to the clicked item
        @type QListWidgetItem
        """
        self.__currentItemChanged(item)

    def __undoChanges(self):
        """
        Private method to undo the selected set of changes.
        """
        currentUndoItem = self.undoChangesList.currentItem()
        change = currentUndoItem.text()
        changeId = currentUndoItem.data(HistoryDialog.ChangeIDRole)
        res = EricMessageBox.yesNo(
            None,
            self.tr("Undo Refactorings"),
            self.tr("""Shall all refactorings up to <b>{0}</b> be undone?""").format(
                Utilities.html_encode(change)
            ),
        )
        if res:
            if not self.__refactoring.confirmAllBuffersSaved():
                return

            self.__refactoring.sendJson(
                "History",
                {
                    "Subcommand": "Undo",
                    "Id": changeId,
                },
            )

    def __redoChanges(self):
        """
        Private method to redo the selected set of changes.
        """
        currentRedoItem = self.redoChangesList.currentItem()
        change = currentRedoItem.text()
        changeId = currentRedoItem.data(HistoryDialog.ChangeIDRole)
        res = EricMessageBox.yesNo(
            None,
            self.tr("Redo Refactorings"),
            self.tr("""Shall all refactorings up to <b>{0}</b> be redone?""").format(
                Utilities.html_encode(change)
            ),
        )
        if res:
            if not self.__refactoring.confirmAllBuffersSaved():
                return

            self.__refactoring.sendJson(
                "History",
                {
                    "Subcommand": "Redo",
                    "Id": changeId,
                },
            )

    def __refreshHistories(self):
        """
        Private method to refresh the undo and redo history lists.
        """
        self.__undoButton.setEnabled(False)
        self.__redoButton.setEnabled(False)
        self.__refreshButton.setEnabled(False)
        self.__clearButton.setEnabled(False)

        self.undoChangesList.clear()
        self.redoChangesList.clear()
        self.previewEdit.clear()

        self.__refactoring.sendJson(
            "History", {"Subcommand": "Get", "Filename": self.__filename}
        )

    def __clearHistory(self):
        """
        Private method to clear the refactoring history.
        """
        res = EricMessageBox.yesNo(
            None,
            self.tr("Clear History"),
            self.tr("Do you really want to clear the refactoring history?"),
        )
        if res:
            self.sendJson(
                "History",
                {
                    "Subcommand": "Clear",
                },
            )

            self.historyCleared()

    def historyCleared(self):
        """
        Public method to indicate, that the refactoring history was cleared
        through the menu.
        """
        self.__refreshHistories()

    def processHistoryCommand(self, data):
        """
        Public method to process the data sent by the refactoring client.

        @param data dictionary containing the history data
        @type dict
        """
        subcommand = data["Subcommand"]
        if subcommand == "Histories":
            for change, changeId in data["Undo"]:
                itm = QListWidgetItem(change, self.undoChangesList)
                itm.setData(HistoryDialog.ChangeIDRole, changeId)
            for change, changeId in data["Redo"]:
                itm = QListWidgetItem(change, self.redoChangesList)
                itm.setData(HistoryDialog.ChangeIDRole, changeId)
            if self.undoChangesList.count() > 0:
                self.undoChangesList.setCurrentItem(
                    self.undoChangesList.item(0),
                    QItemSelectionModel.SelectionFlag.Select,
                )
            elif self.redoChangesList.count() > 0:
                self.redoChangesList.setCurrentItem(
                    self.redoChangesList.item(0),
                    QItemSelectionModel.SelectionFlag.Select,
                )

            self.__refreshButton.setEnabled(True)
            if self.undoChangesList.count() > 0 or self.redoChangesList.count() > 0:
                self.__clearButton.setEnabled(True)

        elif subcommand == "ChangeDescription":
            for line in data["Description"].splitlines(True):
                try:
                    charFormat = self.formats[line[0]]
                except (IndexError, KeyError):
                    charFormat = self.formats[" "]
                self.__appendText(line, charFormat)

        elif subcommand in ["Undo", "Redo"]:
            self.__refactoring.refreshEditors(data["ChangedFiles"])
            p = ericApp().getObject("Project")
            if p.isDirty():
                p.saveProject()

            self.raise_()
            self.__refreshHistories()

    def closeEvent(self, evt):
        """
        Protected method handling close events.

        @param evt reference to the close event object
        @type QCloseEvent
        """
        self.__refactoring.sendJson(
            "History",
            {
                "Subcommand": "ClearChanges",
            },
        )

        evt.accept()

eric ide

mercurial