src/eric7/VCS/StatusWidget.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 8906
dd71fc3619b7
child 9221
bf71ee032bb4
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2021 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a VCS Status widget for the sidebar/toolbar.
8 """
9
10 import contextlib
11 import os
12
13 from PyQt6.QtCore import pyqtSlot, Qt, QEvent
14 from PyQt6.QtWidgets import (
15 QWidget, QVBoxLayout, QHBoxLayout, QLabel, QSizePolicy, QListView,
16 QListWidget, QListWidgetItem, QToolButton, QAbstractItemView, QMenu,
17 QGroupBox, QDialog
18 )
19
20 from EricWidgets.EricApplication import ericApp
21 from EricWidgets import EricMessageBox
22 from EricWidgets.EricSpellCheckedTextEdit import EricSpellCheckedTextEdit
23 from EricWidgets.EricListSelectionDialog import EricListSelectionDialog
24
25 import Preferences
26 import UI.PixmapCache
27 import Utilities
28
29
30 class StatusWidget(QWidget):
31 """
32 Class implementing a VCS Status widget for the sidebar/toolbox.
33 """
34 StatusDataRole = Qt.ItemDataRole.UserRole + 1
35
36 def __init__(self, project, viewmanager, parent=None):
37 """
38 Constructor
39
40 @param project reference to the project object
41 @type Project
42 @param viewmanager reference to the viewmanager object
43 @type ViewManager
44 @param parent reference to the parent widget (defaults to None)
45 @type QWidget (optional)
46 """
47 super().__init__(parent)
48 self.setObjectName("VcsStatusWidget")
49
50 self.__project = project
51 self.__vm = viewmanager
52
53 self.__layout = QVBoxLayout()
54 self.__layout.setObjectName("MainLayout")
55 self.__layout.setContentsMargins(0, 3, 0, 0)
56 self.__topLayout = QHBoxLayout()
57 self.__topLayout.setObjectName("topLayout")
58
59 # Create the top area
60 self.__infoLabel = QLabel(self)
61 self.__infoLabel.setSizePolicy(
62 QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred)
63 self.__topLayout.addWidget(self.__infoLabel)
64
65 self.__commitToggleButton = QToolButton(self)
66 self.__commitToggleButton.setIcon(UI.PixmapCache.getIcon("check"))
67 self.__commitToggleButton.setToolTip(
68 self.tr("Press to toggle the commit markers"))
69 self.__commitToggleButton.clicked.connect(self.__toggleCheckMark)
70 self.__topLayout.addWidget(self.__commitToggleButton)
71
72 self.__commitButton = QToolButton(self)
73 self.__commitButton.setIcon(UI.PixmapCache.getIcon("vcsCommit"))
74 self.__commitButton.setToolTip(
75 self.tr("Press to commit the marked entries with options"))
76 self.__commitButton.clicked.connect(self.__commit)
77 self.__topLayout.addWidget(self.__commitButton)
78
79 self.__addButton = QToolButton(self)
80 self.__addButton.setIcon(UI.PixmapCache.getIcon("vcsAdd"))
81 self.__addButton.setToolTip(
82 self.tr("Press to add the selected, untracked entries"))
83 self.__addButton.clicked.connect(self.__addUntracked)
84 self.__topLayout.addWidget(self.__addButton)
85
86 self.__reloadButton = QToolButton(self)
87 self.__reloadButton.setIcon(UI.PixmapCache.getIcon("reload"))
88 self.__reloadButton.setToolTip(
89 self.tr("Press to reload the status list"))
90 self.__reloadButton.clicked.connect(self.__reload)
91 self.__topLayout.addWidget(self.__reloadButton)
92
93 self.__actionsButton = QToolButton(self)
94 self.__actionsButton.setIcon(
95 UI.PixmapCache.getIcon("actionsToolButton"))
96 self.__actionsButton.setToolTip(
97 self.tr("Select action from menu"))
98 self.__actionsButton.setPopupMode(
99 QToolButton.ToolButtonPopupMode.InstantPopup)
100 self.__topLayout.addWidget(self.__actionsButton)
101
102 self.__layout.addLayout(self.__topLayout)
103 ###################################################################
104
105 # Create the middle part
106 self.__statusList = QListWidget(self)
107 self.__statusList.setAlternatingRowColors(True)
108 self.__statusList.setSortingEnabled(True)
109 self.__statusList.setViewMode(QListView.ViewMode.ListMode)
110 self.__statusList.setTextElideMode(Qt.TextElideMode.ElideLeft)
111 self.__statusList.setSelectionMode(
112 QAbstractItemView.SelectionMode.ExtendedSelection)
113 self.__statusList.itemSelectionChanged.connect(
114 self.__updateEnabledStates)
115 self.__statusList.itemDoubleClicked.connect(self.__itemDoubleClicked)
116 self.__statusList.itemChanged.connect(self.__updateEnabledStates)
117 self.__layout.addWidget(self.__statusList)
118 ###################################################################
119
120 # create the Quick Commit area
121 self.__quickCommitGroup = QGroupBox(self.tr("Quick Commit"), self)
122 self.__quickCommitLayout = QVBoxLayout()
123 self.__quickCommitEdit = EricSpellCheckedTextEdit(self)
124 self.__quickCommitEdit.setSizePolicy(
125 QSizePolicy.Policy.Expanding,
126 QSizePolicy.Policy.Preferred)
127 self.__quickCommitEdit.setMaximumHeight(100)
128 self.__quickCommitEdit.setTabChangesFocus(True)
129 self.__quickCommitEdit.installEventFilter(self)
130 self.__quickCommitEdit.textChanged.connect(
131 self.__quickCommitEditTextChanged)
132 self.__quickCommitLayout.addWidget(self.__quickCommitEdit)
133
134 self.__quickCommitLayout2 = QHBoxLayout()
135 self.__quickCommitLayout2.addStretch()
136
137 self.__quickCommitHistoryButton = QToolButton(self)
138 self.__quickCommitHistoryButton.setIcon(
139 UI.PixmapCache.getIcon("history"))
140 self.__quickCommitHistoryButton.setToolTip(
141 self.tr("Select commit message from previous commits"))
142 self.__quickCommitHistoryButton.clicked.connect(
143 self.__selectQuickCommitMessage)
144 self.__quickCommitLayout2.addWidget(self.__quickCommitHistoryButton)
145
146 self.__quickCommitHistoryClearButton = QToolButton(self)
147 self.__quickCommitHistoryClearButton.setIcon(
148 UI.PixmapCache.getIcon("historyClear"))
149 self.__quickCommitHistoryClearButton.setToolTip(
150 self.tr("Clear the list of saved commit messages"))
151 self.__quickCommitHistoryClearButton.clicked.connect(
152 self.__clearCommitMessages)
153 self.__quickCommitLayout2.addWidget(
154 self.__quickCommitHistoryClearButton)
155
156 self.__quickCommitButton = QToolButton(self)
157 self.__quickCommitButton.setIcon(
158 UI.PixmapCache.getIcon("vcsCommit"))
159 self.__quickCommitButton.setToolTip(
160 self.tr("Press to commit the marked entries"))
161 self.__quickCommitButton.clicked.connect(self.__quickCommit)
162 self.__quickCommitLayout2.addWidget(self.__quickCommitButton)
163
164 self.__quickCommitLayout.addLayout(self.__quickCommitLayout2)
165 self.__quickCommitGroup.setLayout(self.__quickCommitLayout)
166 self.__layout.addWidget(self.__quickCommitGroup)
167 ###################################################################
168
169 self.setLayout(self.__layout)
170
171 self.__statusIcons = {
172 "A": "vcs-added", # added
173 "M": "vcs-modified", # modified
174 "O": "vcs-removed", # removed
175 "R": "vcs-renamed", # renamed
176 "U": "vcs-update-required", # update needed
177 "Z": "vcs-conflicting", # conflict
178 "?": "vcs-untracked", # not tracked
179 "!": "vcs-missing", # missing
180 }
181 self.__statusTexts = {
182 "A": self.tr("added"),
183 "M": self.tr("modified"),
184 "O": self.tr("removed"),
185 "R": self.tr("renamed"),
186 "U": self.tr("needs update"),
187 "Z": self.tr("conflict"),
188 "?": self.tr("not tracked"),
189 "!": self.tr("missing"),
190 }
191
192 self.__initActionsMenu()
193
194 self.__reset()
195
196 if self.__project.isOpen():
197 self.__projectOpened()
198 else:
199 self.__projectClosed()
200
201 self.__addedItemsText = []
202
203 self.__project.projectOpened.connect(self.__projectOpened)
204 self.__project.projectClosed.connect(self.__projectClosed)
205 self.__project.projectPropertiesChanged.connect(
206 self.__setProjectSpellCheckData)
207 self.__project.vcsCommitted.connect(self.__committed)
208 self.__project.vcsStatusMonitorInfo.connect(self.__setInfoText)
209 self.__project.vcsStatusMonitorAllData.connect(
210 self.__processStatusData)
211
212 def __initActionsMenu(self):
213 """
214 Private method to initialize the actions menu.
215 """
216 self.__actionsMenu = QMenu()
217 self.__actionsMenu.setToolTipsVisible(True)
218 self.__actionsMenu.aboutToShow.connect(self.__showActionsMenu)
219
220 self.__commitAct = self.__actionsMenu.addAction(
221 UI.PixmapCache.getIcon("vcsCommit"),
222 self.tr("Commit"), self.__commit)
223 self.__commitAct.setToolTip(self.tr(
224 "Commit the marked entries with options"))
225 self.__commitSelectAct = self.__actionsMenu.addAction(
226 self.tr("Select all for commit"), self.__commitSelectAll)
227 self.__commitDeselectAct = self.__actionsMenu.addAction(
228 self.tr("Unselect all from commit"), self.__commitDeselectAll)
229
230 self.__actionsMenu.addSeparator()
231
232 self.__addAct = self.__actionsMenu.addAction(
233 UI.PixmapCache.getIcon("vcsAdd"),
234 self.tr("Add"), self.__addUntracked)
235 self.__addAct.setToolTip(self.tr(
236 "Add the selected, untracked entries"))
237 self.__addAllAct = self.__actionsMenu.addAction(
238 self.tr("Add All"), self.__addAllUntracked)
239 self.__addAllAct.setToolTip(self.tr(
240 "Add all untracked entries"))
241
242 self.__actionsMenu.addSeparator()
243
244 self.__diffAct = self.__actionsMenu.addAction(
245 UI.PixmapCache.getIcon("vcsDiff"),
246 self.tr("Differences"), self.__diff)
247 self.__diffAct.setToolTip(self.tr(
248 "Shows the differences of the selected entry in a"
249 " separate dialog"))
250 self.__sbsDiffAct = self.__actionsMenu.addAction(
251 UI.PixmapCache.getIcon("vcsSbsDiff"),
252 self.tr("Differences Side-By-Side"), self.__sbsDiff)
253 self.__sbsDiffAct.setToolTip(self.tr(
254 "Shows the differences of the selected entry side-by-side in"
255 " a separate dialog"))
256 self.__diffAllAct = self.__actionsMenu.addAction(
257 self.tr("All Differences"), self.__diffAll)
258 self.__diffAllAct.setToolTip(self.tr(
259 "Shows the differences of all entries in a separate dialog"))
260
261 self.__actionsMenu.addSeparator()
262
263 self.__revertAct = self.__actionsMenu.addAction(
264 UI.PixmapCache.getIcon("vcsRevert"),
265 self.tr("Revert"), self.__revert)
266 self.__revertAct.setToolTip(self.tr(
267 "Reverts the changes of the selected files"))
268
269 self.__actionsMenu.addSeparator()
270
271 self.__forgetAct = self.__actionsMenu.addAction(
272 self.tr("Forget Missing"), self.__forgetMissing)
273 self.__forgetAct.setToolTip(self.tr(
274 "Forgets about the selected missing files"))
275 self.__restoreAct = self.__actionsMenu.addAction(
276 self.tr("Restore Missing"), self.__restoreMissing)
277 self.__restoreAct.setToolTip(self.tr(
278 "Restores the selected missing files"))
279 self.__actionsMenu.addSeparator()
280
281 self.__editAct = self.__actionsMenu.addAction(
282 UI.PixmapCache.getIcon("open"),
283 self.tr("Edit Conflict"), self.__editConflict)
284 self.__editAct.setToolTip(self.tr(
285 "Edit the selected conflicting file"))
286 self.__resolvedAct = self.__actionsMenu.addAction(
287 UI.PixmapCache.getIcon("vcsResolved"),
288 self.tr("Conflict Resolved"), self.__conflictResolved)
289 self.__resolvedAct.setToolTip(self.tr(
290 "Mark the selected conflicting file as resolved"))
291
292 self.__actionsButton.setMenu(self.__actionsMenu)
293
294 @pyqtSlot()
295 def __projectOpened(self):
296 """
297 Private slot to handle the opening of a project.
298 """
299 self.__reloadButton.setEnabled(True)
300 self.__setProjectSpellCheckData()
301
302 @pyqtSlot()
303 def __setProjectSpellCheckData(self):
304 """
305 Private slot to set the spell check properties of the
306 quick commit area.
307 """
308 pwl, pel = self.__project.getProjectDictionaries()
309 language = self.__project.getProjectSpellLanguage()
310 self.__quickCommitEdit.setLanguageWithPWL(
311 language, pwl or None, pel or None)
312
313 @pyqtSlot()
314 def __projectClosed(self):
315 """
316 Private slot to handle the closing of a project.
317 """
318 self.__infoLabel.setText(self.tr("No project open."))
319
320 self.__reloadButton.setEnabled(False)
321
322 self.__reset()
323
324 @pyqtSlot(str)
325 def __setInfoText(self, info):
326 """
327 Private slot to set the info label text.
328
329 @param info text to be shown
330 @type str
331 """
332 self.__infoLabel.setText(info)
333
334 @pyqtSlot()
335 def __reload(self):
336 """
337 Private slot to reload the status list.
338 """
339 self.__project.checkVCSStatus()
340
341 def __reset(self):
342 """
343 Private method to reset the widget to default.
344 """
345 self.__statusList.clear()
346
347 self.__commitToggleButton.setEnabled(False)
348 self.__commitButton.setEnabled(False)
349 self.__addButton.setEnabled(False)
350
351 self.__quickCommitEdit.clear()
352 self.__quickCommitGroup.setEnabled(False)
353
354 def __updateEnabledStates(self):
355 """
356 Private method to set the enabled states depending on the list state.
357 """
358 modified = len(self.__getModifiedItems())
359 unversioned = len(self.__getSelectedUnversionedItems())
360 commitable = len(self.__getCommitableItems())
361
362 self.__commitToggleButton.setEnabled(modified)
363 self.__commitButton.setEnabled(commitable)
364 self.__addButton.setEnabled(unversioned)
365
366 self.__quickCommitGroup.setEnabled(commitable)
367
368 @pyqtSlot(dict)
369 def __processStatusData(self, data):
370 """
371 Private slot to process the status data emitted by the project.
372
373 Each entry of the status data consists of a status flag and and the
374 path relative to the project directory starting with the third column.
375 The known status flags are:
376 <ul>
377 <li>"A" path was added but not yet committed</li>
378 <li>"M" path has local changes</li>
379 <li>"O" path was removed</li>
380 <li>"R" path was deleted and then re-added</li>
381 <li>"U" path needs an update</li>
382 <li>"Z" path contains a conflict</li>
383 <li>"?" path is not tracked</li>
384 <li>"!" path is missing</li>
385 <li>" " path is back at normal</li>
386 </ul>
387
388 @param data dictionary containing the status data
389 @type dict
390 """
391 # step 1: remember all currently checked entries
392 checkedEntries = [itm.text() for itm in self.__getCommitableItems()]
393 selectedEntries = [itm.text()
394 for itm in self.__statusList.selectedItems()]
395 knownEntries = [self.__statusList.item(row).text()
396 for row in range(self.__statusList.count())]
397
398 # step 2: clear the list and re-populate it with new data
399 self.__statusList.clear()
400
401 block = self.__statusList.blockSignals(True)
402 for name, status in data.items():
403 if status:
404 itm = QListWidgetItem(name, self.__statusList)
405 with contextlib.suppress(KeyError):
406 itm.setToolTip(self.__statusTexts[status])
407 itm.setIcon(UI.PixmapCache.getIcon(
408 self.__statusIcons[status]))
409 itm.setData(self.StatusDataRole, status)
410 if status in "AMOR":
411 itm.setFlags(
412 itm.flags() | Qt.ItemFlag.ItemIsUserCheckable)
413 if (
414 name in checkedEntries or
415 name not in knownEntries or
416 name in self.__addedItemsText
417 ):
418 itm.setCheckState(Qt.CheckState.Checked)
419 else:
420 itm.setCheckState(Qt.CheckState.Unchecked)
421 else:
422 itm.setFlags(
423 itm.flags() & ~Qt.ItemFlag.ItemIsUserCheckable)
424 itm.setSelected(name in selectedEntries)
425
426 self.__statusList.sortItems(Qt.SortOrder.AscendingOrder)
427 self.__statusList.blockSignals(block)
428
429 self.__updateEnabledStates()
430
431 @pyqtSlot()
432 def __toggleCheckMark(self):
433 """
434 Private slot to toggle the check marks.
435 """
436 itemList = (
437 self.__statusList.selectedItems()
438 if len(self.__statusList.selectedItems()) else
439 [self.__statusList.item(row)
440 for row in range(self.__statusList.count())]
441 )
442 for itm in itemList:
443 if (
444 itm.flags() & Qt.ItemFlag.ItemIsUserCheckable ==
445 Qt.ItemFlag.ItemIsUserCheckable
446 ):
447 if itm.checkState() == Qt.CheckState.Unchecked:
448 itm.setCheckState(Qt.CheckState.Checked)
449 else:
450 itm.setCheckState(Qt.CheckState.Unchecked)
451
452 def __setCheckMark(self, checked):
453 """
454 Private method to set or unset all check marks.
455
456 @param checked check mark state to be set
457 @type bool
458 """
459 for row in range(self.__statusList.count()):
460 itm = self.__statusList.item(row)
461 if (
462 itm.flags() & Qt.ItemFlag.ItemIsUserCheckable ==
463 Qt.ItemFlag.ItemIsUserCheckable
464 ):
465 if checked:
466 itm.setCheckState(Qt.CheckState.Checked)
467 else:
468 itm.setCheckState(Qt.CheckState.Unchecked)
469
470 @pyqtSlot()
471 def __commit(self):
472 """
473 Private slot to handle the commit button.
474 """
475 projectPath = self.__project.getProjectPath()
476 names = []
477
478 for row in range(self.__statusList.count()):
479 itm = self.__statusList.item(row)
480 if itm.checkState() == Qt.CheckState.Checked:
481 names.append(os.path.join(projectPath, itm.text()))
482
483 if not names:
484 EricMessageBox.information(
485 self,
486 self.tr("Commit"),
487 self.tr("""There are no entries selected to be"""
488 """ committed."""))
489 return
490
491 if Preferences.getVCS("AutoSaveFiles"):
492 vm = ericApp().getObject("ViewManager")
493 for name in names:
494 vm.saveEditor(name)
495 vcs = self.__project.getVcs()
496 vcs and vcs.vcsCommit(names, '')
497
498 @pyqtSlot()
499 def __committed(self):
500 """
501 Private slot called after the commit has been completed.
502 """
503 self.__reload()
504
505 @pyqtSlot()
506 def __commitSelectAll(self):
507 """
508 Private slot to select all entries for commit.
509 """
510 self.__setCheckMark(True)
511
512 @pyqtSlot()
513 def __commitDeselectAll(self):
514 """
515 Private slot to deselect all entries from commit.
516 """
517 self.__setCheckMark(False)
518
519 @pyqtSlot()
520 def __addUntracked(self, allItems=False):
521 """
522 Private slot to add the selected untracked entries.
523
524 @param allItems flag indicating to show the differences of all files
525 (defaults to False)
526 @type bool (optional)
527 """
528 projectPath = self.__project.getProjectPath()
529
530 names = [
531 os.path.join(projectPath, itm.text())
532 for itm in self.__getUnversionedItems()
533 ] if allItems else [
534 os.path.join(projectPath, itm.text())
535 for itm in self.__getSelectedUnversionedItems()
536 ]
537
538 if not names:
539 EricMessageBox.information(
540 self,
541 self.tr("Add"),
542 self.tr("""There are no unversioned entries"""
543 """ available/selected."""))
544 return
545
546 self.__addedItemsText = [
547 itm.text() for itm in self.__getUnversionedItems()
548 ] if allItems else [
549 itm.text() for itm in self.__getSelectedUnversionedItems()
550 ]
551
552 vcs = self.__project.getVcs()
553 vcs and vcs.vcsAdd(names)
554 self.__reload()
555
556 @pyqtSlot(QListWidgetItem)
557 def __itemDoubleClicked(self, itm):
558 """
559 Private slot to handle double clicking an item.
560
561 @param itm reference to the double clicked item
562 @type QListWidgetItem
563 """
564 projectPath = self.__project.getProjectPath()
565
566 if itm.data(self.StatusDataRole) in "MZ":
567 # modified and conflicting items
568 name = os.path.join(projectPath, itm.text())
569 vcs = self.__project.getVcs()
570 vcs and vcs.vcsDiff(name)
571
572 ###########################################################################
573 ## Menu handling methods
574 ###########################################################################
575
576 def __showActionsMenu(self):
577 """
578 Private slot to prepare the actions button menu before it is shown.
579 """
580 modified = len(self.__getSelectedModifiedItems())
581 allModified = len(self.__getModifiedItems())
582 unversioned = len(self.__getSelectedUnversionedItems())
583 allUnversioned = len(self.__getUnversionedItems())
584 missing = len(self.__getMissingItems())
585 commitable = len(self.__getCommitableItems())
586 commitableUnselected = len(self.__getCommitableUnselectedItems())
587 conflicting = len(self.__getSelectedConflictingItems())
588
589 self.__addAct.setEnabled(unversioned)
590 self.__addAllAct.setEnabled(allUnversioned)
591 self.__diffAct.setEnabled(modified)
592 self.__sbsDiffAct.setEnabled(modified == 1)
593 self.__diffAllAct.setEnabled(allModified)
594 self.__revertAct.setEnabled(modified)
595 self.__forgetAct.setEnabled(missing)
596 self.__restoreAct.setEnabled(missing)
597 self.__commitAct.setEnabled(commitable)
598 self.__commitSelectAct.setEnabled(commitableUnselected)
599 self.__commitDeselectAct.setEnabled(commitable)
600 self.__editAct.setEnabled(conflicting == 1)
601 self.__resolvedAct.setEnabled(conflicting)
602
603 def __getCommitableItems(self):
604 """
605 Private method to retrieve all entries the user wants to commit.
606
607 @return list of all items, the user has checked
608 @rtype list of QListWidgetItem
609 """
610 commitableItems = []
611 for row in range(self.__statusList.count()):
612 itm = self.__statusList.item(row)
613 if (
614 itm.checkState() == Qt.CheckState.Checked
615 ):
616 commitableItems.append(itm)
617 return commitableItems
618
619 def __getCommitableUnselectedItems(self):
620 """
621 Private method to retrieve all entries the user may commit but hasn't
622 selected.
623
624 @return list of all items, the user has checked
625 @rtype list of QListWidgetItem
626 """
627 items = []
628 for row in range(self.__statusList.count()):
629 itm = self.__statusList.item(row)
630 if (
631 (itm.flags() & Qt.ItemFlag.ItemIsUserCheckable ==
632 Qt.ItemFlag.ItemIsUserCheckable) and
633 itm.checkState() == Qt.CheckState.Unchecked
634 ):
635 items.append(itm)
636 return items
637
638 def __getModifiedItems(self):
639 """
640 Private method to retrieve all entries, that have a modified status.
641
642 @return list of all items with a modified status
643 @rtype list of QListWidgetItem
644 """
645 items = []
646 for row in range(self.__statusList.count()):
647 itm = self.__statusList.item(row)
648 if itm.data(self.StatusDataRole) in "AMOR":
649 items.append(itm)
650 return items
651
652 def __getSelectedModifiedItems(self):
653 """
654 Private method to retrieve all selected entries, that have a modified
655 status.
656
657 @return list of all selected entries with a modified status
658 @rtype list of QListWidgetItem
659 """
660 return [itm for itm in self.__statusList.selectedItems()
661 if itm.data(self.StatusDataRole) in "AMOR"]
662
663 def __getUnversionedItems(self):
664 """
665 Private method to retrieve all entries, that have an unversioned
666 status.
667
668 @return list of all items with an unversioned status
669 @rtype list of QListWidgetItem
670 """
671 items = []
672 for row in range(self.__statusList.count()):
673 itm = self.__statusList.item(row)
674 if itm.data(self.StatusDataRole) == "?":
675 items.append(itm)
676 return items
677
678 def __getSelectedUnversionedItems(self):
679 """
680 Private method to retrieve all selected entries, that have an
681 unversioned status.
682
683 @return list of all items with an unversioned status
684 @rtype list of QListWidgetItem
685 """
686 return [itm for itm in self.__statusList.selectedItems()
687 if itm.data(self.StatusDataRole) == "?"]
688
689 def __getMissingItems(self):
690 """
691 Private method to retrieve all entries, that have a missing status.
692
693 @return list of all items with a missing status
694 @rtype list of QListWidgetItem
695 """
696 return [itm for itm in self.__statusList.selectedItems()
697 if itm.data(self.StatusDataRole) == "!"]
698
699 def __getSelectedConflictingItems(self):
700 """
701 Private method to retrieve all selected entries, that have a conflict
702 status.
703
704 @return list of all selected entries with a conflict status
705 @rtype list of QListWidgetItem
706 """
707 return [itm for itm in self.__statusList.selectedItems()
708 if itm.data(self.StatusDataRole) == "Z"]
709
710 @pyqtSlot()
711 def __addAllUntracked(self):
712 """
713 Private slot to handle the Add All action menu entry.
714 """
715 self.__addUntracked(allItems=True)
716
717 @pyqtSlot()
718 def __diff(self, allItems=False):
719 """
720 Private slot to handle the Differences action menu entry.
721
722 @param allItems flag indicating to show the differences of all files
723 (defaults to False)
724 @type bool (optional)
725 """
726 projectPath = self.__project.getProjectPath()
727
728 names = [
729 os.path.join(projectPath, itm.text())
730 for itm in self.__getModifiedItems()
731 ] if allItems else [
732 os.path.join(projectPath, itm.text())
733 for itm in self.__getSelectedModifiedItems()
734 ]
735 if not names:
736 EricMessageBox.information(
737 self,
738 self.tr("Differences"),
739 self.tr("""There are no uncommitted changes"""
740 """ available/selected."""))
741 return
742
743 vcs = self.__project.getVcs()
744 vcs and vcs.vcsDiff(names)
745
746 @pyqtSlot()
747 def __diffAll(self):
748 """
749 Private slot to handle the All Differences action menu entry.
750 """
751 self.__diff(allItems=True)
752
753 @pyqtSlot()
754 def __sbsDiff(self):
755 """
756 Private slot to handle the Side-By-Side Differences action menu entry.
757 """
758 projectPath = self.__project.getProjectPath()
759
760 names = [os.path.join(projectPath, itm.text())
761 for itm in self.__getSelectedModifiedItems()]
762 if not names:
763 EricMessageBox.information(
764 self,
765 self.tr("Differences Side-By-Side"),
766 self.tr("""There are no uncommitted changes"""
767 """ available/selected."""))
768 return
769 elif len(names) > 1:
770 EricMessageBox.information(
771 self,
772 self.tr("Differences Side-By-Side"),
773 self.tr("""Only one file with uncommitted changes"""
774 """ must be selected."""))
775 return
776
777 vcs = self.__project.getVcs()
778 vcs and vcs.vcsSbsDiff(names[0])
779
780 @pyqtSlot()
781 def __revert(self):
782 """
783 Private slot to handle the Revert action menu entry.
784 """
785 projectPath = self.__project.getProjectPath()
786
787 names = [os.path.join(projectPath, itm.text())
788 for itm in self.__getSelectedModifiedItems()]
789 if not names:
790 EricMessageBox.information(
791 self,
792 self.tr("Revert"),
793 self.tr("""There are no uncommitted changes"""
794 """ available/selected."""))
795 return
796
797 vcs = self.__project.getVcs()
798 vcs and vcs.vcsRevert(names)
799 self.__reload()
800
801 @pyqtSlot()
802 def __forgetMissing(self):
803 """
804 Private slot to handle the Forget action menu entry.
805 """
806 projectPath = self.__project.getProjectPath()
807
808 names = [os.path.join(projectPath, itm.text())
809 for itm in self.__getMissingItems()]
810 if not names:
811 EricMessageBox.information(
812 self,
813 self.tr("Forget Missing"),
814 self.tr("""There are no missing entries"""
815 """ available/selected."""))
816 return
817
818 vcs = self.__project.getVcs()
819 vcs and vcs.vcsForget(names)
820 self.__reload()
821
822 @pyqtSlot()
823 def __restoreMissing(self):
824 """
825 Private slot to handle the Restore Missing context menu entry.
826 """
827 projectPath = self.__project.getProjectPath()
828
829 names = [os.path.join(projectPath, itm.text())
830 for itm in self.__getMissingItems()]
831 if not names:
832 EricMessageBox.information(
833 self,
834 self.tr("Restore Missing"),
835 self.tr("""There are no missing entries"""
836 """ available/selected."""))
837 return
838
839 vcs = self.__project.getVcs()
840 vcs and vcs.vcsRevert(names)
841 self.__reload()
842
843 @pyqtSlot()
844 def __editConflict(self):
845 """
846 Private slot to handle the Edit Conflict action menu entry.
847 """
848 projectPath = self.__project.getProjectPath()
849
850 itm = self.__getSelectedConflictingItems()[0]
851 filename = os.path.join(projectPath, itm.text())
852 if Utilities.MimeTypes.isTextFile(filename):
853 self.__vm.getEditor(filename)
854
855 @pyqtSlot()
856 def __conflictResolved(self):
857 """
858 Private slot to handle the Conflict Resolved action menu entry.
859 """
860 projectPath = self.__project.getProjectPath()
861
862 names = [os.path.join(projectPath, itm.text())
863 for itm in self.__getSelectedConflictingItems()]
864 if not names:
865 EricMessageBox.information(
866 self,
867 self.tr("Conflict Resolved"),
868 self.tr("""There are no conflicting entries"""
869 """ available/selected."""))
870 return
871
872 vcs = self.__project.getVcs()
873 vcs and vcs.vcsResolved(names)
874 self.__reload()
875
876 #######################################################################
877 ## Quick Commit handling methods
878 #######################################################################
879
880 @pyqtSlot()
881 def __selectQuickCommitMessage(self):
882 """
883 Private slot to select a commit message from the list of
884 saved messages.
885 """
886 vcs = self.__project.getVcs()
887 if vcs:
888 commitMessages = vcs.vcsCommitMessages()
889 dlg = EricListSelectionDialog(
890 commitMessages,
891 selectionMode=QAbstractItemView.SelectionMode.SingleSelection,
892 title=self.tr("Quick Commit"),
893 message=self.tr("Select your commit message:"),
894 doubleClickOk=True,
895 parent=self
896 )
897 if dlg.exec() == QDialog.DialogCode.Accepted:
898 selection = dlg.getSelection()
899 if selection:
900 self.__quickCommitEdit.setPlainText(selection[0])
901
902 @pyqtSlot()
903 def __clearCommitMessages(self):
904 """
905 Private slot to clear the list of saved commit messages.
906 """
907 vcs = self.__project.getVcs()
908 vcs and vcs.vcsClearCommitMessages()
909
910 @pyqtSlot()
911 def __quickCommit(self):
912 """
913 Private slot to commit all marked entries with the entered
914 commit message.
915 """
916 projectPath = self.__project.getProjectPath()
917 names = []
918
919 for row in range(self.__statusList.count()):
920 itm = self.__statusList.item(row)
921 if itm.checkState() == Qt.CheckState.Checked:
922 names.append(os.path.join(projectPath, itm.text()))
923
924 if not names:
925 EricMessageBox.information(
926 self,
927 self.tr("Commit"),
928 self.tr("""There are no entries selected to be"""
929 """ committed."""))
930 return
931
932 if Preferences.getVCS("AutoSaveFiles"):
933 vm = ericApp().getObject("ViewManager")
934 for name in names:
935 vm.saveEditor(name)
936
937 commitMessage = self.__quickCommitEdit.toPlainText()
938 vcs = self.__project.getVcs()
939 if vcs:
940 vcs.vcsCommit(names, commitMessage, noDialog=True)
941 vcs.vcsAddCommitMessage(commitMessage)
942 self.__quickCommitEdit.clear()
943
944 @pyqtSlot()
945 def __quickCommitEditTextChanged(self):
946 """
947 Private slot to react upon changes of the quick commit text.
948 """
949 self.__quickCommitButton.setEnabled(bool(
950 self.__quickCommitEdit.toPlainText()))
951
952 def eventFilter(self, obj, evt):
953 """
954 Public method to process some events for the Commit edit.
955
956 @param obj reference to the object the event was meant for
957 @type QObject
958 @param evt reference to the event object
959 @type QEvent
960 @return flag to indicate that the event was handled
961 @rtype bool
962 """
963 if (
964 obj is self.__quickCommitEdit and
965 evt.type() == QEvent.Type.KeyPress and
966 evt.key() in (Qt.Key.Key_Return, Qt.Key.Key_Enter) and
967 evt.modifiers() == Qt.KeyboardModifier.ControlModifier
968 ):
969 # Ctrl-Enter or Ctrl-Return => commit
970 self.__quickCommitButton.animateClick()
971 return True
972 else:
973 # standard event processing
974 return super().eventFilter(obj, evt)

eric ide

mercurial