--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/eric7/Plugins/VcsPlugins/vcsMercurial/HgDiffDialog.py Sat May 15 18:45:04 2021 +0200 @@ -0,0 +1,273 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2010 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing a dialog to show the output of the hg diff command process. +""" + +from PyQt5.QtCore import pyqtSlot, QFileInfo, Qt +from PyQt5.QtGui import QTextCursor +from PyQt5.QtWidgets import QWidget, QDialogButtonBox + +from E5Gui import E5MessageBox, E5FileDialog +from E5Gui.E5Application import e5App + +from .Ui_HgDiffDialog import Ui_HgDiffDialog +from .HgDiffHighlighter import HgDiffHighlighter +from .HgDiffGenerator import HgDiffGenerator + +import Utilities +import Preferences + + +class HgDiffDialog(QWidget, Ui_HgDiffDialog): + """ + Class implementing a dialog to show the output of the hg diff command + process. + """ + def __init__(self, vcs, parent=None): + """ + Constructor + + @param vcs reference to the vcs object + @param parent parent widget (QWidget) + """ + super().__init__(parent) + self.setupUi(self) + + self.refreshButton = self.buttonBox.addButton( + self.tr("Refresh"), QDialogButtonBox.ButtonRole.ActionRole) + self.refreshButton.setToolTip( + self.tr("Press to refresh the display")) + self.refreshButton.setEnabled(False) + self.buttonBox.button( + QDialogButtonBox.StandardButton.Save).setEnabled(False) + self.buttonBox.button( + QDialogButtonBox.StandardButton.Close).setDefault(True) + + self.searchWidget.attachTextEdit(self.contents) + + self.vcs = vcs + + font = Preferences.getEditorOtherFonts("MonospacedFont") + self.contents.document().setDefaultFont(font) + + self.highlighter = HgDiffHighlighter(self.contents.document()) + + self.__diffGenerator = HgDiffGenerator(vcs, self) + self.__diffGenerator.finished.connect(self.__generatorFinished) + + def closeEvent(self, e): + """ + Protected slot implementing a close event handler. + + @param e close event (QCloseEvent) + """ + self.__diffGenerator.stopProcess() + e.accept() + + def start(self, fn, versions=None, bundle=None, qdiff=False, + refreshable=False): + """ + Public slot to start the hg diff command. + + @param fn filename to be diffed (string) + @param versions list of versions to be diffed (list of up to + 2 strings or None) + @param bundle name of a bundle file (string) + @param qdiff flag indicating qdiff command shall be used (boolean) + @param refreshable flag indicating a refreshable diff (boolean) + """ + self.refreshButton.setVisible(refreshable) + + self.errorGroup.hide() + self.filename = fn + + self.contents.clear() + self.filesCombo.clear() + self.highlighter.regenerateRules() + + if qdiff: + self.setWindowTitle(self.tr("Patch Contents")) + + self.raise_() + self.activateWindow() + + procStarted = self.__diffGenerator.start( + fn, versions=versions, bundle=bundle, qdiff=qdiff) + if not procStarted: + E5MessageBox.critical( + self, + self.tr('Process Generation Error'), + self.tr( + 'The process {0} could not be started. ' + 'Ensure, that it is in the search path.' + ).format('hg')) + + def __generatorFinished(self): + """ + Private slot connected to the finished signal. + """ + self.refreshButton.setEnabled(True) + + diff, errors, fileSeparators = self.__diffGenerator.getResult() + + if diff: + self.contents.setPlainText("".join(diff)) + else: + self.contents.setPlainText( + self.tr('There is no difference.')) + + if errors: + self.errorGroup.show() + self.errors.setPlainText("".join(errors)) + self.errors.ensureCursorVisible() + + self.buttonBox.button( + QDialogButtonBox.StandardButton.Save).setEnabled(bool(diff)) + self.buttonBox.button( + QDialogButtonBox.StandardButton.Close).setEnabled(True) + self.buttonBox.button( + QDialogButtonBox.StandardButton.Close).setDefault(True) + self.buttonBox.button( + QDialogButtonBox.StandardButton.Close).setFocus( + Qt.FocusReason.OtherFocusReason) + + tc = self.contents.textCursor() + tc.movePosition(QTextCursor.MoveOperation.Start) + self.contents.setTextCursor(tc) + self.contents.ensureCursorVisible() + + self.filesCombo.addItem(self.tr("<Start>"), 0) + self.filesCombo.addItem(self.tr("<End>"), -1) + for oldFile, newFile, pos in sorted(fileSeparators): + if not oldFile: + self.filesCombo.addItem(newFile, pos) + elif oldFile != newFile: + self.filesCombo.addItem( + "{0}\n{1}".format(oldFile, newFile), pos) + else: + self.filesCombo.addItem(oldFile, pos) + + def on_buttonBox_clicked(self, button): + """ + Private slot called by a button of the button box clicked. + + @param button button that was clicked (QAbstractButton) + """ + if button == self.buttonBox.button( + QDialogButtonBox.StandardButton.Save + ): + self.on_saveButton_clicked() + elif button == self.refreshButton: + self.on_refreshButton_clicked() + + @pyqtSlot(int) + def on_filesCombo_activated(self, index): + """ + Private slot to handle the selection of a file. + + @param index activated row (integer) + """ + para = self.filesCombo.itemData(index) + + if para == 0: + tc = self.contents.textCursor() + tc.movePosition(QTextCursor.MoveOperation.Start) + self.contents.setTextCursor(tc) + self.contents.ensureCursorVisible() + elif para == -1: + tc = self.contents.textCursor() + tc.movePosition(QTextCursor.MoveOperation.End) + self.contents.setTextCursor(tc) + self.contents.ensureCursorVisible() + else: + # step 1: move cursor to end + tc = self.contents.textCursor() + tc.movePosition(QTextCursor.MoveOperation.End) + self.contents.setTextCursor(tc) + self.contents.ensureCursorVisible() + + # step 2: move cursor to desired line + tc = self.contents.textCursor() + delta = tc.blockNumber() - para + tc.movePosition(QTextCursor.MoveOperation.PreviousBlock, + QTextCursor.MoveMode.MoveAnchor, + delta) + self.contents.setTextCursor(tc) + self.contents.ensureCursorVisible() + + @pyqtSlot() + def on_saveButton_clicked(self): + """ + Private slot to handle the Save button press. + + It saves the diff shown in the dialog to a file in the local + filesystem. + """ + if isinstance(self.filename, list): + if len(self.filename) > 1: + fname = self.vcs.splitPathList(self.filename)[0] + else: + dname, fname = self.vcs.splitPath(self.filename[0]) + if fname != '.': + fname = "{0}.diff".format(self.filename[0]) + else: + fname = dname + else: + fname = self.vcs.splitPath(self.filename)[0] + + fname, selectedFilter = E5FileDialog.getSaveFileNameAndFilter( + self, + self.tr("Save Diff"), + fname, + self.tr("Patch Files (*.diff)"), + None, + E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite)) + + if not fname: + return # user aborted + + ext = QFileInfo(fname).suffix() + if not ext: + ex = selectedFilter.split("(*")[1].split(")")[0] + if ex: + fname += ex + if QFileInfo(fname).exists(): + res = E5MessageBox.yesNo( + self, + self.tr("Save Diff"), + self.tr("<p>The patch file <b>{0}</b> already exists." + " Overwrite it?</p>").format(fname), + icon=E5MessageBox.Warning) + if not res: + return + fname = Utilities.toNativeSeparators(fname) + + eol = e5App().getObject("Project").getEolString() + try: + with open(fname, "w", encoding="utf-8", newline="") as f: + f.write(eol.join(self.contents.toPlainText().splitlines())) + except OSError as why: + E5MessageBox.critical( + self, self.tr('Save Diff'), + self.tr( + '<p>The patch file <b>{0}</b> could not be saved.' + '<br>Reason: {1}</p>') + .format(fname, str(why))) + + @pyqtSlot() + def on_refreshButton_clicked(self): + """ + Private slot to refresh the display. + """ + self.buttonBox.button( + QDialogButtonBox.StandardButton.Close).setEnabled(False) + + self.buttonBox.button( + QDialogButtonBox.StandardButton.Save).setEnabled(False) + self.refreshButton.setEnabled(False) + + self.start(self.filename, refreshable=True)