--- a/RefactoringRope/HistoryDialog.py Sat Sep 16 18:51:19 2017 +0200 +++ b/RefactoringRope/HistoryDialog.py Sun Sep 17 17:03:16 2017 +0200 @@ -9,127 +9,308 @@ from __future__ import unicode_literals -from PyQt5.QtCore import Qt, pyqtSlot -from PyQt5.QtWidgets import QDialogButtonBox, QListWidgetItem, QApplication +from PyQt5.QtCore import pyqtSlot, Qt, QItemSelectionModel +from PyQt5.QtGui import QBrush, QColor, QTextCursor +from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QListWidgetItem, \ + QAbstractButton from E5Gui.E5Application import e5App from E5Gui import E5MessageBox -from PreviewDialogBase import PreviewDialogBase +from Ui_HistoryDialog import Ui_HistoryDialog +import Globals import Utilities -class HistoryDialog(PreviewDialogBase): +class HistoryDialog(QDialog, Ui_HistoryDialog): """ Class implementing the History dialog. """ ChangeIDRole = Qt.UserRole - def __init__(self, refactoring, changes, isUndo, parent=None): + def __init__(self, refactoring, filename="", parent=None): """ Constructor @param refactoring reference to the main refactoring object - (Refactoring) - @param changes list of ChangeSet objects - (list of rope.base.change.ChangeSet) - @param isUndo flag indicating an undo history dialog (boolean) - @param parent reference to the parent widget (QWidget) + @type Refactoring + @param filename name of the file to show the history for + @type str + @param parent reference to the parent widget + @type QWidget """ - PreviewDialogBase.__init__(self, parent) + QDialog.__init__(self, parent) + self.setupUi(self) + self.setWindowFlags(Qt.Window) + + if Globals.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(QColor(190, 237, 190))) + self.formats['+'] = charFormat + charFormat = self.previewEdit.currentCharFormat() + charFormat.setBackground(QBrush(QColor(237, 190, 190))) + self.formats['-'] = charFormat + charFormat = self.previewEdit.currentCharFormat() + charFormat.setBackground(QBrush(QColor(190, 190, 237))) + self.formats['@'] = charFormat + charFormat = self.previewEdit.currentCharFormat() + charFormat.setBackground(QBrush(QColor(124, 124, 124))) + self.formats['?'] = charFormat + charFormat = self.previewEdit.currentCharFormat() + charFormat.setBackground(QBrush(QColor(190, 190, 190))) + self.formats['='] = charFormat self.__refactoring = refactoring - self.__isUndo = isUndo + 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)) - if self.__isUndo: - self.__actionButton = self.buttonBox.addButton( - self.tr("&Undo"), - QDialogButtonBox.AcceptRole) - self.description.setText(self.tr("Undoable Changes")) - title = self.tr("Undo History") - else: - self.__actionButton = self.buttonBox.addButton( - self.tr("&Redo"), - QDialogButtonBox.AcceptRole) - self.description.setText(self.tr("Redoable Changes")) - title = self.tr("Redo History") - self.buttonBox.addButton(QDialogButtonBox.Close) - self.setWindowTitle(title) + self.__undoButton = self.buttonBox.addButton( + self.tr("&Undo"), QDialogButtonBox.ActionRole) + self.__redoButton = self.buttonBox.addButton( + self.tr("&Redo"), QDialogButtonBox.ActionRole) + self.__refreshButton = self.buttonBox.addButton( + self.tr("Re&fresh"), QDialogButtonBox.ActionRole) + self.__clearButton = self.buttonBox.addButton( + self.tr("&Clear History"), QDialogButtonBox.ActionRole) # populate the list - self.__changes = {} - for change in changes: - self.__changes[id(change)] = change - itm = QListWidgetItem(str(change), self.changesList) - itm.setData(HistoryDialog.ChangeIDRole, id(change)) - if self.changesList.count() > 0: - self.changesList.item(0).setSelected(True) + 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.End) + self.previewEdit.setTextCursor(tc) + self.previewEdit.setCurrentCharFormat(charFormat) + self.previewEdit.insertPlainText(txt) - @pyqtSlot(QListWidgetItem, QListWidgetItem) - def on_changesList_currentItemChanged(self, current, previous): + @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 """ - Private slot called when a change is selected. + if button == QDialogButtonBox.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 new current item (QListWidgetItem) - @param previous reference to the old current item (QListWidgetItem) + @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) - change = self.__changes[changeId] - for line in change.get_description().splitlines(True): - try: - charFormat = self.formats[line[0]] - except (IndexError, KeyError): - charFormat = self.formats[' '] - self._appendText(line, charFormat) + self.__refactoring.sendJson("History", { + "Subcommand": "GetChange", + "Id": changeId, + }) + + @pyqtSlot(QListWidgetItem, QListWidgetItem) + def on_redoChangesList_currentItemChanged(self, current, previous): + """ + 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) - def accept(self): + @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 """ - Public slot to undo the selected set of changes. + self.__currentItemChanged(item) + + @pyqtSlot(QListWidgetItem, QListWidgetItem) + def on_undoChangesList_currentItemChanged(self, current, previous): + """ + 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 """ - changeId = self.changesList.currentItem()\ - .data(HistoryDialog.ChangeIDRole) - change = self.__changes[changeId] + 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. - if self.__isUndo: - res = E5MessageBox.yesNo( - None, - self.tr("Undo refactorings"), - self.tr("""Shall all refactorings up to <b>{0}</b>""" - """ be undone?""") - .format(Utilities.html_encode(str(change)))) - else: - res = E5MessageBox.yesNo( - None, - self.tr("Redo refactorings"), - self.tr("""Shall all refactorings up to <b>{0}</b>""" - """ be redone?""") - .format(Utilities.html_encode(str(change)))) + @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 = E5MessageBox.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 = E5MessageBox.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 - from ProgressHandle import ProgressHandle - handle = ProgressHandle(change.description, False, self) - handle.show() - QApplication.processEvents() - if self.__isUndo: - self.__refactoring.getProject().history.undo( - change, task_handle=handle) - else: - self.__refactoring.getProject().history.redo( - change, task_handle=handle) - handle.reset() + 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 = E5MessageBox.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.__refactoring.refreshEditors(change) + 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.Select) + elif self.redoChangesList.count() > 0: + self.redoChangesList.setCurrentItem( + self.redoChangesList.item(0), + QItemSelectionModel.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 = e5App().getObject("Project") if p.isDirty(): p.saveProject() - PreviewDialogBase.accept(self) - else: - self.reject() + self.raise_() + self.__refreshHistories()