Plugins/VcsPlugins/vcsSubversion/SvnLogDialog.py

changeset 0
de9c2efb9d02
child 12
1d8dd9706f46
equal deleted inserted replaced
-1:000000000000 0:de9c2efb9d02
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2003 - 2009 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a dialog to show the output of the svn log command process.
8 """
9
10 import os
11 import sys
12
13 from PyQt4.QtCore import *
14 from PyQt4.QtGui import *
15
16 from Ui_SvnLogDialog import Ui_SvnLogDialog
17 from SvnDiffDialog import SvnDiffDialog
18
19 import Utilities
20
21 class SvnLogDialog(QWidget, Ui_SvnLogDialog):
22 """
23 Class implementing a dialog to show the output of the svn log command process.
24
25 The dialog is nonmodal. Clicking a link in the upper text pane shows
26 a diff of the versions.
27 """
28 def __init__(self, vcs, parent = None):
29 """
30 Constructor
31
32 @param vcs reference to the vcs object
33 @param parent parent widget (QWidget)
34 """
35 QWidget.__init__(self, parent)
36 self.setupUi(self)
37
38 self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
39
40 self.process = QProcess()
41 self.vcs = vcs
42
43 self.contents.setHtml(\
44 self.trUtf8('<b>Processing your request, please wait...</b>'))
45
46 self.connect(self.process, SIGNAL('finished(int, QProcess::ExitStatus)'),
47 self.__procFinished)
48 self.connect(self.process, SIGNAL('readyReadStandardOutput()'),
49 self.__readStdout)
50 self.connect(self.process, SIGNAL('readyReadStandardError()'),
51 self.__readStderr)
52
53 self.connect(self.contents, SIGNAL('anchorClicked(const QUrl&)'),
54 self.__sourceChanged)
55
56 self.rx_sep = QRegExp('\\-+\\s*')
57 self.rx_sep2 = QRegExp('=+\\s*')
58 self.rx_rev = QRegExp('rev ([0-9]+): ([^|]*) \| ([^|]*) \| ([0-9]+) .*')
59 # "rev" followed by one or more decimals followed by a colon followed
60 # anything up to " | " (twice) followed by one or more decimals followed
61 # by anything
62 self.rx_rev2 = QRegExp('r([0-9]+) \| ([^|]*) \| ([^|]*) \| ([0-9]+) .*')
63 # "r" followed by one or more decimals followed by " | " followed
64 # anything up to " | " (twice) followed by one or more decimals followed
65 # by anything
66 self.rx_flags = QRegExp(' ([ADM])( .*)\\s*')
67 # three blanks followed by A or D or M
68 self.rx_changed = QRegExp('Changed .*\\s*')
69
70 self.flags = {
71 'A' : self.trUtf8('Added'),
72 'D' : self.trUtf8('Deleted'),
73 'M' : self.trUtf8('Modified')
74 }
75
76 self.revisions = [] # stack of remembered revisions
77 self.revString = self.trUtf8('revision')
78
79 self.buf = [] # buffer for stdout
80 self.diff = None
81
82 def closeEvent(self, e):
83 """
84 Private slot implementing a close event handler.
85
86 @param e close event (QCloseEvent)
87 """
88 if self.process is not None and \
89 self.process.state() != QProcess.NotRunning:
90 self.process.terminate()
91 QTimer.singleShot(2000, self.process, SLOT('kill()'))
92 self.process.waitForFinished(3000)
93
94 e.accept()
95
96 def start(self, fn, noEntries = 0):
97 """
98 Public slot to start the cvs log command.
99
100 @param fn filename to show the log for (string)
101 @param noEntries number of entries to show (integer)
102 """
103 self.errorGroup.hide()
104 QApplication.processEvents()
105
106 self.intercept = False
107 self.filename = fn
108 self.dname, self.fname = self.vcs.splitPath(fn)
109
110 self.process.kill()
111
112 args = []
113 args.append('log')
114 self.vcs.addArguments(args, self.vcs.options['global'])
115 self.vcs.addArguments(args, self.vcs.options['log'])
116 if noEntries:
117 args.append('--limit')
118 args.append(str(noEntries))
119 self.activateWindow()
120 self.raise_()
121 args.append(self.fname)
122
123 self.process.setWorkingDirectory(self.dname)
124
125 self.process.start('svn', args)
126 procStarted = self.process.waitForStarted()
127 if not procStarted:
128 self.inputGroup.setEnabled(False)
129 QMessageBox.critical(None,
130 self.trUtf8('Process Generation Error'),
131 self.trUtf8(
132 'The process {0} could not be started. '
133 'Ensure, that it is in the search path.'
134 ).format('svn'))
135
136 def __procFinished(self, exitCode, exitStatus):
137 """
138 Private slot connected to the finished signal.
139
140 @param exitCode exit code of the process (integer)
141 @param exitStatus exit status of the process (QProcess.ExitStatus)
142 """
143 self.inputGroup.setEnabled(False)
144
145 self.contents.clear()
146
147 lvers = 1
148 for s in self.buf:
149 rev_match = False
150 if self.rx_rev.exactMatch(s):
151 ver = self.rx_rev.cap(1)
152 author = self.rx_rev.cap(2)
153 date = self.rx_rev.cap(3)
154 # number of lines is ignored
155 rev_match = True
156 elif self.rx_rev2.exactMatch(s):
157 ver = self.rx_rev2.cap(1)
158 author = self.rx_rev2.cap(2)
159 date = self.rx_rev2.cap(3)
160 # number of lines is ignored
161 rev_match = True
162
163 if rev_match:
164 dstr = '<b>{0} {1}</b>'.format(self.revString, ver)
165 try:
166 lv = self.revisions[lvers]
167 lvers += 1
168 url = QUrl()
169 url.setScheme("file")
170 url.setPath(self.filename)
171 query = QByteArray()
172 query.append(lv).append('_').append(ver)
173 url.setEncodedQuery(query)
174 dstr += ' [<a href="%s" name="%s">%s</a>]' % (
175 url.toString(),
176 query,
177 self.trUtf8('diff to {0}').format(lv),
178 )
179 except IndexError:
180 pass
181 dstr += '<br />\n'
182 self.contents.insertHtml(dstr)
183
184 dstr = self.trUtf8('<i>author: {0}</i><br />\n').format(author)
185 self.contents.insertHtml(dstr)
186
187 dstr = self.trUtf8('<i>date: {0}</i><br />\n').format(date)
188 self.contents.insertHtml(dstr)
189
190 elif self.rx_sep.exactMatch(s) or self.rx_sep2.exactMatch(s):
191 self.contents.insertHtml('<hr />\n')
192
193 elif self.rx_flags.exactMatch(s):
194 dstr = self.flags[self.rx_flags.cap(1)]
195 dstr += self.rx_flags.cap(2)
196 dstr += '<br />\n'
197 self.contents.insertHtml(dstr)
198
199 elif self.rx_changed.exactMatch(s):
200 dstr = '<br />{0}<br />\n'.format(s)
201 self.contents.insertHtml(dstr)
202
203 else:
204 if s == "":
205 s = self.contents.insertHtml('<br />\n')
206 else:
207 self.contents.insertHtml(Utilities.html_encode(s))
208 self.contents.insertHtml('<br />\n')
209
210 tc = self.contents.textCursor()
211 tc.movePosition(QTextCursor.Start)
212 self.contents.setTextCursor(tc)
213 self.contents.ensureCursorVisible()
214
215 def __readStdout(self):
216 """
217 Private slot to handle the readyReadStandardOutput signal.
218
219 It reads the output of the process and inserts it into a buffer.
220 """
221 self.process.setReadChannel(QProcess.StandardOutput)
222
223 while self.process.canReadLine():
224 line = unicode(self.process.readLine())
225 self.buf.append(line)
226 if self.rx_rev.exactMatch(line):
227 ver = self.rx_rev.cap(1)
228 # save revision number for later use
229 self.revisions.append(ver)
230 elif self.rx_rev2.exactMatch(line):
231 ver = self.rx_rev2.cap(1)
232 # save revision number for later use
233 self.revisions.append(ver)
234
235 def __readStderr(self):
236 """
237 Private slot to handle the readyReadStandardError signal.
238
239 It reads the error output of the process and inserts it into the
240 error pane.
241 """
242 if self.process is not None:
243 self.errorGroup.show()
244 s = unicode(self.process.readAllStandardError())
245 self.errors.insertPlainText(s)
246 self.errors.ensureCursorVisible()
247
248 def __sourceChanged(self, url):
249 """
250 Private slot to handle the sourceChanged signal of the contents pane.
251
252 @param url the url that was clicked (QUrl)
253 """
254 self.contents.setSource(QUrl(''))
255 filename = url.path()
256 if Utilities.isWindowsPlatform():
257 if filename.startswith("/"):
258 filename = filename[1:]
259 ver = unicode(url.encodedQuery())
260 v1 = ver.split('_')[0]
261 v2 = ver.split('_')[1]
262 if v1 == "" or v2 == "":
263 return
264 self.contents.scrollToAnchor(ver)
265
266 if self.diff:
267 del self.diff
268 self.diff = SvnDiffDialog(self.vcs)
269 self.diff.show()
270 self.diff.start(filename, [v1, v2])
271
272 def on_passwordCheckBox_toggled(self, isOn):
273 """
274 Private slot to handle the password checkbox toggled.
275
276 @param isOn flag indicating the status of the check box (boolean)
277 """
278 if isOn:
279 self.input.setEchoMode(QLineEdit.Password)
280 else:
281 self.input.setEchoMode(QLineEdit.Normal)
282
283 @pyqtSlot()
284 def on_sendButton_clicked(self):
285 """
286 Private slot to send the input to the subversion process.
287 """
288 input = self.input.text()
289 input += os.linesep
290
291 if self.passwordCheckBox.isChecked():
292 self.errors.insertPlainText(os.linesep)
293 self.errors.ensureCursorVisible()
294 else:
295 self.errors.insertPlainText(input)
296 self.errors.ensureCursorVisible()
297
298 self.process.write(input)
299
300 self.passwordCheckBox.setChecked(False)
301 self.input.clear()
302
303 def on_input_returnPressed(self):
304 """
305 Private slot to handle the press of the return key in the input field.
306 """
307 self.intercept = True
308 self.on_sendButton_clicked()
309
310 def keyPressEvent(self, evt):
311 """
312 Protected slot to handle a key press event.
313
314 @param evt the key press event (QKeyEvent)
315 """
316 if self.intercept:
317 self.intercept = False
318 evt.accept()
319 return
320 QWidget.keyPressEvent(self, evt)

eric ide

mercurial