Plugins/VcsPlugins/vcsMercurial/HgLogDialog.py

changeset 178
dd9f0bca5e2f
child 181
4af57f97c1bc
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 log command process.
8 """
9
10 import os
11
12 from PyQt4.QtCore import pyqtSlot, QProcess, SIGNAL, QTimer, QUrl, QByteArray
13 from PyQt4.QtGui import QWidget, QDialogButtonBox, QApplication, QMessageBox, \
14 QLineEdit, QTextCursor
15
16 from .Ui_HgLogDialog import Ui_HgLogDialog
17 from .HgDiffDialog import HgDiffDialog
18
19 import Utilities
20 import Preferences
21
22 class HgLogDialog(QWidget, Ui_HgLogDialog):
23 """
24 Class implementing a dialog to show the output of the hg log command process.
25
26 The dialog is nonmodal. Clicking a link in the upper text pane shows
27 a diff of the revisions.
28 """
29 def __init__(self, vcs, mode = "log", parent = None):
30 """
31 Constructor
32
33 @param vcs reference to the vcs object
34 @param mode mode of the dialog (string, one of log, incoming, outgoing)
35 @param parent parent widget (QWidget)
36 """
37 QWidget.__init__(self, parent)
38 self.setupUi(self)
39
40 self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
41
42 self.process = QProcess()
43 self.vcs = vcs
44 if mode in ("log", "incoming", "outgoing"):
45 self.mode = mode
46 else:
47 self.mode = "log"
48
49 self.contents.setHtml(\
50 self.trUtf8('<b>Processing your request, please wait...</b>'))
51
52 self.connect(self.process, SIGNAL('finished(int, QProcess::ExitStatus)'),
53 self.__procFinished)
54 self.connect(self.process, SIGNAL('readyReadStandardOutput()'),
55 self.__readStdout)
56 self.connect(self.process, SIGNAL('readyReadStandardError()'),
57 self.__readStderr)
58
59 self.connect(self.contents, SIGNAL('anchorClicked(const QUrl&)'),
60 self.__sourceChanged)
61
62 self.revisions = [] # stack of remembered revisions
63 self.revString = self.trUtf8('Revision')
64
65 self.buf = [] # buffer for stdout
66 self.diff = None
67
68 def closeEvent(self, e):
69 """
70 Private slot implementing a close event handler.
71
72 @param e close event (QCloseEvent)
73 """
74 if self.process is not None and \
75 self.process.state() != QProcess.NotRunning:
76 self.process.terminate()
77 QTimer.singleShot(2000, self.process.kill)
78 self.process.waitForFinished(3000)
79
80 e.accept()
81
82 def start(self, fn, noEntries = 0):
83 """
84 Public slot to start the hg log command.
85
86 @param fn filename to show the log for (string)
87 @param noEntries number of entries to show (integer)
88 """
89 self.errorGroup.hide()
90 QApplication.processEvents()
91
92 self.intercept = False
93 self.filename = fn
94 self.dname, self.fname = self.vcs.splitPath(fn)
95
96 # find the root of the repo
97 repodir = self.dname
98 while not os.path.isdir(os.path.join(repodir, self.vcs.adminDir)):
99 repodir = os.path.dirname(repodir)
100 if repodir == os.sep:
101 return
102
103 self.process.kill()
104
105 self.activateWindow()
106 self.raise_()
107
108 args = []
109 args.append(self.mode)
110 self.vcs.addArguments(args, self.vcs.options['global'])
111 self.vcs.addArguments(args, self.vcs.options['log'])
112 if noEntries:
113 args.append('--limit')
114 args.append(str(noEntries))
115 args.append('--template')
116 args.append("change|{rev}:{node|short}\n"
117 "branches|{branches}\n"
118 "tags|{tags}\n"
119 "parents|{parents}\n"
120 "user|{author}\n"
121 "date|{date|isodate}\n"
122 "description|{desc}\n"
123 "file_adds|{file_adds}\n"
124 "files_mods|{file_mods}\n"
125 "file_dels|{file_dels}\n"
126 "@@@\n")
127 if self.fname != "." or self.dname != repodir:
128 args.append(self.filename)
129
130 self.process.setWorkingDirectory(repodir)
131
132 self.process.start('hg', args)
133 procStarted = self.process.waitForStarted()
134 if not procStarted:
135 self.inputGroup.setEnabled(False)
136 QMessageBox.critical(None,
137 self.trUtf8('Process Generation Error'),
138 self.trUtf8(
139 'The process {0} could not be started. '
140 'Ensure, that it is in the search path.'
141 ).format('hg'))
142
143 def __procFinished(self, exitCode, exitStatus):
144 """
145 Private slot connected to the finished signal.
146
147 @param exitCode exit code of the process (integer)
148 @param exitStatus exit status of the process (QProcess.ExitStatus)
149 """
150 self.inputGroup.setEnabled(False)
151 self.inputGroup.hide()
152
153 self.contents.clear()
154
155 if not self.buf:
156 self.errors.append(self.trUtf8("No log available for '{0}'")\
157 .format(self.filename))
158 self.errorGroup.show()
159 return
160
161 hasInitialText = 0 # three states flag (-1, 0, 1)
162 lvers = 1
163 for s in self.buf:
164 if s == "@@@\n":
165 self.contents.insertHtml('</p>{0}<br/>\n'.format(80 * "="))
166 else:
167 try:
168 key, value = s.split("|", 1)
169 except ValueError:
170 key = ""
171 value = s
172 if key == "change":
173 if hasInitialText == 1:
174 self.contents.insertHtml('{0}<br/>\n'.format(80 * "="))
175 hasInitialText = -1
176 rev, hexRev = value.split(":")
177 dstr = '<p><b>{0} {1}</b>'.format(self.revString, value)
178 try:
179 lv = self.revisions[lvers]
180 lvers += 1
181 url = QUrl()
182 url.setScheme("file")
183 url.setPath(self.filename)
184 query = QByteArray()
185 query.append(lv.split(":")[0]).append('_').append(rev)
186 url.setEncodedQuery(query)
187 dstr += ' [<a href="{0}" name="{1}" id="{1}">{2}</a>]'.format(
188 url.toString(),
189 str(query, encoding="ascii"),
190 self.trUtf8('diff to {0}').format(lv),
191 )
192 except IndexError:
193 pass
194 dstr += '<br />\n'
195 self.contents.insertHtml(dstr)
196 elif key == "branches":
197 if value.strip():
198 self.contents.insertHtml(self.trUtf8("Branches: {0}<br />\n")\
199 .format(value.strip()))
200 elif key == "tags":
201 if value.strip():
202 self.contents.insertHtml(self.trUtf8("Tags: {0}<br />\n")\
203 .format(value.strip()))
204 elif key == "parents":
205 if value.strip():
206 self.contents.insertHtml(self.trUtf8("Parents: {0}<br />\n")\
207 .format(value.strip()))
208 elif key == "user":
209 dstr = self.contents.insertHtml(
210 self.trUtf8('<i>Author: {0}</i><br />\n').format(value.strip()))
211 elif key == "date":
212 date, time = value.strip().split()[:2]
213 dstr = self.contents.insertHtml(
214 self.trUtf8('<i>Date: {0}, {1}</i><br />\n')\
215 .format(date, time))
216 elif key == "description":
217 self.contents.insertHtml(Utilities.html_encode(value.strip()))
218 self.contents.insertHtml('<br />\n')
219 elif key == "file_adds":
220 if value.strip():
221 self.contents.insertHtml('<br />\n')
222 for f in value.strip().split():
223 self.contents.insertHtml(
224 self.trUtf8('Added {0}<br />\n')\
225 .format(Utilities.html_encode(f)))
226 elif key == "files_mods":
227 if value.strip():
228 self.contents.insertHtml('<br />\n')
229 for f in value.strip().split():
230 self.contents.insertHtml(
231 self.trUtf8('Modified {0}<br />\n')\
232 .format(Utilities.html_encode(f)))
233 elif key == "file_dels":
234 if value.strip():
235 self.contents.insertHtml('<br />\n')
236 for f in value.strip().split():
237 self.contents.insertHtml(
238 self.trUtf8('Deleted {0}<br />\n')\
239 .format(Utilities.html_encode(f)))
240 else:
241 if value.strip():
242 self.contents.insertHtml(Utilities.html_encode(value.strip()))
243 self.contents.insertHtml('<br />\n')
244 if hasInitialText == 0:
245 hasInitialText = 1
246
247 tc = self.contents.textCursor()
248 tc.movePosition(QTextCursor.Start)
249 self.contents.setTextCursor(tc)
250 self.contents.ensureCursorVisible()
251
252 def __readStdout(self):
253 """
254 Private slot to handle the readyReadStandardOutput signal.
255
256 It reads the output of the process and inserts it into a buffer.
257 """
258 self.process.setReadChannel(QProcess.StandardOutput)
259
260 while self.process.canReadLine():
261 line = str(self.process.readLine(),
262 Preferences.getSystem("IOEncoding"),
263 'replace')
264 self.buf.append(line)
265
266 if line.startswith("change|"):
267 ver = line[7:]
268 # save revision number for later use
269 self.revisions.append(ver)
270
271 def __readStderr(self):
272 """
273 Private slot to handle the readyReadStandardError signal.
274
275 It reads the error output of the process and inserts it into the
276 error pane.
277 """
278 if self.process is not None:
279 self.errorGroup.show()
280 s = str(self.process.readAllStandardError(),
281 Preferences.getSystem("IOEncoding"),
282 'replace')
283 self.errors.insertPlainText(s)
284 self.errors.ensureCursorVisible()
285
286 def __sourceChanged(self, url):
287 """
288 Private slot to handle the sourceChanged signal of the contents pane.
289
290 @param url the url that was clicked (QUrl)
291 """
292 filename = url.path()
293 if Utilities.isWindowsPlatform():
294 if filename.startswith("/"):
295 filename = filename[1:]
296 ver = bytes(url.encodedQuery()).decode()
297 v1, v2 = ver.split('_')
298 if v1 == "" or v2 == "":
299 return
300 self.contents.scrollToAnchor(ver)
301
302 if self.diff:
303 del self.diff
304 self.diff = HgDiffDialog(self.vcs)
305 self.diff.show()
306 self.diff.start(filename, [v1, v2])
307
308 def on_passwordCheckBox_toggled(self, isOn):
309 """
310 Private slot to handle the password checkbox toggled.
311
312 @param isOn flag indicating the status of the check box (boolean)
313 """
314 if isOn:
315 self.input.setEchoMode(QLineEdit.Password)
316 else:
317 self.input.setEchoMode(QLineEdit.Normal)
318
319 @pyqtSlot()
320 def on_sendButton_clicked(self):
321 """
322 Private slot to send the input to the hg process.
323 """
324 input = self.input.text()
325 input += os.linesep
326
327 if self.passwordCheckBox.isChecked():
328 self.errors.insertPlainText(os.linesep)
329 self.errors.ensureCursorVisible()
330 else:
331 self.errors.insertPlainText(input)
332 self.errors.ensureCursorVisible()
333
334 self.process.write(input)
335
336 self.passwordCheckBox.setChecked(False)
337 self.input.clear()
338
339 def on_input_returnPressed(self):
340 """
341 Private slot to handle the press of the return key in the input field.
342 """
343 self.intercept = True
344 self.on_sendButton_clicked()
345
346 def keyPressEvent(self, evt):
347 """
348 Protected slot to handle a key press event.
349
350 @param evt the key press event (QKeyEvent)
351 """
352 if self.intercept:
353 self.intercept = False
354 evt.accept()
355 return
356 QWidget.keyPressEvent(self, evt)

eric ide

mercurial