Plugins/VcsPlugins/vcsSubversion/SvnRepoBrowserDialog.py

changeset 0
de9c2efb9d02
child 7
c679fb30c8f3
equal deleted inserted replaced
-1:000000000000 0:de9c2efb9d02
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2007 - 2009 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the subversion repository browser dialog.
8 """
9
10 import os
11
12 from PyQt4.QtGui import *
13 from PyQt4.QtCore import *
14
15 from Ui_SvnRepoBrowserDialog import Ui_SvnRepoBrowserDialog
16
17 import UI.PixmapCache
18
19 import Preferences
20
21 class SvnRepoBrowserDialog(QDialog, Ui_SvnRepoBrowserDialog):
22 """
23 Class implementing the subversion repository browser dialog.
24 """
25 def __init__(self, vcs, mode = "browse", parent = None):
26 """
27 Constructor
28
29 @param vcs reference to the vcs object
30 @param mode mode of the dialog (string, "browse" or "select")
31 @param parent parent widget (QWidget)
32 """
33 QDialog.__init__(self, parent)
34 self.setupUi(self)
35
36 self.repoTree.headerItem().setText(self.repoTree.columnCount(), "")
37 self.repoTree.header().setSortIndicator(0, Qt.AscendingOrder)
38
39 self.process = None
40 self.vcs = vcs
41 self.mode = mode
42
43 if self.mode == "select":
44 self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False)
45 self.buttonBox.button(QDialogButtonBox.Close).hide()
46 else:
47 self.buttonBox.button(QDialogButtonBox.Ok).hide()
48 self.buttonBox.button(QDialogButtonBox.Cancel).hide()
49
50 self.__dirIcon = UI.PixmapCache.getIcon("dirClosed.png")
51 self.__fileIcon = UI.PixmapCache.getIcon("fileMisc.png")
52
53 self.__urlRole = Qt.UserRole
54 self.__ignoreExpand = False
55 self.intercept = False
56
57 self.__rx_dir = \
58 QRegExp(r"""\s*([0-9]+)\s+(\w+)\s+((?:\w+\s+\d+|[0-9.]+\s+\w+)\s+[0-9:]+)\s+(.+)\s*""")
59 self.__rx_file = \
60 QRegExp(r"""\s*([0-9]+)\s+(\w+)\s+([0-9]+)\s((?:\w+\s+\d+|[0-9.]+\s+\w+)\s+[0-9:]+)\s+(.+)\s*""")
61
62 def closeEvent(self, e):
63 """
64 Private slot implementing a close event handler.
65
66 @param e close event (QCloseEvent)
67 """
68 if self.process is not None and \
69 self.process.state() != QProcess.NotRunning:
70 self.process.terminate()
71 QTimer.singleShot(2000, self.process, SLOT('kill()'))
72 self.process.waitForFinished(3000)
73
74 e.accept()
75
76 def __resort(self):
77 """
78 Private method to resort the tree.
79 """
80 self.repoTree.sortItems(self.repoTree.sortColumn(),
81 self.repoTree.header().sortIndicatorOrder())
82
83 def __resizeColumns(self):
84 """
85 Private method to resize the tree columns.
86 """
87 self.repoTree.header().resizeSections(QHeaderView.ResizeToContents)
88 self.repoTree.header().setStretchLastSection(True)
89
90 def __generateItem(self, repopath, revision, author, size, date,
91 nodekind, url):
92 """
93 Private method to generate a tree item in the repository tree.
94
95 @param parent parent of the item to be created (QTreeWidget or QTreeWidgetItem)
96 @param repopath path of the item (string)
97 @param revision revision info (string)
98 @param author author info (string)
99 @param size size info (string)
100 @param date date info (string)
101 @param nodekind node kind info (string, "dir" or "file")
102 @param url url of the entry (string)
103 @return reference to the generated item (QTreeWidgetItem)
104 """
105 path = repopath
106
107 itm = QTreeWidgetItem(self.parentItem, [
108 path,
109 revision,
110 author,
111 size,
112 date,
113 ])
114
115 if nodekind == "dir":
116 itm.setIcon(0, self.__dirIcon)
117 itm.setChildIndicatorPolicy(QTreeWidgetItem.ShowIndicator)
118 elif nodekind == "file":
119 itm.setIcon(0, self.__fileIcon)
120
121 itm.setData(0, self.__urlRole, QVariant(url))
122
123 itm.setTextAlignment(0, Qt.AlignLeft)
124 itm.setTextAlignment(1, Qt.AlignRight)
125 itm.setTextAlignment(2, Qt.AlignLeft)
126 itm.setTextAlignment(3, Qt.AlignRight)
127 itm.setTextAlignment(4, Qt.AlignLeft)
128
129 return itm
130
131 def __repoRoot(self, url):
132 """
133 Private method to get the repository root using the svn info command.
134
135 @param url the repository URL to browser (string)
136 @return repository root (string)
137 """
138 ioEncoding = Preferences.getSystem("IOEncoding")
139 repoRoot = None
140
141 process = QProcess()
142
143 args = []
144 args.append('info')
145 self.vcs.addArguments(args, self.vcs.options['global'])
146 args.append('--xml')
147 args.append(url)
148
149 process.start('svn', args)
150 procStarted = process.waitForStarted()
151 if procStarted:
152 finished = process.waitForFinished(30000)
153 if finished:
154 if process.exitCode() == 0:
155 output = unicode(process.readAllStandardOutput(),
156 ioEncoding, 'replace')
157 for line in output.splitlines():
158 line = line.strip()
159 if line.startswith('<root>'):
160 repoRoot = line.replace('<root>', '').replace('</root>', '')
161 break
162 else:
163 error = unicode(process.readAllStandardError())
164 self.errors.insertPlainText(error)
165 self.errors.ensureCursorVisible()
166 else:
167 QApplication.restoreOverrideCursor()
168 QMessageBox.critical(None,
169 self.trUtf8('Process Generation Error'),
170 self.trUtf8(
171 'The process {0} could not be started. '
172 'Ensure, that it is in the search path.'
173 ).format('svn'))
174 return repoRoot
175
176 def __listRepo(self, url, parent = None):
177 """
178 Private method to perform the svn list command.
179
180 @param url the repository URL to browse (string)
181 @param parent reference to the item, the data should be appended to
182 (QTreeWidget or QTreeWidgetItem)
183 """
184 QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
185 QApplication.processEvents()
186
187 self.repoUrl = url
188
189 if parent is None:
190 self.parentItem = self.repoTree
191 else:
192 self.parentItem = parent
193
194 if self.parentItem == self.repoTree:
195 repoRoot = self.__repoRoot(url)
196 if repoRoot is None:
197 self.__finish()
198 return
199 self.__ignoreExpand = True
200 itm = self.__generateItem(repoRoot, "", "", "", "", "dir", repoRoot)
201 itm.setExpanded(True)
202 self.parentItem = itm
203 urlPart = repoRoot
204 for element in url.replace(repoRoot, "").split("/"):
205 if element:
206 urlPart = "%s/%s" % (urlPart, element)
207 itm = self.__generateItem(element, "", "", "", "", "dir", urlPart)
208 itm.setExpanded(True)
209 self.parentItem = itm
210 itm.setExpanded(False)
211 self.__ignoreExpand = False
212 self.__finish()
213 return
214
215 self.intercept = False
216
217 if self.process:
218 self.process.kill()
219 else:
220 self.process = QProcess()
221 self.connect(self.process, SIGNAL('finished(int, QProcess::ExitStatus)'),
222 self.__procFinished)
223 self.connect(self.process, SIGNAL('readyReadStandardOutput()'),
224 self.__readStdout)
225 self.connect(self.process, SIGNAL('readyReadStandardError()'),
226 self.__readStderr)
227
228 args = []
229 args.append('list')
230 self.vcs.addArguments(args, self.vcs.options['global'])
231 if '--verbose' not in self.vcs.options['global']:
232 args.append('--verbose')
233 args.append(url)
234
235 self.process.start('svn', args)
236 procStarted = self.process.waitForStarted()
237 if not procStarted:
238 self.__finish()
239 self.inputGroup.setEnabled(False)
240 QMessageBox.critical(None,
241 self.trUtf8('Process Generation Error'),
242 self.trUtf8(
243 'The process {0} could not be started. '
244 'Ensure, that it is in the search path.'
245 ).format('svn'))
246 else:
247 self.inputGroup.setEnabled(True)
248
249 def __normalizeUrl(self, url):
250 """
251 Private method to normalite the url.
252
253 @param url the url to normalize (string)
254 @return normalized URL (string)
255 """
256 if url.endswith("/"):
257 return url[:-1]
258 return url
259
260 def start(self, url):
261 """
262 Public slot to start the svn info command.
263
264 @param url the repository URL to browser (string)
265 """
266 self.url = ""
267
268 self.urlCombo.addItem(self.__normalizeUrl(url))
269
270 @pyqtSlot(str)
271 def on_urlCombo_currentIndexChanged(self, text):
272 """
273 Private slot called, when a new repository URL is entered or selected.
274
275 @param text the text of the current item (string)
276 """
277 url = self.__normalizeUrl(text)
278 if url != self.url:
279 self.url = url
280 self.repoTree.clear()
281 self.__listRepo(url)
282
283 @pyqtSlot(QTreeWidgetItem)
284 def on_repoTree_itemExpanded(self, item):
285 """
286 Private slot called when an item is expanded.
287
288 @param item reference to the item to be expanded (QTreeWidgetItem)
289 """
290 if not self.__ignoreExpand:
291 url = item.data(0, self.__urlRole).toString()
292 self.__listRepo(url, item)
293
294 @pyqtSlot(QTreeWidgetItem)
295 def on_repoTree_itemCollapsed(self, item):
296 """
297 Private slot called when an item is collapsed.
298
299 @param item reference to the item to be collapsed (QTreeWidgetItem)
300 """
301 for child in item.takeChildren():
302 del child
303
304 @pyqtSlot()
305 def on_repoTree_itemSelectionChanged(self):
306 """
307 Private slot called when the selection changes.
308 """
309 if self.mode == "select":
310 self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(True)
311
312 def accept(self):
313 """
314 Public slot called when the dialog is accepted.
315 """
316 if self.focusWidget() == self.urlCombo:
317 return
318
319 QDialog.accept(self)
320
321 def getSelectedUrl(self):
322 """
323 Public method to retrieve the selected repository URL.
324
325 @return the selected repository URL (string)
326 """
327 items = self.repoTree.selectedItems()
328 if len(items) == 1:
329 return items[0].data(0, self.__urlRole).toString()
330 else:
331 return ""
332
333 def __finish(self):
334 """
335 Private slot called when the process finished or the user pressed the button.
336 """
337 if self.process is not None and \
338 self.process.state() != QProcess.NotRunning:
339 self.process.terminate()
340 QTimer.singleShot(2000, self.process, SLOT('kill()'))
341 self.process.waitForFinished(3000)
342
343 self.inputGroup.setEnabled(False)
344
345 self.repoTree.doItemsLayout()
346 self.__resizeColumns()
347 self.__resort()
348 QApplication.restoreOverrideCursor()
349
350 def __procFinished(self, exitCode, exitStatus):
351 """
352 Private slot connected to the finished signal.
353
354 @param exitCode exit code of the process (integer)
355 @param exitStatus exit status of the process (QProcess.ExitStatus)
356 """
357 self.__finish()
358
359 def __readStdout(self):
360 """
361 Private slot to handle the readyReadStandardOutput signal.
362
363 It reads the output of the process, formats it and inserts it into
364 the contents pane.
365 """
366 if self.process is not None:
367 self.process.setReadChannel(QProcess.StandardOutput)
368
369 while self.process.canReadLine():
370 s = unicode(self.process.readLine())
371 if self.__rx_dir.exactMatch(s):
372 revision = self.__rx_dir.cap(1)
373 author = self.__rx_dir.cap(2)
374 date = self.__rx_dir.cap(3)
375 name = self.__rx_dir.cap(4).strip()
376 if name.endswith("/"):
377 name = name[:-1]
378 size = ""
379 nodekind = "dir"
380 elif self.__rx_file.exactMatch(s):
381 revision = self.__rx_file.cap(1)
382 author = self.__rx_file.cap(2)
383 size = self.__rx_file.cap(3)
384 date = self.__rx_file.cap(4)
385 name = self.__rx_file.cap(5).strip()
386 nodekind = "file"
387 else:
388 continue
389 url = "%s/%s" % (self.repoUrl, name)
390 self.__generateItem(name, revision, author, size, date, nodekind, url)
391
392 def __readStderr(self):
393 """
394 Private slot to handle the readyReadStandardError signal.
395
396 It reads the error output of the process and inserts it into the
397 error pane.
398 """
399 if self.process is not None:
400 s = unicode(self.process.readAllStandardError())
401 self.errors.insertPlainText(s)
402 self.errors.ensureCursorVisible()
403
404 def on_passwordCheckBox_toggled(self, isOn):
405 """
406 Private slot to handle the password checkbox toggled.
407
408 @param isOn flag indicating the status of the check box (boolean)
409 """
410 if isOn:
411 self.input.setEchoMode(QLineEdit.Password)
412 else:
413 self.input.setEchoMode(QLineEdit.Normal)
414
415 @pyqtSlot()
416 def on_sendButton_clicked(self):
417 """
418 Private slot to send the input to the subversion process.
419 """
420 input = self.input.text()
421 input += os.linesep
422
423 if self.passwordCheckBox.isChecked():
424 self.errors.insertPlainText(os.linesep)
425 self.errors.ensureCursorVisible()
426 else:
427 self.errors.insertPlainText(input)
428 self.errors.ensureCursorVisible()
429
430 self.process.write(input)
431
432 self.passwordCheckBox.setChecked(False)
433 self.input.clear()
434
435 def on_input_returnPressed(self):
436 """
437 Private slot to handle the press of the return key in the input field.
438 """
439 self.intercept = True
440 self.on_sendButton_clicked()
441
442 def keyPressEvent(self, evt):
443 """
444 Protected slot to handle a key press event.
445
446 @param evt the key press event (QKeyEvent)
447 """
448 if self.intercept:
449 self.intercept = False
450 evt.accept()
451 return
452 QWidget.keyPressEvent(self, evt)
453

eric ide

mercurial