Plugins/VcsPlugins/vcsMercurial/ShelveExtension/HgShelveBrowserDialog.py

changeset 3297
7b7e8124fb51
child 3302
e92f0dd51979
equal deleted inserted replaced
3294:3aabaf28817b 3297:7b7e8124fb51
1 # -*- coding: utf-8 -*-
2
3 """
4 Module implementing Mercurial shelve browser dialog.
5 """
6
7 import os
8
9 from PyQt4.QtCore import pyqtSlot, Qt, QPoint, QProcess, QTimer
10 from PyQt4.QtGui import QWidget, QDialogButtonBox, QTreeWidgetItem, \
11 QAbstractButton, QMenu, QHeaderView, QApplication, QCursor, \
12 QLineEdit
13
14 from E5Gui import E5MessageBox
15
16 from .Ui_HgShelveBrowserDialog import Ui_HgShelveBrowserDialog
17
18 import Preferences
19
20
21 class HgShelveBrowserDialog(QWidget, Ui_HgShelveBrowserDialog):
22 """
23 Class implementing Mercurial shelve browser dialog.
24 """
25 NameColumn = 0
26 AgeColumn = 1
27 MessageColumn = 2
28
29 def __init__(self, vcs, parent=None):
30 """
31 Constructor
32
33 @param vcs reference to the vcs object
34 @param parent parent widget (QWidget)
35 """
36 super().__init__(parent)
37 self.setupUi(self)
38
39 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
40 self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
41
42 self.__position = QPoint()
43
44 self.__fileStatisticsRole = Qt.UserRole
45 self.__totalStatisticsRole = Qt.UserRole + 1
46
47 self.shelveList.header().setSortIndicator(0, Qt.AscendingOrder)
48
49 self.refreshButton = self.buttonBox.addButton(
50 self.tr("&Refresh"), QDialogButtonBox.ActionRole)
51 self.refreshButton.setToolTip(
52 self.tr("Press to refresh the list of shelves"))
53 self.refreshButton.setEnabled(False)
54
55 self.vcs = vcs
56 self.__hgClient = vcs.getClient()
57 self.__resetUI()
58
59 if self.__hgClient:
60 self.process = None
61 else:
62 self.process = QProcess()
63 self.process.finished.connect(self.__procFinished)
64 self.process.readyReadStandardOutput.connect(self.__readStdout)
65 self.process.readyReadStandardError.connect(self.__readStderr)
66
67 self.__contextMenu = QMenu()
68 self.__unshelveAct = self.__contextMenu.addAction(
69 self.tr("Restore selected shelve"), self.__unshelve)
70 self.__deleteAct = self.__contextMenu.addAction(
71 self.tr("Delete selected shelves"), self.__deleteShelves)
72 self.__contextMenu.addAction(
73 self.tr("Delete all shelves"), self.__cleanupShelves)
74
75 def closeEvent(self, e):
76 """
77 Private slot implementing a close event handler.
78
79 @param e close event (QCloseEvent)
80 """
81 if self.__hgClient:
82 if self.__hgClient.isExecuting():
83 self.__hgClient.cancel()
84 else:
85 if self.process is not None and \
86 self.process.state() != QProcess.NotRunning:
87 self.process.terminate()
88 QTimer.singleShot(2000, self.process.kill)
89 self.process.waitForFinished(3000)
90
91 self.__position = self.pos()
92
93 e.accept()
94
95 def show(self):
96 """
97 Public slot to show the dialog.
98 """
99 if not self.__position.isNull():
100 self.move(self.__position)
101 self.__resetUI()
102
103 super().show()
104
105 def __resetUI(self):
106 """
107 Private method to reset the user interface.
108 """
109 self.shelveList.clear()
110
111 def __resizeColumnsShelves(self):
112 """
113 Private method to resize the shelve list columns.
114 """
115 self.shelveList.header().resizeSections(QHeaderView.ResizeToContents)
116 self.shelveList.header().setStretchLastSection(True)
117
118 def __generateShelveEntry(self, name, age, message, fileStatistics,
119 totals):
120 """
121 Private method to generate the shelve items.
122
123 @param name name of the shelve (string)
124 @param age age of the shelve (string)
125 @param message shelve message (string)
126 @param fileStatistics per file change statistics (tuple of
127 four strings with file name, number of changes, number of
128 added lines and number of deleted lines)
129 @param totals overall statistics (tuple of three strings with
130 number of changed files, number of added lines and number
131 of deleted lines)
132 """
133 itm = QTreeWidgetItem(self.shelveList, [name, age, message])
134 itm.setData(0, self.__fileStatisticsRole, fileStatistics)
135 itm.setData(0, self.__totalStatisticsRole, totals)
136
137 def __getShelveEntries(self):
138 """
139 Private method to retrieve the list of shelves.
140 """
141 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
142 self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True)
143 self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
144 QApplication.processEvents()
145
146 QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
147 QApplication.processEvents()
148
149 self.buf = []
150 self.errors.clear()
151 self.intercept = False
152
153 args = []
154 args.append("shelve")
155 args.append("--list")
156 args.append("--stat")
157
158 if self.__hgClient:
159 self.inputGroup.setEnabled(False)
160 self.inputGroup.hide()
161
162 out, err = self.__hgClient.runcommand(args)
163 self.buf = out.splitlines(True)
164 if err:
165 self.__showError(err)
166 self.__processBuffer()
167 self.__finish()
168 else:
169 self.process.kill()
170
171 self.process.setWorkingDirectory(self.repodir)
172
173 self.inputGroup.setEnabled(True)
174 self.inputGroup.show()
175
176 self.process.start('hg', args)
177 procStarted = self.process.waitForStarted(5000)
178 if not procStarted:
179 self.inputGroup.setEnabled(False)
180 self.inputGroup.hide()
181 E5MessageBox.critical(
182 self,
183 self.tr('Process Generation Error'),
184 self.tr(
185 'The process {0} could not be started. '
186 'Ensure, that it is in the search path.'
187 ).format('hg'))
188
189 def start(self, projectDir):
190 """
191 Public slot to start the hg shelve command.
192
193 @param projectDir name of the project directory (string)
194 """
195 self.errorGroup.hide()
196 QApplication.processEvents()
197
198 self.__projectDir = projectDir
199
200 # find the root of the repo
201 self.repodir = self.__projectDir
202 while not os.path.isdir(os.path.join(self.repodir, self.vcs.adminDir)):
203 self.repodir = os.path.dirname(self.repodir)
204 if os.path.splitdrive(self.repodir)[1] == os.sep:
205 return
206
207 self.activateWindow()
208 self.raise_()
209
210 self.shelveList.clear()
211 self.__started = True
212 self.__getShelveEntries()
213
214 def __procFinished(self, exitCode, exitStatus):
215 """
216 Private slot connected to the finished signal.
217
218 @param exitCode exit code of the process (integer)
219 @param exitStatus exit status of the process (QProcess.ExitStatus)
220 """
221 self.__processBuffer()
222 self.__finish()
223
224 def __finish(self):
225 """
226 Private slot called when the process finished or the user pressed
227 the button.
228 """
229 if self.process is not None and \
230 self.process.state() != QProcess.NotRunning:
231 self.process.terminate()
232 QTimer.singleShot(2000, self.process.kill)
233 self.process.waitForFinished(3000)
234
235 QApplication.restoreOverrideCursor()
236
237 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
238 self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
239 self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
240
241 self.inputGroup.setEnabled(False)
242 self.inputGroup.hide()
243 self.refreshButton.setEnabled(True)
244
245 def __processBuffer(self):
246 """
247 Private method to process the buffered output of the hg shelve command.
248 """
249 lastWasFileStats = False
250 firstLine = True
251 itemData = {}
252 for line in self.buf:
253 if firstLine:
254 name, line = line.split("(", 1)
255 age, message = line.split(")", 1)
256 itemData["name"] = name.strip()
257 itemData["age"] = age.strip()
258 itemData["message"] = message.strip()
259 itemData["files"] = []
260 firstLine = False
261 elif '|' in line:
262 # file stats: foo.py | 3 ++-
263 file, changes = line.strip().split("|", 1)
264 total, addDelete = changes.strip().split(None, 1)
265 additions = str(addDelete.count("+"))
266 deletions = str(addDelete.count("-"))
267 itemData["files"].append((file, total, additions, deletions))
268 lastWasFileStats = True
269 elif lastWasFileStats:
270 # summary line
271 # 2 files changed, 15 insertions(+), 1 deletions(-)
272 total, added, deleted = line.strip().split(",", 2)
273 total = total.split()[0]
274 added = added.split()[0]
275 deleted = deleted.split()[0]
276 itemData["summary"] = (total, added, deleted)
277
278 self.__generateShelveEntry(
279 itemData["name"], itemData["age"], itemData["message"],
280 itemData["files"], itemData["summary"])
281
282 lastWasFileStats = False
283 firstLine = True
284 itemData = {}
285
286 self.__resizeColumnsShelves()
287
288 if self.__started:
289 self.shelveList.setCurrentItem(self.shelveList.topLevelItem(0))
290 self.__started = False
291
292 def __readStdout(self):
293 """
294 Private slot to handle the readyReadStandardOutput signal.
295
296 It reads the output of the process and inserts it into a buffer.
297 """
298 self.process.setReadChannel(QProcess.StandardOutput)
299
300 while self.process.canReadLine():
301 line = str(self.process.readLine(),
302 Preferences.getSystem("IOEncoding"),
303 'replace')
304 self.buf.append(line)
305
306 def __readStderr(self):
307 """
308 Private slot to handle the readyReadStandardError signal.
309
310 It reads the error output of the process and inserts it into the
311 error pane.
312 """
313 if self.process is not None:
314 s = str(self.process.readAllStandardError(),
315 Preferences.getSystem("IOEncoding"),
316 'replace')
317 self.__showError(s)
318
319 def __showError(self, out):
320 """
321 Private slot to show some error.
322
323 @param out error to be shown (string)
324 """
325 self.errorGroup.show()
326 self.errors.insertPlainText(out)
327 self.errors.ensureCursorVisible()
328
329 @pyqtSlot(QAbstractButton)
330 def on_buttonBox_clicked(self, button):
331 """
332 Private slot called by a button of the button box clicked.
333
334 @param button button that was clicked (QAbstractButton)
335 """
336 if button == self.buttonBox.button(QDialogButtonBox.Close):
337 self.close()
338 elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
339 self.cancelled = True
340 if self.__hgClient:
341 self.__hgClient.cancel()
342 else:
343 self.__finish()
344 elif button == self.refreshButton:
345 self.on_refreshButton_clicked()
346
347 @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem)
348 def on_shelveList_currentItemChanged(self, current, previous):
349 """
350 Private slot called, when the current item of the shelve list changes.
351
352 @param current reference to the new current item (QTreeWidgetItem)
353 @param previous reference to the old current item (QTreeWidgetItem)
354 """
355 self.statisticsList.clear()
356 if current:
357 for dataSet in current.data(0, self.__fileStatisticsRole):
358 QTreeWidgetItem(self.statisticsList, list(dataSet))
359
360 totals = current.data(0, self.__totalStatisticsRole)
361 self.filesLabel.setText(
362 self.tr("%n file(s) changed", None, int(totals[0])))
363 self.insertionsLabel.setText(
364 self.tr("%n line(s) inserted", None, int(totals[1])))
365 self.deletionsLabel.setText(
366 self.tr("%n line(s) deleted", None, int(totals[2])))
367 else:
368 self.filesLabel.setText("")
369 self.insertionsLabel.setText("")
370 self.deletionsLabel.setText("")
371
372 @pyqtSlot(QPoint)
373 def on_shelveList_customContextMenuRequested(self, pos):
374 """
375 Protected slot to show the context menu of the shelve list.
376
377 @param pos position of the mouse pointer (QPoint)
378 """
379 selectedItemsCount = len(self.shelveList.selectedItems())
380 self.__unshelveAct.setEnabled(selectedItemsCount == 1)
381 self.__deleteAct.setEnabled(selectedItemsCount > 0)
382
383 self.__contextMenu.popup(self.mapToGlobal(pos))
384
385 @pyqtSlot()
386 def on_refreshButton_clicked(self):
387 """
388 Private slot to refresh the list of shelves.
389 """
390 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
391 self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True)
392 self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
393
394 self.inputGroup.setEnabled(True)
395 self.inputGroup.show()
396 self.refreshButton.setEnabled(False)
397
398 self.start(self.__projectDir)
399
400 def on_passwordCheckBox_toggled(self, isOn):
401 """
402 Private slot to handle the password checkbox toggled.
403
404 @param isOn flag indicating the status of the check box (boolean)
405 """
406 if isOn:
407 self.input.setEchoMode(QLineEdit.Password)
408 else:
409 self.input.setEchoMode(QLineEdit.Normal)
410
411 @pyqtSlot()
412 def on_sendButton_clicked(self):
413 """
414 Private slot to send the input to the merurial process.
415 """
416 input = self.input.text()
417 input += os.linesep
418
419 if self.passwordCheckBox.isChecked():
420 self.errors.insertPlainText(os.linesep)
421 self.errors.ensureCursorVisible()
422 else:
423 self.errors.insertPlainText(input)
424 self.errors.ensureCursorVisible()
425 self.errorGroup.show()
426
427 self.process.write(input)
428
429 self.passwordCheckBox.setChecked(False)
430 self.input.clear()
431
432 def on_input_returnPressed(self):
433 """
434 Private slot to handle the press of the return key in the input field.
435 """
436 self.intercept = True
437 self.on_sendButton_clicked()
438
439 def keyPressEvent(self, evt):
440 """
441 Protected slot to handle a key press event.
442
443 @param evt the key press event (QKeyEvent)
444 """
445 if self.intercept:
446 self.intercept = False
447 evt.accept()
448 return
449 super().keyPressEvent(evt)
450
451 def __unshelve(self):
452 """
453 Private slot to restore the selected shelve of changes.
454 """
455 itm = self.shelveList.selectedItems()[0]
456 if itm is not None:
457 name = itm.text(self.NameColumn)
458 self.vcs.getExtensionObject("shelve")\
459 .hgUnshelve(self.__projectDir, shelveName=name)
460 self.on_refreshButton_clicked()
461
462 def __deleteShelves(self):
463 """
464 Private slot to delete the selected shelves.
465 """
466 shelveNames = []
467 for itm in self.shelveList.selectedItems():
468 shelveNames.append(itm.text(self.NameColumn))
469 if shelveNames:
470 self.vcs.getExtensionObject("shelve")\
471 .hgDeleteShelves(self.__projectDir, shelveNames=shelveNames)
472 self.on_refreshButton_clicked()
473
474 def __cleanupShelves(self):
475 """
476 Private slot to delete all shelves.
477 """
478 self.vcs.getExtensionObject("shelve")\
479 .hgCleanupShelves(self.__projectDir)
480 self.on_refreshButton_clicked()

eric ide

mercurial