eric6/Plugins/VcsPlugins/vcsMercurial/HgStatusDialog.py

changeset 6942
2602857055c5
parent 6645
ad476851d7e0
child 7183
4ac1c9daa90b
equal deleted inserted replaced
6941:f99d60d6b59b 6942:2602857055c5
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2010 - 2019 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a dialog to show the output of the hg status command
8 process.
9 """
10
11 from __future__ import unicode_literals
12 try:
13 str = unicode
14 except NameError:
15 pass
16
17 import os
18
19 from PyQt5.QtCore import pyqtSlot, Qt, QProcess, QTimer, QSize
20 from PyQt5.QtGui import QTextCursor, QCursor
21 from PyQt5.QtWidgets import QWidget, QDialogButtonBox, QMenu, QHeaderView, \
22 QTreeWidgetItem, QLineEdit, QToolTip
23
24 from E5Gui.E5Application import e5App
25 from E5Gui import E5MessageBox
26
27 from .Ui_HgStatusDialog import Ui_HgStatusDialog
28
29 from .HgDiffHighlighter import HgDiffHighlighter
30 from .HgDiffGenerator import HgDiffGenerator
31
32 import Preferences
33 import UI.PixmapCache
34 from Globals import qVersionTuple, strToQByteArray
35
36
37 class HgStatusDialog(QWidget, Ui_HgStatusDialog):
38 """
39 Class implementing a dialog to show the output of the hg status command
40 process.
41 """
42 def __init__(self, vcs, mq=False, parent=None):
43 """
44 Constructor
45
46 @param vcs reference to the vcs object
47 @param mq flag indicating to show a queue repo status (boolean)
48 @param parent parent widget (QWidget)
49 """
50 super(HgStatusDialog, self).__init__(parent)
51 self.setupUi(self)
52
53 self.__toBeCommittedColumn = 0
54 self.__statusColumn = 1
55 self.__pathColumn = 2
56 self.__lastColumn = self.statusList.columnCount()
57
58 self.refreshButton = self.buttonBox.addButton(
59 self.tr("Refresh"), QDialogButtonBox.ActionRole)
60 self.refreshButton.setToolTip(
61 self.tr("Press to refresh the status display"))
62 self.refreshButton.setEnabled(False)
63 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
64 self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
65
66 self.diff = None
67 self.vcs = vcs
68 self.vcs.committed.connect(self.__committed)
69 self.__hgClient = self.vcs.getClient()
70 self.__mq = mq
71 if self.__hgClient:
72 self.process = None
73 else:
74 self.process = QProcess()
75 self.process.finished.connect(self.__procFinished)
76 self.process.readyReadStandardOutput.connect(self.__readStdout)
77 self.process.readyReadStandardError.connect(self.__readStderr)
78
79 self.statusList.headerItem().setText(self.__lastColumn, "")
80 self.statusList.header().setSortIndicator(
81 self.__pathColumn, Qt.AscendingOrder)
82
83 font = Preferences.getEditorOtherFonts("MonospacedFont")
84 self.diffEdit.setFontFamily(font.family())
85 self.diffEdit.setFontPointSize(font.pointSize())
86
87 self.diffHighlighter = HgDiffHighlighter(self.diffEdit.document())
88 self.__diffGenerator = HgDiffGenerator(vcs, self)
89 self.__diffGenerator.finished.connect(self.__generatorFinished)
90
91 self.__selectedName = ""
92
93 self.modifiedIndicators = [
94 self.tr('added'),
95 self.tr('modified'),
96 self.tr('removed'),
97 ]
98
99 self.unversionedIndicators = [
100 self.tr('not tracked'),
101 ]
102
103 self.missingIndicators = [
104 self.tr('missing')
105 ]
106
107 self.status = {
108 'A': self.tr('added'),
109 'C': self.tr('normal'),
110 'I': self.tr('ignored'),
111 'M': self.tr('modified'),
112 'R': self.tr('removed'),
113 '?': self.tr('not tracked'),
114 '!': self.tr('missing'),
115 }
116
117 self.__initActionsMenu()
118
119 if mq:
120 self.diffLabel.setVisible(False)
121 self.diffEdit.setVisible(False)
122 self.actionsButton.setEnabled(False)
123 self.diffSplitter.setSizes([600, 0])
124 else:
125 self.diffSplitter.setSizes([300, 300])
126
127 def __initActionsMenu(self):
128 """
129 Private method to initialize the actions menu.
130 """
131 self.__actionsMenu = QMenu()
132 self.__actionsMenu.setTearOffEnabled(True)
133 if qVersionTuple() >= (5, 1, 0):
134 self.__actionsMenu.setToolTipsVisible(True)
135 else:
136 self.__actionsMenu.hovered.connect(self.__actionsMenuHovered)
137 self.__actionsMenu.aboutToShow.connect(self.__showActionsMenu)
138
139 self.__commitAct = self.__actionsMenu.addAction(
140 self.tr("Commit"), self.__commit)
141 self.__commitAct.setToolTip(self.tr("Commit the selected changes"))
142 self.__commitSelectAct = self.__actionsMenu.addAction(
143 self.tr("Select all for commit"), self.__commitSelectAll)
144 self.__commitDeselectAct = self.__actionsMenu.addAction(
145 self.tr("Unselect all from commit"), self.__commitDeselectAll)
146
147 self.__actionsMenu.addSeparator()
148
149 self.__addAct = self.__actionsMenu.addAction(
150 self.tr("Add"), self.__add)
151 self.__addAct.setToolTip(self.tr("Add the selected files"))
152 self.__lfAddLargeAct = self.__actionsMenu.addAction(
153 self.tr("Add as Large Files"), lambda: self.__lfAdd("large"))
154 self.__lfAddLargeAct.setToolTip(self.tr(
155 "Add the selected files as a large files using the 'Large Files'"
156 " extension"))
157 self.__lfAddNormalAct = self.__actionsMenu.addAction(
158 self.tr("Add as Normal Files"), lambda: self.__lfAdd("normal"))
159 self.__lfAddNormalAct.setToolTip(self.tr(
160 "Add the selected files as a normal files using the 'Large Files'"
161 " extension"))
162
163 self.__actionsMenu.addSeparator()
164
165 self.__diffAct = self.__actionsMenu.addAction(
166 self.tr("Differences"), self.__diff)
167 self.__diffAct.setToolTip(self.tr(
168 "Shows the differences of the selected entry in a"
169 " separate dialog"))
170 self.__sbsDiffAct = self.__actionsMenu.addAction(
171 self.tr("Differences Side-By-Side"), self.__sbsDiff)
172 self.__sbsDiffAct.setToolTip(self.tr(
173 "Shows the differences of the selected entry side-by-side in"
174 " a separate dialog"))
175
176 self.__actionsMenu.addSeparator()
177
178 self.__revertAct = self.__actionsMenu.addAction(
179 self.tr("Revert"), self.__revert)
180 self.__revertAct.setToolTip(self.tr(
181 "Reverts the changes of the selected files"))
182
183 self.__actionsMenu.addSeparator()
184
185 self.__forgetAct = self.__actionsMenu.addAction(
186 self.tr("Forget missing"), self.__forget)
187 self.__forgetAct.setToolTip(self.tr(
188 "Forgets about the selected missing files"))
189 self.__restoreAct = self.__actionsMenu.addAction(
190 self.tr("Restore missing"), self.__restoreMissing)
191 self.__restoreAct.setToolTip(self.tr(
192 "Restores the selected missing files"))
193
194 self.__actionsMenu.addSeparator()
195
196 act = self.__actionsMenu.addAction(
197 self.tr("Adjust column sizes"), self.__resizeColumns)
198 act.setToolTip(self.tr(
199 "Adjusts the width of all columns to their contents"))
200
201 self.actionsButton.setIcon(
202 UI.PixmapCache.getIcon("actionsToolButton.png"))
203 self.actionsButton.setMenu(self.__actionsMenu)
204
205 def __actionsMenuHovered(self, action):
206 """
207 Private slot to show the tooltip for an action menu entry.
208
209 @param action action to show tooltip for
210 @type QAction
211 """
212 QToolTip.showText(
213 QCursor.pos(), action.toolTip(),
214 self.__actionsMenu, self.__actionsMenu.actionGeometry(action))
215
216 def closeEvent(self, e):
217 """
218 Protected slot implementing a close event handler.
219
220 @param e close event (QCloseEvent)
221 """
222 if self.__hgClient:
223 if self.__hgClient.isExecuting():
224 self.__hgClient.cancel()
225 else:
226 if self.process is not None and \
227 self.process.state() != QProcess.NotRunning:
228 self.process.terminate()
229 QTimer.singleShot(2000, self.process.kill)
230 self.process.waitForFinished(3000)
231
232 if self.__mq:
233 self.vcs.getPlugin().setPreferences(
234 "MqStatusDialogGeometry", self.saveGeometry())
235 self.vcs.getPlugin().setPreferences(
236 "MqStatusDialogSplitterState", self.diffSplitter.saveState())
237 else:
238 self.vcs.getPlugin().setPreferences(
239 "StatusDialogGeometry", self.saveGeometry())
240 self.vcs.getPlugin().setPreferences(
241 "StatusDialogSplitterState", self.diffSplitter.saveState())
242
243 e.accept()
244
245 def show(self):
246 """
247 Public slot to show the dialog.
248 """
249 super(HgStatusDialog, self).show()
250
251 if self.__mq:
252 geom = self.vcs.getPlugin().getPreferences(
253 "MqStatusDialogGeometry")
254 else:
255 geom = self.vcs.getPlugin().getPreferences(
256 "StatusDialogGeometry")
257 if geom.isEmpty():
258 s = QSize(800, 600)
259 self.resize(s)
260 else:
261 self.restoreGeometry(geom)
262
263 if self.__mq:
264 diffSplitterState = self.vcs.getPlugin().getPreferences(
265 "MqStatusDialogSplitterState")
266 else:
267 diffSplitterState = self.vcs.getPlugin().getPreferences(
268 "StatusDialogSplitterState")
269 if diffSplitterState is not None:
270 self.diffSplitter.restoreState(diffSplitterState)
271
272 def __resort(self):
273 """
274 Private method to resort the tree.
275 """
276 self.statusList.sortItems(
277 self.statusList.sortColumn(),
278 self.statusList.header().sortIndicatorOrder())
279
280 def __resizeColumns(self):
281 """
282 Private method to resize the list columns.
283 """
284 self.statusList.header().resizeSections(QHeaderView.ResizeToContents)
285 self.statusList.header().setStretchLastSection(True)
286
287 def __generateItem(self, status, path):
288 """
289 Private method to generate a status item in the status list.
290
291 @param status status indicator (string)
292 @param path path of the file or directory (string)
293 """
294 statusText = self.status[status]
295 itm = QTreeWidgetItem(self.statusList, [
296 "",
297 statusText,
298 path,
299 ])
300
301 itm.setTextAlignment(1, Qt.AlignHCenter)
302 itm.setTextAlignment(2, Qt.AlignLeft)
303
304 if status in "AMR":
305 itm.setFlags(itm.flags() | Qt.ItemIsUserCheckable)
306 itm.setCheckState(self.__toBeCommittedColumn, Qt.Checked)
307 else:
308 itm.setFlags(itm.flags() & ~Qt.ItemIsUserCheckable)
309
310 if statusText not in self.__statusFilters:
311 self.__statusFilters.append(statusText)
312
313 def start(self, fn):
314 """
315 Public slot to start the hg status command.
316
317 @param fn filename(s)/directoryname(s) to show the status of
318 (string or list of strings)
319 """
320 self.errorGroup.hide()
321 self.intercept = False
322 self.args = fn
323
324 self.actionsButton.setEnabled(False)
325
326 self.statusFilterCombo.clear()
327 self.__statusFilters = []
328 self.statusList.clear()
329
330 if self.__mq:
331 self.setWindowTitle(
332 self.tr("Mercurial Queue Repository Status"))
333 else:
334 self.setWindowTitle(self.tr('Mercurial Status'))
335
336 args = self.vcs.initCommand("status")
337 if self.__mq:
338 args.append('--mq')
339 if isinstance(fn, list):
340 self.dname, fnames = self.vcs.splitPathList(fn)
341 else:
342 self.dname, fname = self.vcs.splitPath(fn)
343 else:
344 if self.vcs.hasSubrepositories():
345 args.append("--subrepos")
346
347 if isinstance(fn, list):
348 self.dname, fnames = self.vcs.splitPathList(fn)
349 self.vcs.addArguments(args, fn)
350 else:
351 self.dname, fname = self.vcs.splitPath(fn)
352 args.append(fn)
353
354 # find the root of the repo
355 repodir = self.dname
356 while not os.path.isdir(os.path.join(repodir, self.vcs.adminDir)):
357 repodir = os.path.dirname(repodir)
358 if os.path.splitdrive(repodir)[1] == os.sep:
359 return
360
361 if self.__hgClient:
362 self.inputGroup.setEnabled(False)
363 self.inputGroup.hide()
364 self.refreshButton.setEnabled(False)
365
366 out, err = self.__hgClient.runcommand(args)
367 if err:
368 self.__showError(err)
369 if out:
370 for line in out.splitlines():
371 self.__processOutputLine(line)
372 if self.__hgClient.wasCanceled():
373 break
374 self.__finish()
375 else:
376 if self.process:
377 self.process.kill()
378
379 self.process.setWorkingDirectory(repodir)
380
381 self.process.start('hg', args)
382 procStarted = self.process.waitForStarted(5000)
383 if not procStarted:
384 self.inputGroup.setEnabled(False)
385 self.inputGroup.hide()
386 E5MessageBox.critical(
387 self,
388 self.tr('Process Generation Error'),
389 self.tr(
390 'The process {0} could not be started. '
391 'Ensure, that it is in the search path.'
392 ).format('hg'))
393 else:
394 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
395 self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True)
396 self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
397
398 self.refreshButton.setEnabled(False)
399
400 def __finish(self):
401 """
402 Private slot called when the process finished or the user pressed
403 the button.
404 """
405 if self.process is not None and \
406 self.process.state() != QProcess.NotRunning:
407 self.process.terminate()
408 QTimer.singleShot(2000, self.process.kill)
409 self.process.waitForFinished(3000)
410
411 self.inputGroup.setEnabled(False)
412 self.inputGroup.hide()
413 self.refreshButton.setEnabled(True)
414
415 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
416 self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
417 self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
418 self.buttonBox.button(QDialogButtonBox.Close).setFocus(
419 Qt.OtherFocusReason)
420
421 self.__statusFilters.sort()
422 self.__statusFilters.insert(0, "<{0}>".format(self.tr("all")))
423 self.statusFilterCombo.addItems(self.__statusFilters)
424
425 if not self.__mq:
426 self.actionsButton.setEnabled(True)
427
428 self.__resort()
429 self.__resizeColumns()
430
431 self.__refreshDiff()
432
433 def on_buttonBox_clicked(self, button):
434 """
435 Private slot called by a button of the button box clicked.
436
437 @param button button that was clicked (QAbstractButton)
438 """
439 if button == self.buttonBox.button(QDialogButtonBox.Close):
440 self.close()
441 elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
442 if self.__hgClient:
443 self.__hgClient.cancel()
444 else:
445 self.__finish()
446 elif button == self.refreshButton:
447 self.on_refreshButton_clicked()
448
449 def __procFinished(self, exitCode, exitStatus):
450 """
451 Private slot connected to the finished signal.
452
453 @param exitCode exit code of the process (integer)
454 @param exitStatus exit status of the process (QProcess.ExitStatus)
455 """
456 self.__finish()
457
458 def __readStdout(self):
459 """
460 Private slot to handle the readyReadStandardOutput signal.
461
462 It reads the output of the process, formats it and inserts it into
463 the contents pane.
464 """
465 if self.process is not None:
466 self.process.setReadChannel(QProcess.StandardOutput)
467
468 while self.process.canReadLine():
469 line = str(self.process.readLine(), self.vcs.getEncoding(),
470 'replace')
471 self.__processOutputLine(line)
472
473 def __processOutputLine(self, line):
474 """
475 Private method to process the lines of output.
476
477 @param line output line to be processed (string)
478 """
479 if line[0] in "ACIMR?!" and line[1] == " ":
480 status, path = line.strip().split(" ", 1)
481 self.__generateItem(status, path)
482
483 def __readStderr(self):
484 """
485 Private slot to handle the readyReadStandardError signal.
486
487 It reads the error output of the process and inserts it into the
488 error pane.
489 """
490 if self.process is not None:
491 s = str(self.process.readAllStandardError(),
492 self.vcs.getEncoding(), 'replace')
493 self.__showError(s)
494
495 def __showError(self, out):
496 """
497 Private slot to show some error.
498
499 @param out error to be shown (string)
500 """
501 self.errorGroup.show()
502 self.errors.insertPlainText(out)
503 self.errors.ensureCursorVisible()
504
505 if not self.__hgClient:
506 # show input in case the process asked for some input
507 self.inputGroup.setEnabled(True)
508 self.inputGroup.show()
509
510 def on_passwordCheckBox_toggled(self, isOn):
511 """
512 Private slot to handle the password checkbox toggled.
513
514 @param isOn flag indicating the status of the check box (boolean)
515 """
516 if isOn:
517 self.input.setEchoMode(QLineEdit.Password)
518 else:
519 self.input.setEchoMode(QLineEdit.Normal)
520
521 @pyqtSlot()
522 def on_sendButton_clicked(self):
523 """
524 Private slot to send the input to the subversion process.
525 """
526 inputTxt = self.input.text()
527 inputTxt += os.linesep
528
529 if self.passwordCheckBox.isChecked():
530 self.errors.insertPlainText(os.linesep)
531 self.errors.ensureCursorVisible()
532 else:
533 self.errors.insertPlainText(inputTxt)
534 self.errors.ensureCursorVisible()
535
536 self.process.write(strToQByteArray(inputTxt))
537
538 self.passwordCheckBox.setChecked(False)
539 self.input.clear()
540
541 def on_input_returnPressed(self):
542 """
543 Private slot to handle the press of the return key in the input field.
544 """
545 self.intercept = True
546 self.on_sendButton_clicked()
547
548 def keyPressEvent(self, evt):
549 """
550 Protected slot to handle a key press event.
551
552 @param evt the key press event (QKeyEvent)
553 """
554 if self.intercept:
555 self.intercept = False
556 evt.accept()
557 return
558 super(HgStatusDialog, self).keyPressEvent(evt)
559
560 @pyqtSlot()
561 def on_refreshButton_clicked(self):
562 """
563 Private slot to refresh the status display.
564 """
565 selectedItems = self.statusList.selectedItems()
566 if len(selectedItems) == 1:
567 self.__selectedName = selectedItems[0].text(self.__pathColumn)
568 else:
569 self.__selectedName = ""
570
571 self.start(self.args)
572
573 @pyqtSlot(str)
574 def on_statusFilterCombo_activated(self, txt):
575 """
576 Private slot to react to the selection of a status filter.
577
578 @param txt selected status filter (string)
579 """
580 if txt == "<{0}>".format(self.tr("all")):
581 for topIndex in range(self.statusList.topLevelItemCount()):
582 topItem = self.statusList.topLevelItem(topIndex)
583 topItem.setHidden(False)
584 else:
585 for topIndex in range(self.statusList.topLevelItemCount()):
586 topItem = self.statusList.topLevelItem(topIndex)
587 topItem.setHidden(topItem.text(self.__statusColumn) != txt)
588
589 @pyqtSlot()
590 def on_statusList_itemSelectionChanged(self):
591 """
592 Private slot to act upon changes of selected items.
593 """
594 self.__generateDiffs()
595
596 ###########################################################################
597 ## Menu handling methods
598 ###########################################################################
599
600 def __showActionsMenu(self):
601 """
602 Private slot to prepare the actions button menu before it is shown.
603 """
604 modified = len(self.__getModifiedItems())
605 unversioned = len(self.__getUnversionedItems())
606 missing = len(self.__getMissingItems())
607 commitable = len(self.__getCommitableItems())
608 commitableUnselected = len(self.__getCommitableUnselectedItems())
609
610 self.__addAct.setEnabled(unversioned)
611 self.__diffAct.setEnabled(modified)
612 self.__sbsDiffAct.setEnabled(modified == 1)
613 self.__revertAct.setEnabled(modified)
614 self.__forgetAct.setEnabled(missing)
615 self.__restoreAct.setEnabled(missing)
616 self.__commitAct.setEnabled(commitable)
617 self.__commitSelectAct.setEnabled(commitableUnselected)
618 self.__commitDeselectAct.setEnabled(commitable)
619
620 if self.vcs.isExtensionActive("largefiles"):
621 enable = bool(unversioned)
622 else:
623 enable = False
624 self.__lfAddLargeAct.setEnabled(enable)
625 self.__lfAddNormalAct.setEnabled(enable)
626
627 def __commit(self):
628 """
629 Private slot to handle the Commit context menu entry.
630 """
631 if self.__mq:
632 self.vcs.vcsCommit(self.dname, "", mq=True)
633 else:
634 names = [os.path.join(self.dname, itm.text(self.__pathColumn))
635 for itm in self.__getCommitableItems()]
636 if not names:
637 E5MessageBox.information(
638 self,
639 self.tr("Commit"),
640 self.tr("""There are no entries selected to be"""
641 """ committed."""))
642 return
643
644 if Preferences.getVCS("AutoSaveFiles"):
645 vm = e5App().getObject("ViewManager")
646 for name in names:
647 vm.saveEditor(name)
648 self.vcs.vcsCommit(names, '')
649
650 def __committed(self):
651 """
652 Private slot called after the commit has finished.
653 """
654 if self.isVisible():
655 self.on_refreshButton_clicked()
656 self.vcs.checkVCSStatus()
657
658 def __commitSelectAll(self):
659 """
660 Private slot to select all entries for commit.
661 """
662 self.__commitSelect(True)
663
664 def __commitDeselectAll(self):
665 """
666 Private slot to deselect all entries from commit.
667 """
668 self.__commitSelect(False)
669
670 def __add(self):
671 """
672 Private slot to handle the Add context menu entry.
673 """
674 names = [os.path.join(self.dname, itm.text(self.__pathColumn))
675 for itm in self.__getUnversionedItems()]
676 if not names:
677 E5MessageBox.information(
678 self,
679 self.tr("Add"),
680 self.tr("""There are no unversioned entries"""
681 """ available/selected."""))
682 return
683
684 self.vcs.vcsAdd(names)
685 self.on_refreshButton_clicked()
686
687 project = e5App().getObject("Project")
688 for name in names:
689 project.getModel().updateVCSStatus(name)
690 self.vcs.checkVCSStatus()
691
692 def __lfAdd(self, mode):
693 """
694 Private slot to add a file to the repository.
695
696 @param mode add mode (string one of 'normal' or 'large')
697 """
698 names = [os.path.join(self.dname, itm.text(self.__pathColumn))
699 for itm in self.__getUnversionedItems()]
700 if not names:
701 E5MessageBox.information(
702 self,
703 self.tr("Add"),
704 self.tr("""There are no unversioned entries"""
705 """ available/selected."""))
706 return
707
708 self.vcs.getExtensionObject("largefiles").hgAdd(
709 names, mode)
710 self.on_refreshButton_clicked()
711
712 project = e5App().getObject("Project")
713 for name in names:
714 project.getModel().updateVCSStatus(name)
715 self.vcs.checkVCSStatus()
716
717 def __forget(self):
718 """
719 Private slot to handle the Remove context menu entry.
720 """
721 names = [os.path.join(self.dname, itm.text(self.__pathColumn))
722 for itm in self.__getMissingItems()]
723 if not names:
724 E5MessageBox.information(
725 self,
726 self.tr("Remove"),
727 self.tr("""There are no missing entries"""
728 """ available/selected."""))
729 return
730
731 self.vcs.hgForget(names)
732 self.on_refreshButton_clicked()
733
734 def __revert(self):
735 """
736 Private slot to handle the Revert context menu entry.
737 """
738 names = [os.path.join(self.dname, itm.text(self.__pathColumn))
739 for itm in self.__getModifiedItems()]
740 if not names:
741 E5MessageBox.information(
742 self,
743 self.tr("Revert"),
744 self.tr("""There are no uncommitted changes"""
745 """ available/selected."""))
746 return
747
748 self.vcs.hgRevert(names)
749 self.raise_()
750 self.activateWindow()
751 self.on_refreshButton_clicked()
752
753 project = e5App().getObject("Project")
754 for name in names:
755 project.getModel().updateVCSStatus(name)
756 self.vcs.checkVCSStatus()
757
758 def __restoreMissing(self):
759 """
760 Private slot to handle the Restore Missing context menu entry.
761 """
762 names = [os.path.join(self.dname, itm.text(self.__pathColumn))
763 for itm in self.__getMissingItems()]
764 if not names:
765 E5MessageBox.information(
766 self,
767 self.tr("Revert"),
768 self.tr("""There are no missing entries"""
769 """ available/selected."""))
770 return
771
772 self.vcs.hgRevert(names)
773 self.on_refreshButton_clicked()
774 self.vcs.checkVCSStatus()
775
776 def __diff(self):
777 """
778 Private slot to handle the Diff context menu entry.
779 """
780 names = [os.path.join(self.dname, itm.text(self.__pathColumn))
781 for itm in self.__getModifiedItems()]
782 if not names:
783 E5MessageBox.information(
784 self,
785 self.tr("Differences"),
786 self.tr("""There are no uncommitted changes"""
787 """ available/selected."""))
788 return
789
790 if self.diff is None:
791 from .HgDiffDialog import HgDiffDialog
792 self.diff = HgDiffDialog(self.vcs)
793 self.diff.show()
794 self.diff.start(names, refreshable=True)
795
796 def __sbsDiff(self):
797 """
798 Private slot to handle the Diff context menu entry.
799 """
800 names = [os.path.join(self.dname, itm.text(self.__pathColumn))
801 for itm in self.__getModifiedItems()]
802 if not names:
803 E5MessageBox.information(
804 self,
805 self.tr("Side-by-Side Diff"),
806 self.tr("""There are no uncommitted changes"""
807 """ available/selected."""))
808 return
809 elif len(names) > 1:
810 E5MessageBox.information(
811 self,
812 self.tr("Side-by-Side Diff"),
813 self.tr("""Only one file with uncommitted changes"""
814 """ must be selected."""))
815 return
816
817 self.vcs.hgSbsDiff(names[0])
818
819 def __getCommitableItems(self):
820 """
821 Private method to retrieve all entries the user wants to commit.
822
823 @return list of all items, the user has checked
824 """
825 commitableItems = []
826 for index in range(self.statusList.topLevelItemCount()):
827 itm = self.statusList.topLevelItem(index)
828 if itm.checkState(self.__toBeCommittedColumn) == Qt.Checked:
829 commitableItems.append(itm)
830 return commitableItems
831
832 def __getCommitableUnselectedItems(self):
833 """
834 Private method to retrieve all entries the user may commit but hasn't
835 selected.
836
837 @return list of all items, the user has checked
838 """
839 items = []
840 for index in range(self.statusList.topLevelItemCount()):
841 itm = self.statusList.topLevelItem(index)
842 if itm.flags() & Qt.ItemIsUserCheckable and \
843 itm.checkState(self.__toBeCommittedColumn) == Qt.Unchecked:
844 items.append(itm)
845 return items
846
847 def __getModifiedItems(self):
848 """
849 Private method to retrieve all entries, that have a modified status.
850
851 @return list of all items with a modified status
852 """
853 modifiedItems = []
854 for itm in self.statusList.selectedItems():
855 if itm.text(self.__statusColumn) in self.modifiedIndicators:
856 modifiedItems.append(itm)
857 return modifiedItems
858
859 def __getUnversionedItems(self):
860 """
861 Private method to retrieve all entries, that have an unversioned
862 status.
863
864 @return list of all items with an unversioned status
865 """
866 unversionedItems = []
867 for itm in self.statusList.selectedItems():
868 if itm.text(self.__statusColumn) in self.unversionedIndicators:
869 unversionedItems.append(itm)
870 return unversionedItems
871
872 def __getMissingItems(self):
873 """
874 Private method to retrieve all entries, that have a missing status.
875
876 @return list of all items with a missing status
877 """
878 missingItems = []
879 for itm in self.statusList.selectedItems():
880 if itm.text(self.__statusColumn) in self.missingIndicators:
881 missingItems.append(itm)
882 return missingItems
883
884 def __commitSelect(self, selected):
885 """
886 Private slot to select or deselect all entries.
887
888 @param selected commit selection state to be set (boolean)
889 """
890 for index in range(self.statusList.topLevelItemCount()):
891 itm = self.statusList.topLevelItem(index)
892 if itm.flags() & Qt.ItemIsUserCheckable:
893 if selected:
894 itm.setCheckState(self.__toBeCommittedColumn, Qt.Checked)
895 else:
896 itm.setCheckState(self.__toBeCommittedColumn, Qt.Unchecked)
897
898 ###########################################################################
899 ## Diff handling methods below
900 ###########################################################################
901
902 def __generateDiffs(self):
903 """
904 Private slot to generate diff outputs for the selected item.
905 """
906 self.diffEdit.clear()
907 self.diffHighlighter.regenerateRules()
908
909 if not self.__mq:
910 selectedItems = self.statusList.selectedItems()
911 if len(selectedItems) == 1:
912 fn = os.path.join(self.dname,
913 selectedItems[0].text(self.__pathColumn))
914 self.__diffGenerator.start(fn)
915
916 def __generatorFinished(self):
917 """
918 Private slot connected to the finished signal of the diff generator.
919 """
920 diff = self.__diffGenerator.getResult()[0]
921
922 if diff:
923 for line in diff[:]:
924 if line.startswith("@@ "):
925 break
926 else:
927 diff.pop(0)
928 self.diffEdit.setPlainText("".join(diff))
929
930 tc = self.diffEdit.textCursor()
931 tc.movePosition(QTextCursor.Start)
932 self.diffEdit.setTextCursor(tc)
933 self.diffEdit.ensureCursorVisible()
934
935 def __refreshDiff(self):
936 """
937 Private method to refresh the diff output after a refresh.
938 """
939 if self.__selectedName and not self.__mq:
940 for index in range(self.statusList.topLevelItemCount()):
941 itm = self.statusList.topLevelItem(index)
942 if itm.text(self.__pathColumn) == self.__selectedName:
943 itm.setSelected(True)
944 break
945
946 self.__selectedName = ""

eric ide

mercurial