eric7/Plugins/VcsPlugins/vcsMercurial/HgDiffDialog.py

branch
eric7
changeset 8312
800c432b34c8
parent 8218
7c09585bd960
child 8318
962bce857696
equal deleted inserted replaced
8311:4e8b98454baa 8312:800c432b34c8
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2010 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a dialog to show the output of the hg diff command process.
8 """
9
10 from PyQt5.QtCore import pyqtSlot, QFileInfo, Qt
11 from PyQt5.QtGui import QTextCursor
12 from PyQt5.QtWidgets import QWidget, QDialogButtonBox
13
14 from E5Gui import E5MessageBox, E5FileDialog
15 from E5Gui.E5Application import e5App
16
17 from .Ui_HgDiffDialog import Ui_HgDiffDialog
18 from .HgDiffHighlighter import HgDiffHighlighter
19 from .HgDiffGenerator import HgDiffGenerator
20
21 import Utilities
22 import Preferences
23
24
25 class HgDiffDialog(QWidget, Ui_HgDiffDialog):
26 """
27 Class implementing a dialog to show the output of the hg diff command
28 process.
29 """
30 def __init__(self, vcs, parent=None):
31 """
32 Constructor
33
34 @param vcs reference to the vcs object
35 @param parent parent widget (QWidget)
36 """
37 super().__init__(parent)
38 self.setupUi(self)
39
40 self.refreshButton = self.buttonBox.addButton(
41 self.tr("Refresh"), QDialogButtonBox.ButtonRole.ActionRole)
42 self.refreshButton.setToolTip(
43 self.tr("Press to refresh the display"))
44 self.refreshButton.setEnabled(False)
45 self.buttonBox.button(
46 QDialogButtonBox.StandardButton.Save).setEnabled(False)
47 self.buttonBox.button(
48 QDialogButtonBox.StandardButton.Close).setDefault(True)
49
50 self.searchWidget.attachTextEdit(self.contents)
51
52 self.vcs = vcs
53
54 font = Preferences.getEditorOtherFonts("MonospacedFont")
55 self.contents.document().setDefaultFont(font)
56
57 self.highlighter = HgDiffHighlighter(self.contents.document())
58
59 self.__diffGenerator = HgDiffGenerator(vcs, self)
60 self.__diffGenerator.finished.connect(self.__generatorFinished)
61
62 def closeEvent(self, e):
63 """
64 Protected slot implementing a close event handler.
65
66 @param e close event (QCloseEvent)
67 """
68 self.__diffGenerator.stopProcess()
69 e.accept()
70
71 def start(self, fn, versions=None, bundle=None, qdiff=False,
72 refreshable=False):
73 """
74 Public slot to start the hg diff command.
75
76 @param fn filename to be diffed (string)
77 @param versions list of versions to be diffed (list of up to
78 2 strings or None)
79 @param bundle name of a bundle file (string)
80 @param qdiff flag indicating qdiff command shall be used (boolean)
81 @param refreshable flag indicating a refreshable diff (boolean)
82 """
83 self.refreshButton.setVisible(refreshable)
84
85 self.errorGroup.hide()
86 self.filename = fn
87
88 self.contents.clear()
89 self.filesCombo.clear()
90 self.highlighter.regenerateRules()
91
92 if qdiff:
93 self.setWindowTitle(self.tr("Patch Contents"))
94
95 self.raise_()
96 self.activateWindow()
97
98 procStarted = self.__diffGenerator.start(
99 fn, versions=versions, bundle=bundle, qdiff=qdiff)
100 if not procStarted:
101 E5MessageBox.critical(
102 self,
103 self.tr('Process Generation Error'),
104 self.tr(
105 'The process {0} could not be started. '
106 'Ensure, that it is in the search path.'
107 ).format('hg'))
108
109 def __generatorFinished(self):
110 """
111 Private slot connected to the finished signal.
112 """
113 self.refreshButton.setEnabled(True)
114
115 diff, errors, fileSeparators = self.__diffGenerator.getResult()
116
117 if diff:
118 self.contents.setPlainText("".join(diff))
119 else:
120 self.contents.setPlainText(
121 self.tr('There is no difference.'))
122
123 if errors:
124 self.errorGroup.show()
125 self.errors.setPlainText("".join(errors))
126 self.errors.ensureCursorVisible()
127
128 self.buttonBox.button(
129 QDialogButtonBox.StandardButton.Save).setEnabled(bool(diff))
130 self.buttonBox.button(
131 QDialogButtonBox.StandardButton.Close).setEnabled(True)
132 self.buttonBox.button(
133 QDialogButtonBox.StandardButton.Close).setDefault(True)
134 self.buttonBox.button(
135 QDialogButtonBox.StandardButton.Close).setFocus(
136 Qt.FocusReason.OtherFocusReason)
137
138 tc = self.contents.textCursor()
139 tc.movePosition(QTextCursor.MoveOperation.Start)
140 self.contents.setTextCursor(tc)
141 self.contents.ensureCursorVisible()
142
143 self.filesCombo.addItem(self.tr("<Start>"), 0)
144 self.filesCombo.addItem(self.tr("<End>"), -1)
145 for oldFile, newFile, pos in sorted(fileSeparators):
146 if not oldFile:
147 self.filesCombo.addItem(newFile, pos)
148 elif oldFile != newFile:
149 self.filesCombo.addItem(
150 "{0}\n{1}".format(oldFile, newFile), pos)
151 else:
152 self.filesCombo.addItem(oldFile, pos)
153
154 def on_buttonBox_clicked(self, button):
155 """
156 Private slot called by a button of the button box clicked.
157
158 @param button button that was clicked (QAbstractButton)
159 """
160 if button == self.buttonBox.button(
161 QDialogButtonBox.StandardButton.Save
162 ):
163 self.on_saveButton_clicked()
164 elif button == self.refreshButton:
165 self.on_refreshButton_clicked()
166
167 @pyqtSlot(int)
168 def on_filesCombo_activated(self, index):
169 """
170 Private slot to handle the selection of a file.
171
172 @param index activated row (integer)
173 """
174 para = self.filesCombo.itemData(index)
175
176 if para == 0:
177 tc = self.contents.textCursor()
178 tc.movePosition(QTextCursor.MoveOperation.Start)
179 self.contents.setTextCursor(tc)
180 self.contents.ensureCursorVisible()
181 elif para == -1:
182 tc = self.contents.textCursor()
183 tc.movePosition(QTextCursor.MoveOperation.End)
184 self.contents.setTextCursor(tc)
185 self.contents.ensureCursorVisible()
186 else:
187 # step 1: move cursor to end
188 tc = self.contents.textCursor()
189 tc.movePosition(QTextCursor.MoveOperation.End)
190 self.contents.setTextCursor(tc)
191 self.contents.ensureCursorVisible()
192
193 # step 2: move cursor to desired line
194 tc = self.contents.textCursor()
195 delta = tc.blockNumber() - para
196 tc.movePosition(QTextCursor.MoveOperation.PreviousBlock,
197 QTextCursor.MoveMode.MoveAnchor,
198 delta)
199 self.contents.setTextCursor(tc)
200 self.contents.ensureCursorVisible()
201
202 @pyqtSlot()
203 def on_saveButton_clicked(self):
204 """
205 Private slot to handle the Save button press.
206
207 It saves the diff shown in the dialog to a file in the local
208 filesystem.
209 """
210 if isinstance(self.filename, list):
211 if len(self.filename) > 1:
212 fname = self.vcs.splitPathList(self.filename)[0]
213 else:
214 dname, fname = self.vcs.splitPath(self.filename[0])
215 if fname != '.':
216 fname = "{0}.diff".format(self.filename[0])
217 else:
218 fname = dname
219 else:
220 fname = self.vcs.splitPath(self.filename)[0]
221
222 fname, selectedFilter = E5FileDialog.getSaveFileNameAndFilter(
223 self,
224 self.tr("Save Diff"),
225 fname,
226 self.tr("Patch Files (*.diff)"),
227 None,
228 E5FileDialog.Options(E5FileDialog.DontConfirmOverwrite))
229
230 if not fname:
231 return # user aborted
232
233 ext = QFileInfo(fname).suffix()
234 if not ext:
235 ex = selectedFilter.split("(*")[1].split(")")[0]
236 if ex:
237 fname += ex
238 if QFileInfo(fname).exists():
239 res = E5MessageBox.yesNo(
240 self,
241 self.tr("Save Diff"),
242 self.tr("<p>The patch file <b>{0}</b> already exists."
243 " Overwrite it?</p>").format(fname),
244 icon=E5MessageBox.Warning)
245 if not res:
246 return
247 fname = Utilities.toNativeSeparators(fname)
248
249 eol = e5App().getObject("Project").getEolString()
250 try:
251 with open(fname, "w", encoding="utf-8", newline="") as f:
252 f.write(eol.join(self.contents.toPlainText().splitlines()))
253 except OSError as why:
254 E5MessageBox.critical(
255 self, self.tr('Save Diff'),
256 self.tr(
257 '<p>The patch file <b>{0}</b> could not be saved.'
258 '<br>Reason: {1}</p>')
259 .format(fname, str(why)))
260
261 @pyqtSlot()
262 def on_refreshButton_clicked(self):
263 """
264 Private slot to refresh the display.
265 """
266 self.buttonBox.button(
267 QDialogButtonBox.StandardButton.Close).setEnabled(False)
268
269 self.buttonBox.button(
270 QDialogButtonBox.StandardButton.Save).setEnabled(False)
271 self.refreshButton.setEnabled(False)
272
273 self.start(self.filename, refreshable=True)

eric ide

mercurial