|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2014 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a dialog to show a list of files which had or still have |
|
8 conflicts. |
|
9 """ |
|
10 |
|
11 from PyQt5.QtCore import pyqtSlot, Qt, QPoint |
|
12 from PyQt5.QtWidgets import ( |
|
13 QAbstractButton, QDialogButtonBox, QHeaderView, QTreeWidgetItem, |
|
14 QApplication, QWidget |
|
15 ) |
|
16 |
|
17 from E5Gui.E5Application import e5App |
|
18 |
|
19 from .Ui_HgConflictsListDialog import Ui_HgConflictsListDialog |
|
20 |
|
21 import Utilities.MimeTypes |
|
22 |
|
23 |
|
24 class HgConflictsListDialog(QWidget, Ui_HgConflictsListDialog): |
|
25 """ |
|
26 Class implementing a dialog to show a list of files which had or still |
|
27 have conflicts. |
|
28 """ |
|
29 StatusRole = Qt.ItemDataRole.UserRole + 1 |
|
30 FilenameRole = Qt.ItemDataRole.UserRole + 2 |
|
31 |
|
32 def __init__(self, vcs, parent=None): |
|
33 """ |
|
34 Constructor |
|
35 |
|
36 @param vcs reference to the vcs object |
|
37 @param parent parent widget (QWidget) |
|
38 """ |
|
39 super().__init__(parent) |
|
40 self.setupUi(self) |
|
41 |
|
42 self.__position = QPoint() |
|
43 |
|
44 self.buttonBox.button( |
|
45 QDialogButtonBox.StandardButton.Close).setEnabled(False) |
|
46 self.buttonBox.button( |
|
47 QDialogButtonBox.StandardButton.Cancel).setDefault(True) |
|
48 |
|
49 self.conflictsList.headerItem().setText( |
|
50 self.conflictsList.columnCount(), "") |
|
51 self.conflictsList.header().setSortIndicator( |
|
52 0, Qt.SortOrder.AscendingOrder) |
|
53 |
|
54 self.refreshButton = self.buttonBox.addButton( |
|
55 self.tr("&Refresh"), QDialogButtonBox.ButtonRole.ActionRole) |
|
56 self.refreshButton.setToolTip( |
|
57 self.tr("Press to refresh the list of conflicts")) |
|
58 self.refreshButton.setEnabled(False) |
|
59 |
|
60 self.vcs = vcs |
|
61 self.project = e5App().getObject("Project") |
|
62 |
|
63 self.__hgClient = vcs.getClient() |
|
64 |
|
65 def closeEvent(self, e): |
|
66 """ |
|
67 Protected slot implementing a close event handler. |
|
68 |
|
69 @param e close event (QCloseEvent) |
|
70 """ |
|
71 if self.__hgClient.isExecuting(): |
|
72 self.__hgClient.cancel() |
|
73 |
|
74 self.__position = self.pos() |
|
75 |
|
76 e.accept() |
|
77 |
|
78 def show(self): |
|
79 """ |
|
80 Public slot to show the dialog. |
|
81 """ |
|
82 if not self.__position.isNull(): |
|
83 self.move(self.__position) |
|
84 |
|
85 super().show() |
|
86 |
|
87 def start(self): |
|
88 """ |
|
89 Public slot to start the tags command. |
|
90 """ |
|
91 self.errorGroup.hide() |
|
92 QApplication.processEvents() |
|
93 |
|
94 self.intercept = False |
|
95 |
|
96 self.activateWindow() |
|
97 self.raise_() |
|
98 |
|
99 self.conflictsList.clear() |
|
100 self.__started = True |
|
101 self.__getEntries() |
|
102 |
|
103 def __getEntries(self): |
|
104 """ |
|
105 Private method to get the conflict entries. |
|
106 """ |
|
107 args = self.vcs.initCommand("resolve") |
|
108 args.append('--list') |
|
109 |
|
110 out, err = self.__hgClient.runcommand(args) |
|
111 if err: |
|
112 self.__showError(err) |
|
113 if out: |
|
114 for line in out.splitlines(): |
|
115 self.__processOutputLine(line) |
|
116 if self.__hgClient.wasCanceled(): |
|
117 break |
|
118 self.__finish() |
|
119 |
|
120 def __finish(self): |
|
121 """ |
|
122 Private slot called when the process finished or the user pressed |
|
123 the button. |
|
124 """ |
|
125 self.buttonBox.button( |
|
126 QDialogButtonBox.StandardButton.Close).setEnabled(True) |
|
127 self.buttonBox.button( |
|
128 QDialogButtonBox.StandardButton.Cancel).setEnabled(False) |
|
129 self.buttonBox.button( |
|
130 QDialogButtonBox.StandardButton.Close).setDefault(True) |
|
131 |
|
132 self.refreshButton.setEnabled(True) |
|
133 |
|
134 self.__resizeColumns() |
|
135 self.__resort() |
|
136 self.on_conflictsList_itemSelectionChanged() |
|
137 |
|
138 @pyqtSlot(QAbstractButton) |
|
139 def on_buttonBox_clicked(self, button): |
|
140 """ |
|
141 Private slot called by a button of the button box clicked. |
|
142 |
|
143 @param button button that was clicked (QAbstractButton) |
|
144 """ |
|
145 if button == self.buttonBox.button( |
|
146 QDialogButtonBox.StandardButton.Close |
|
147 ): |
|
148 self.close() |
|
149 elif button == self.buttonBox.button( |
|
150 QDialogButtonBox.StandardButton.Cancel |
|
151 ): |
|
152 self.__hgClient.cancel() |
|
153 elif button == self.refreshButton: |
|
154 self.on_refreshButton_clicked() |
|
155 |
|
156 def __resort(self): |
|
157 """ |
|
158 Private method to resort the tree. |
|
159 """ |
|
160 self.conflictsList.sortItems( |
|
161 self.conflictsList.sortColumn(), |
|
162 self.conflictsList.header().sortIndicatorOrder()) |
|
163 |
|
164 def __resizeColumns(self): |
|
165 """ |
|
166 Private method to resize the list columns. |
|
167 """ |
|
168 self.conflictsList.header().resizeSections( |
|
169 QHeaderView.ResizeMode.ResizeToContents) |
|
170 self.conflictsList.header().setStretchLastSection(True) |
|
171 |
|
172 def __generateItem(self, status, name): |
|
173 """ |
|
174 Private method to generate a tag item in the tag list. |
|
175 |
|
176 @param status status of the file (string) |
|
177 @param name name of the file (string) |
|
178 """ |
|
179 itm = QTreeWidgetItem(self.conflictsList) |
|
180 if status == "U": |
|
181 itm.setText(0, self.tr("Unresolved")) |
|
182 elif status == "R": |
|
183 itm.setText(0, self.tr("Resolved")) |
|
184 else: |
|
185 itm.setText(0, self.tr("Unknown Status")) |
|
186 itm.setText(1, name) |
|
187 |
|
188 itm.setData(0, self.StatusRole, status) |
|
189 itm.setData(0, self.FilenameRole, self.project.getAbsolutePath(name)) |
|
190 |
|
191 def __processOutputLine(self, line): |
|
192 """ |
|
193 Private method to process the lines of output. |
|
194 |
|
195 @param line output line to be processed (string) |
|
196 """ |
|
197 status, filename = line.strip().split(None, 1) |
|
198 self.__generateItem(status, filename) |
|
199 |
|
200 def __showError(self, out): |
|
201 """ |
|
202 Private slot to show some error. |
|
203 |
|
204 @param out error to be shown (string) |
|
205 """ |
|
206 self.errorGroup.show() |
|
207 self.errors.insertPlainText(out) |
|
208 self.errors.ensureCursorVisible() |
|
209 |
|
210 @pyqtSlot() |
|
211 def on_refreshButton_clicked(self): |
|
212 """ |
|
213 Private slot to refresh the log. |
|
214 """ |
|
215 self.buttonBox.button( |
|
216 QDialogButtonBox.StandardButton.Close).setEnabled(False) |
|
217 self.buttonBox.button( |
|
218 QDialogButtonBox.StandardButton.Cancel).setEnabled(True) |
|
219 self.buttonBox.button( |
|
220 QDialogButtonBox.StandardButton.Cancel).setDefault(True) |
|
221 |
|
222 self.refreshButton.setEnabled(False) |
|
223 self.start() |
|
224 |
|
225 @pyqtSlot(QTreeWidgetItem, int) |
|
226 def on_conflictsList_itemDoubleClicked(self, item, column): |
|
227 """ |
|
228 Private slot to open the double clicked entry. |
|
229 |
|
230 @param item reference to the double clicked item (QTreeWidgetItem) |
|
231 @param column column that was double clicked (integer) |
|
232 """ |
|
233 self.on_editButton_clicked() |
|
234 |
|
235 @pyqtSlot() |
|
236 def on_conflictsList_itemSelectionChanged(self): |
|
237 """ |
|
238 Private slot to handle a change of selected conflict entries. |
|
239 """ |
|
240 selectedCount = len(self.conflictsList.selectedItems()) |
|
241 unresolved = resolved = 0 |
|
242 for itm in self.conflictsList.selectedItems(): |
|
243 status = itm.data(0, self.StatusRole) |
|
244 if status == "U": |
|
245 unresolved += 1 |
|
246 elif status == "R": |
|
247 resolved += 1 |
|
248 |
|
249 self.resolvedButton.setEnabled(unresolved > 0) |
|
250 self.unresolvedButton.setEnabled(resolved > 0) |
|
251 self.reMergeButton.setEnabled(unresolved > 0) |
|
252 self.editButton.setEnabled( |
|
253 selectedCount == 1 and |
|
254 Utilities.MimeTypes.isTextFile( |
|
255 self.conflictsList.selectedItems()[0].data( |
|
256 0, self.FilenameRole))) |
|
257 |
|
258 @pyqtSlot() |
|
259 def on_resolvedButton_clicked(self): |
|
260 """ |
|
261 Private slot to mark the selected entries as resolved. |
|
262 """ |
|
263 names = [ |
|
264 itm.data(0, self.FilenameRole) |
|
265 for itm in self.conflictsList.selectedItems() |
|
266 if itm.data(0, self.StatusRole) == "U" |
|
267 ] |
|
268 if names: |
|
269 self.vcs.hgResolved(names) |
|
270 self.on_refreshButton_clicked() |
|
271 |
|
272 @pyqtSlot() |
|
273 def on_unresolvedButton_clicked(self): |
|
274 """ |
|
275 Private slot to mark the selected entries as unresolved. |
|
276 """ |
|
277 names = [ |
|
278 itm.data(0, self.FilenameRole) |
|
279 for itm in self.conflictsList.selectedItems() |
|
280 if itm.data(0, self.StatusRole) == "R" |
|
281 ] |
|
282 if names: |
|
283 self.vcs.hgResolved(names, unresolve=True) |
|
284 self.on_refreshButton_clicked() |
|
285 |
|
286 @pyqtSlot() |
|
287 def on_reMergeButton_clicked(self): |
|
288 """ |
|
289 Private slot to re-merge the selected entries. |
|
290 """ |
|
291 names = [ |
|
292 itm.data(0, self.FilenameRole) |
|
293 for itm in self.conflictsList.selectedItems() |
|
294 if itm.data(0, self.StatusRole) == "U" |
|
295 ] |
|
296 if names: |
|
297 self.vcs.hgReMerge(names) |
|
298 |
|
299 @pyqtSlot() |
|
300 def on_editButton_clicked(self): |
|
301 """ |
|
302 Private slot to open the selected file in an editor. |
|
303 """ |
|
304 itm = self.conflictsList.selectedItems()[0] |
|
305 filename = itm.data(0, self.FilenameRole) |
|
306 if Utilities.MimeTypes.isTextFile(filename): |
|
307 e5App().getObject("ViewManager").getEditor(filename) |