Plugins/VcsPlugins/vcsMercurial/ShelveExtension/HgShelveBrowserDialog.py

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

eric ide

mercurial