Plugins/VcsPlugins/vcsMercurial/HgStatusDialog.py

changeset 178
dd9f0bca5e2f
child 196
f7a39d6d1000
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 status command process.
8 """
9
10 import os
11
12 from PyQt4.QtCore import pyqtSlot, SIGNAL, Qt, QProcess, QTimer
13 from PyQt4.QtGui import QWidget, QDialogButtonBox, QMenu, QHeaderView, QTreeWidgetItem, \
14 QMessageBox, QLineEdit
15
16 from E5Gui.E5Application import e5App
17
18 from .Ui_HgStatusDialog import Ui_HgStatusDialog
19
20 import Preferences
21
22 class HgStatusDialog(QWidget, Ui_HgStatusDialog):
23 """
24 Class implementing a dialog to show the output of the hg status command process.
25 """
26 def __init__(self, vcs, parent = None):
27 """
28 Constructor
29
30 @param vcs reference to the vcs object
31 @param parent parent widget (QWidget)
32 """
33 QWidget.__init__(self, parent)
34 self.setupUi(self)
35
36 self.__statusColumn = 0
37 self.__pathColumn = 1
38 self.__lastColumn = self.statusList.columnCount()
39
40 self.refreshButton = \
41 self.buttonBox.addButton(self.trUtf8("Refresh"), QDialogButtonBox.ActionRole)
42 self.refreshButton.setToolTip(self.trUtf8("Press to refresh the status display"))
43 self.refreshButton.setEnabled(False)
44 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
45 self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
46
47 self.process = None
48 self.vcs = vcs
49 self.connect(self.vcs, SIGNAL("committed()"), self.__committed)
50
51 self.statusList.headerItem().setText(self.__lastColumn, "")
52 self.statusList.header().setSortIndicator(self.__pathColumn, Qt.AscendingOrder)
53
54 self.menuactions = []
55 self.menu = QMenu()
56 self.menuactions.append(self.menu.addAction(\
57 self.trUtf8("Commit changes to repository..."), self.__commit))
58 self.menu.addSeparator()
59 self.menuactions.append(self.menu.addAction(\
60 self.trUtf8("Add to repository"), self.__add))
61 self.menuactions.append(self.menu.addAction(\
62 self.trUtf8("Revert changes"), self.__revert))
63 self.menu.addSeparator()
64 self.menuactions.append(self.menu.addAction(self.trUtf8("Adjust column sizes"),
65 self.__resizeColumns))
66 for act in self.menuactions:
67 act.setEnabled(False)
68
69 self.statusList.setContextMenuPolicy(Qt.CustomContextMenu)
70 self.connect(self.statusList,
71 SIGNAL("customContextMenuRequested(const QPoint &)"),
72 self.__showContextMenu)
73
74 self.modifiedIndicators = [
75 self.trUtf8('added'),
76 self.trUtf8('modified'),
77 self.trUtf8('removed'),
78 ]
79
80 self.unversionedIndicators = [
81 self.trUtf8('not tracked'),
82 ]
83
84 self.status = {
85 'A' : self.trUtf8('added'),
86 'C' : self.trUtf8('normal'),
87 'I' : self.trUtf8('ignored'),
88 'M' : self.trUtf8('modified'),
89 'R' : self.trUtf8('removed'),
90 '?' : self.trUtf8('not tracked'),
91 '!' : self.trUtf8('missing'),
92 }
93
94 def __resort(self):
95 """
96 Private method to resort the tree.
97 """
98 self.statusList.sortItems(self.statusList.sortColumn(),
99 self.statusList.header().sortIndicatorOrder())
100
101 def __resizeColumns(self):
102 """
103 Private method to resize the list columns.
104 """
105 self.statusList.header().resizeSections(QHeaderView.ResizeToContents)
106 self.statusList.header().setStretchLastSection(True)
107
108 def __generateItem(self, status, path):
109 """
110 Private method to generate a status item in the status list.
111
112 @param status status indicator (string)
113 @param path path of the file or directory (string)
114 """
115 itm = QTreeWidgetItem(self.statusList, [
116 self.status[status],
117 path,
118 ])
119
120 itm.setTextAlignment(0, Qt.AlignHCenter)
121 itm.setTextAlignment(1, Qt.AlignLeft)
122
123 def closeEvent(self, e):
124 """
125 Private slot implementing a close event handler.
126
127 @param e close event (QCloseEvent)
128 """
129 if self.process is not None and \
130 self.process.state() != QProcess.NotRunning:
131 self.process.terminate()
132 QTimer.singleShot(2000, self.process.kill)
133 self.process.waitForFinished(3000)
134
135 e.accept()
136
137 def start(self, fn):
138 """
139 Public slot to start the svn status command.
140
141 @param fn filename(s)/directoryname(s) to show the status of
142 (string or list of strings)
143 """
144 self.errorGroup.hide()
145 self.intercept = False
146 self.args = fn
147
148 if self.process:
149 self.process.kill()
150 else:
151 self.process = QProcess()
152 self.connect(self.process, SIGNAL('finished(int, QProcess::ExitStatus)'),
153 self.__procFinished)
154 self.connect(self.process, SIGNAL('readyReadStandardOutput()'),
155 self.__readStdout)
156 self.connect(self.process, SIGNAL('readyReadStandardError()'),
157 self.__readStderr)
158
159 args = []
160 args.append('status')
161 self.vcs.addArguments(args, self.vcs.options['global'])
162 self.vcs.addArguments(args, self.vcs.options['status'])
163
164 if isinstance(fn, list):
165 self.dname, fnames = self.vcs.splitPathList(fn)
166 self.vcs.addArguments(args, fn)
167 else:
168 self.dname, fname = self.vcs.splitPath(fn)
169 args.append(fn)
170
171 # find the root of the repo
172 repodir = self.dname
173 while not os.path.isdir(os.path.join(repodir, self.vcs.adminDir)):
174 repodir = os.path.dirname(repodir)
175 if repodir == os.sep:
176 return
177
178 self.process.setWorkingDirectory(repodir)
179
180 self.setWindowTitle(self.trUtf8('Mercurial Status'))
181
182 self.process.start('hg', args)
183 procStarted = self.process.waitForStarted()
184 if not procStarted:
185 self.inputGroup.setEnabled(False)
186 self.inputGroup.hide()
187 QMessageBox.critical(None,
188 self.trUtf8('Process Generation Error'),
189 self.trUtf8(
190 'The process {0} could not be started. '
191 'Ensure, that it is in the search path.'
192 ).format('hg'))
193 else:
194 self.inputGroup.setEnabled(True)
195 self.inputGroup.show()
196
197 def __finish(self):
198 """
199 Private slot called when the process finished or the user pressed the button.
200 """
201 if self.process is not None and \
202 self.process.state() != QProcess.NotRunning:
203 self.process.terminate()
204 QTimer.singleShot(2000, self.process.kill)
205 self.process.waitForFinished(3000)
206
207 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
208 self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
209 self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
210
211 self.inputGroup.setEnabled(False)
212 self.inputGroup.hide()
213 self.refreshButton.setEnabled(True)
214
215 for act in self.menuactions:
216 act.setEnabled(True)
217
218 self.process = None
219
220 self.statusList.doItemsLayout()
221 self.__resort()
222 self.__resizeColumns()
223
224 def on_buttonBox_clicked(self, button):
225 """
226 Private slot called by a button of the button box clicked.
227
228 @param button button that was clicked (QAbstractButton)
229 """
230 if button == self.buttonBox.button(QDialogButtonBox.Close):
231 self.close()
232 elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
233 self.__finish()
234 elif button == self.refreshButton:
235 self.on_refreshButton_clicked()
236
237 def __procFinished(self, exitCode, exitStatus):
238 """
239 Private slot connected to the finished signal.
240
241 @param exitCode exit code of the process (integer)
242 @param exitStatus exit status of the process (QProcess.ExitStatus)
243 """
244 self.__finish()
245
246 def __readStdout(self):
247 """
248 Private slot to handle the readyReadStandardOutput signal.
249
250 It reads the output of the process, formats it and inserts it into
251 the contents pane.
252 """
253 if self.process is not None:
254 self.process.setReadChannel(QProcess.StandardOutput)
255
256 while self.process.canReadLine():
257 line = str(self.process.readLine(),
258 Preferences.getSystem("IOEncoding"),
259 'replace')
260 if not line.startswith(" "):
261 status, path = line.strip().split(" ", 1)
262 self.__generateItem(status, path)
263
264 def __readStderr(self):
265 """
266 Private slot to handle the readyReadStandardError signal.
267
268 It reads the error output of the process and inserts it into the
269 error pane.
270 """
271 if self.process is not None:
272 self.errorGroup.show()
273 s = str(self.process.readAllStandardError(),
274 Preferences.getSystem("IOEncoding"),
275 'replace')
276 self.errors.insertPlainText(s)
277 self.errors.ensureCursorVisible()
278
279 def on_passwordCheckBox_toggled(self, isOn):
280 """
281 Private slot to handle the password checkbox toggled.
282
283 @param isOn flag indicating the status of the check box (boolean)
284 """
285 if isOn:
286 self.input.setEchoMode(QLineEdit.Password)
287 else:
288 self.input.setEchoMode(QLineEdit.Normal)
289
290 @pyqtSlot()
291 def on_sendButton_clicked(self):
292 """
293 Private slot to send the input to the subversion process.
294 """
295 input = self.input.text()
296 input += os.linesep
297
298 if self.passwordCheckBox.isChecked():
299 self.errors.insertPlainText(os.linesep)
300 self.errors.ensureCursorVisible()
301 else:
302 self.errors.insertPlainText(input)
303 self.errors.ensureCursorVisible()
304
305 self.process.write(input)
306
307 self.passwordCheckBox.setChecked(False)
308 self.input.clear()
309
310 def on_input_returnPressed(self):
311 """
312 Private slot to handle the press of the return key in the input field.
313 """
314 self.intercept = True
315 self.on_sendButton_clicked()
316
317 def keyPressEvent(self, evt):
318 """
319 Protected slot to handle a key press event.
320
321 @param evt the key press event (QKeyEvent)
322 """
323 if self.intercept:
324 self.intercept = False
325 evt.accept()
326 return
327 QWidget.keyPressEvent(self, evt)
328
329 @pyqtSlot()
330 def on_refreshButton_clicked(self):
331 """
332 Private slot to refresh the status display.
333 """
334 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
335 self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True)
336 self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
337
338 self.inputGroup.setEnabled(True)
339 self.inputGroup.show()
340 self.refreshButton.setEnabled(False)
341
342 for act in self.menuactions:
343 act.setEnabled(False)
344
345 self.statusList.clear()
346
347 self.start(self.args)
348
349 ############################################################################
350 ## Context menu handling methods
351 ############################################################################
352
353 def __showContextMenu(self, coord):
354 """
355 Protected slot to show the context menu of the status list.
356
357 @param coord the position of the mouse pointer (QPoint)
358 """
359 self.menu.popup(self.mapToGlobal(coord))
360
361 def __commit(self):
362 """
363 Private slot to handle the Commit context menu entry.
364 """
365 names = [os.path.join(self.dname, itm.text(self.__pathColumn)) \
366 for itm in self.__getModifiedItems()]
367 if not names:
368 QMessageBox.information(self,
369 self.trUtf8("Commit"),
370 self.trUtf8("""There are no uncommitted changes available/selected."""))
371 return
372
373 if Preferences.getVCS("AutoSaveFiles"):
374 vm = e5App().getObject("ViewManager")
375 for name in names:
376 vm.saveEditor(name)
377 self.vcs.vcsCommit(names, '')
378
379 def __committed(self):
380 """
381 Private slot called after the commit has finished.
382 """
383 if self.isVisible():
384 self.on_refreshButton_clicked()
385 self.vcs.checkVCSStatus()
386
387 def __add(self):
388 """
389 Private slot to handle the Add context menu entry.
390 """
391 names = [os.path.join(self.dname, itm.text(self.__pathColumn)) \
392 for itm in self.__getUnversionedItems()]
393 if not names:
394 QMessageBox.information(self,
395 self.trUtf8("Add"),
396 self.trUtf8("""There are no unversioned entries available/selected."""))
397 return
398
399 self.vcs.vcsAdd(names)
400 self.on_refreshButton_clicked()
401
402 project = e5App().getObject("Project")
403 for name in names:
404 project.getModel().updateVCSStatus(name)
405 self.vcs.checkVCSStatus()
406
407 def __revert(self):
408 """
409 Private slot to handle the Revert context menu entry.
410 """
411 names = [os.path.join(self.dname, itm.text(self.__pathColumn)) \
412 for itm in self.__getModifiedItems()]
413 if not names:
414 QMessageBox.information(self,
415 self.trUtf8("Revert"),
416 self.trUtf8("""There are no uncommitted changes available/selected."""))
417 return
418
419 self.vcs.vcsRevert(names)
420 self.on_refreshButton_clicked()
421
422 project = e5App().getObject("Project")
423 for name in names:
424 project.getModel().updateVCSStatus(name)
425 self.vcs.checkVCSStatus()
426
427 def __getModifiedItems(self):
428 """
429 Private method to retrieve all entries, that have a modified status.
430
431 @return list of all items with a modified status
432 """
433 modifiedItems = []
434 for itm in self.statusList.selectedItems():
435 if itm.text(self.__statusColumn) in self.modifiedIndicators:
436 modifiedItems.append(itm)
437 return modifiedItems
438
439 def __getUnversionedItems(self):
440 """
441 Private method to retrieve all entries, that have an unversioned status.
442
443 @return list of all items with an unversioned status
444 """
445 unversionedItems = []
446 for itm in self.statusList.selectedItems():
447 if itm.text(self.__statusColumn) in self.unversionedIndicators:
448 unversionedItems.append(itm)
449 return unversionedItems

eric ide

mercurial