Plugins/VcsPlugins/vcsMercurial/HgDiffDialog.py

changeset 178
dd9f0bca5e2f
child 207
3f889378dede
equal deleted inserted replaced
177:c822ccc4d138 178:dd9f0bca5e2f
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2010 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 import os
11
12 from PyQt4.QtCore import pyqtSlot, QProcess, SIGNAL, QTimer, QFileInfo
13 from PyQt4.QtGui import QWidget, QDialogButtonBox, QBrush, QColor, QMessageBox, \
14 QTextCursor, QFileDialog, QLineEdit
15
16 from .Ui_HgDiffDialog import Ui_HgDiffDialog
17
18 import Utilities
19 import Preferences
20
21 class HgDiffDialog(QWidget, Ui_HgDiffDialog):
22 """
23 Class implementing a dialog to show the output of the hg diff command process.
24 """
25 def __init__(self, vcs, parent = None):
26 """
27 Constructor
28
29 @param vcs reference to the vcs object
30 @param parent parent widget (QWidget)
31 """
32 QWidget.__init__(self, parent)
33 self.setupUi(self)
34
35 self.buttonBox.button(QDialogButtonBox.Save).setEnabled(False)
36 self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
37
38 self.process = QProcess()
39 self.vcs = vcs
40
41 if Utilities.isWindowsPlatform():
42 self.contents.setFontFamily("Lucida Console")
43 else:
44 self.contents.setFontFamily("Monospace")
45
46 self.cNormalFormat = self.contents.currentCharFormat()
47 self.cAddedFormat = self.contents.currentCharFormat()
48 self.cAddedFormat.setBackground(QBrush(QColor(190, 237, 190)))
49 self.cRemovedFormat = self.contents.currentCharFormat()
50 self.cRemovedFormat.setBackground(QBrush(QColor(237, 190, 190)))
51 self.cLineNoFormat = self.contents.currentCharFormat()
52 self.cLineNoFormat.setBackground(QBrush(QColor(255, 220, 168)))
53
54 self.connect(self.process, SIGNAL('finished(int, QProcess::ExitStatus)'),
55 self.__procFinished)
56 self.connect(self.process, SIGNAL('readyReadStandardOutput()'),
57 self.__readStdout)
58 self.connect(self.process, SIGNAL('readyReadStandardError()'),
59 self.__readStderr)
60
61 def closeEvent(self, e):
62 """
63 Private slot implementing a close event handler.
64
65 @param e close event (QCloseEvent)
66 """
67 if self.process is not None and \
68 self.process.state() != QProcess.NotRunning:
69 self.process.terminate()
70 QTimer.singleShot(2000, self.process.kill)
71 self.process.waitForFinished(3000)
72
73 e.accept()
74
75 def __getVersionArg(self, version):
76 """
77 Private method to get a hg revision argument for the given revision.
78
79 @param version revision (integer or string)
80 @return version argument (string)
81 """
82 if version == "WORKING":
83 return None
84 else:
85 return str(version)
86
87 def start(self, fn, versions = None):
88 """
89 Public slot to start the hg diff command.
90
91 @param fn filename to be diffed (string)
92 @param versions list of versions to be diffed (list of up to 2 strings or None)
93 """
94 self.errorGroup.hide()
95 self.inputGroup.show()
96 self.intercept = False
97 self.filename = fn
98
99 self.process.kill()
100
101 self.contents.clear()
102 self.paras = 0
103
104 args = []
105 args.append('diff')
106 self.vcs.addArguments(args, self.vcs.options['global'])
107 self.vcs.addArguments(args, self.vcs.options['diff'])
108
109 if versions is not None:
110 self.raise_()
111 self.activateWindow()
112
113 rev1 = self.__getVersionArg(versions[0])
114 rev2 = None
115 if len(versions) == 2:
116 rev2 = self.__getVersionArg(versions[1])
117
118 if rev1 is not None or rev2 is not None:
119 args.append('-r')
120 if rev1 is not None and rev2 is not None:
121 args.append('{0}:{1}'.format(rev1, rev2))
122 elif rev2 is None:
123 args.append(rev1)
124 elif rev1 is None:
125 args.append(':{0}'.format(rev2))
126
127 if isinstance(fn, list):
128 dname, fnames = self.vcs.splitPathList(fn)
129 self.vcs.addArguments(args, fn)
130 else:
131 dname, fname = self.vcs.splitPath(fn)
132 args.append(fn)
133
134 # find the root of the repo
135 repodir = dname
136 while not os.path.isdir(os.path.join(repodir, self.vcs.adminDir)):
137 repodir = os.path.dirname(repodir)
138 if repodir == os.sep:
139 return
140
141 self.process.setWorkingDirectory(repodir)
142
143 self.process.start('hg', args)
144 procStarted = self.process.waitForStarted()
145 if not procStarted:
146 self.inputGroup.setEnabled(False)
147 QMessageBox.critical(None,
148 self.trUtf8('Process Generation Error'),
149 self.trUtf8(
150 'The process {0} could not be started. '
151 'Ensure, that it is in the search path.'
152 ).format('hg'))
153
154 def __procFinished(self, exitCode, exitStatus):
155 """
156 Private slot connected to the finished signal.
157
158 @param exitCode exit code of the process (integer)
159 @param exitStatus exit status of the process (QProcess.ExitStatus)
160 """
161 self.inputGroup.setEnabled(False)
162 self.inputGroup.hide()
163
164 if self.paras == 0:
165 self.contents.insertPlainText(\
166 self.trUtf8('There is no difference.'))
167 return
168
169 self.buttonBox.button(QDialogButtonBox.Save).setEnabled(True)
170 tc = self.contents.textCursor()
171 tc.movePosition(QTextCursor.Start)
172 self.contents.setTextCursor(tc)
173 self.contents.ensureCursorVisible()
174
175 def __appendText(self, txt, format):
176 """
177 Private method to append text to the end of the contents pane.
178
179 @param txt text to insert (string)
180 @param format text format to be used (QTextCharFormat)
181 """
182 tc = self.contents.textCursor()
183 tc.movePosition(QTextCursor.End)
184 self.contents.setTextCursor(tc)
185 self.contents.setCurrentCharFormat(format)
186 self.contents.insertPlainText(txt)
187
188 def __readStdout(self):
189 """
190 Private slot to handle the readyReadStandardOutput signal.
191
192 It reads the output of the process, formats it and inserts it into
193 the contents pane.
194 """
195 self.process.setReadChannel(QProcess.StandardOutput)
196
197 while self.process.canReadLine():
198 line = str(self.process.readLine(),
199 Preferences.getSystem("IOEncoding"),
200 'replace')
201 if line.startswith('+'):
202 format = self.cAddedFormat
203 elif line.startswith('-'):
204 format = self.cRemovedFormat
205 elif line.startswith('@@'):
206 format = self.cLineNoFormat
207 else:
208 format = self.cNormalFormat
209 self.__appendText(line, format)
210 self.paras += 1
211
212 def __readStderr(self):
213 """
214 Private slot to handle the readyReadStandardError signal.
215
216 It reads the error output of the process and inserts it into the
217 error pane.
218 """
219 if self.process is not None:
220 self.errorGroup.show()
221 s = str(self.process.readAllStandardError(),
222 Preferences.getSystem("IOEncoding"),
223 'replace')
224 self.errors.insertPlainText(s)
225 self.errors.ensureCursorVisible()
226
227 def on_buttonBox_clicked(self, button):
228 """
229 Private slot called by a button of the button box clicked.
230
231 @param button button that was clicked (QAbstractButton)
232 """
233 if button == self.buttonBox.button(QDialogButtonBox.Save):
234 self.on_saveButton_clicked()
235
236 @pyqtSlot()
237 def on_saveButton_clicked(self):
238 """
239 Private slot to handle the Save button press.
240
241 It saves the diff shown in the dialog to a file in the local
242 filesystem.
243 """
244 if isinstance(self.filename, list):
245 if len(self.filename) > 1:
246 fname = self.vcs.splitPathList(self.filename)[0]
247 else:
248 dname, fname = self.vcs.splitPath(self.filename[0])
249 if fname != '.':
250 fname = "%s.diff" % self.filename[0]
251 else:
252 fname = dname
253 else:
254 fname = self.vcs.splitPath(self.filename)[0]
255
256 fname, selectedFilter = QFileDialog.getSaveFileNameAndFilter(\
257 self,
258 self.trUtf8("Save Diff"),
259 fname,
260 self.trUtf8("Patch Files (*.diff)"),
261 None,
262 QFileDialog.Options(QFileDialog.DontConfirmOverwrite))
263
264 if not fname:
265 return # user aborted
266
267 ext = QFileInfo(fname).suffix()
268 if not ext:
269 ex = selectedFilter.split("(*")[1].split(")")[0]
270 if ex:
271 fname += ex
272 if QFileInfo(fname).exists():
273 res = QMessageBox.warning(self,
274 self.trUtf8("Save Diff"),
275 self.trUtf8("<p>The patch file <b>{0}</b> already exists.</p>")
276 .format(fname),
277 QMessageBox.StandardButtons(\
278 QMessageBox.Abort | \
279 QMessageBox.Save),
280 QMessageBox.Abort)
281 if res != QMessageBox.Save:
282 return
283 fname = Utilities.toNativeSeparators(fname)
284
285 try:
286 f = open(fname, "w", encoding = "utf-8")
287 f.write(self.contents.toPlainText())
288 f.close()
289 except IOError as why:
290 QMessageBox.critical(self, self.trUtf8('Save Diff'),
291 self.trUtf8('<p>The patch file <b>{0}</b> could not be saved.'
292 '<br>Reason: {1}</p>')
293 .format(fname, str(why)))
294
295 def on_passwordCheckBox_toggled(self, isOn):
296 """
297 Private slot to handle the password checkbox toggled.
298
299 @param isOn flag indicating the status of the check box (boolean)
300 """
301 if isOn:
302 self.input.setEchoMode(QLineEdit.Password)
303 else:
304 self.input.setEchoMode(QLineEdit.Normal)
305
306 @pyqtSlot()
307 def on_sendButton_clicked(self):
308 """
309 Private slot to send the input to the subversion process.
310 """
311 input = self.input.text()
312 input += os.linesep
313
314 if self.passwordCheckBox.isChecked():
315 self.errors.insertPlainText(os.linesep)
316 self.errors.ensureCursorVisible()
317 else:
318 self.errors.insertPlainText(input)
319 self.errors.ensureCursorVisible()
320
321 self.process.write(input)
322
323 self.passwordCheckBox.setChecked(False)
324 self.input.clear()
325
326 def on_input_returnPressed(self):
327 """
328 Private slot to handle the press of the return key in the input field.
329 """
330 self.intercept = True
331 self.on_sendButton_clicked()
332
333 def keyPressEvent(self, evt):
334 """
335 Protected slot to handle a key press event.
336
337 @param evt the key press event (QKeyEvent)
338 """
339 if self.intercept:
340 self.intercept = False
341 evt.accept()
342 return
343 QWidget.keyPressEvent(self, evt)

eric ide

mercurial