eric6/Plugins/VcsPlugins/vcsMercurial/HgConflictsListDialog.py

changeset 6942
2602857055c5
parent 6645
ad476851d7e0
child 7229
53054eb5b15a
equal deleted inserted replaced
6941:f99d60d6b59b 6942:2602857055c5
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2014 - 2019 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 __future__ import unicode_literals
12
13 import os
14
15 from PyQt5.QtCore import pyqtSlot, Qt, QPoint, QProcess, QTimer
16 from PyQt5.QtWidgets import QAbstractButton, QDialogButtonBox, QHeaderView, \
17 QTreeWidgetItem, QLineEdit, QApplication, QWidget
18
19 from E5Gui import E5MessageBox
20 from E5Gui.E5Application import e5App
21
22 from .Ui_HgConflictsListDialog import Ui_HgConflictsListDialog
23
24 import Utilities.MimeTypes
25 from Globals import strToQByteArray
26
27
28 class HgConflictsListDialog(QWidget, Ui_HgConflictsListDialog):
29 """
30 Class implementing a dialog to show a list of files which had or still
31 have conflicts.
32 """
33 StatusRole = Qt.UserRole + 1
34 FilenameRole = Qt.UserRole + 2
35
36 def __init__(self, vcs, parent=None):
37 """
38 Constructor
39
40 @param vcs reference to the vcs object
41 @param parent parent widget (QWidget)
42 """
43 super(HgConflictsListDialog, self).__init__(parent)
44 self.setupUi(self)
45
46 self.__position = QPoint()
47
48 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
49 self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
50
51 self.conflictsList.headerItem().setText(
52 self.conflictsList.columnCount(), "")
53 self.conflictsList.header().setSortIndicator(0, Qt.AscendingOrder)
54
55 self.refreshButton = self.buttonBox.addButton(
56 self.tr("&Refresh"), QDialogButtonBox.ActionRole)
57 self.refreshButton.setToolTip(
58 self.tr("Press to refresh the list of conflicts"))
59 self.refreshButton.setEnabled(False)
60
61 self.vcs = vcs
62 self.project = e5App().getObject("Project")
63
64 self.__hgClient = vcs.getClient()
65 if self.__hgClient:
66 self.process = None
67 else:
68 self.process = QProcess()
69 self.process.finished.connect(self.__procFinished)
70 self.process.readyReadStandardOutput.connect(self.__readStdout)
71 self.process.readyReadStandardError.connect(self.__readStderr)
72
73 def closeEvent(self, e):
74 """
75 Protected slot implementing a close event handler.
76
77 @param e close event (QCloseEvent)
78 """
79 if self.__hgClient:
80 if self.__hgClient.isExecuting():
81 self.__hgClient.cancel()
82 else:
83 if self.process is not None and \
84 self.process.state() != QProcess.NotRunning:
85 self.process.terminate()
86 QTimer.singleShot(2000, self.process.kill)
87 self.process.waitForFinished(3000)
88
89 self.__position = self.pos()
90
91 e.accept()
92
93 def show(self):
94 """
95 Public slot to show the dialog.
96 """
97 if not self.__position.isNull():
98 self.move(self.__position)
99
100 super(HgConflictsListDialog, self).show()
101
102 def start(self, path):
103 """
104 Public slot to start the tags command.
105
106 @param path name of directory to list conflicts for (string)
107 """
108 self.errorGroup.hide()
109 QApplication.processEvents()
110
111 self.intercept = False
112 dname, fname = self.vcs.splitPath(path)
113
114 # find the root of the repo
115 self.__repodir = dname
116 while not os.path.isdir(
117 os.path.join(self.__repodir, self.vcs.adminDir)):
118 self.__repodir = os.path.dirname(self.__repodir)
119 if os.path.splitdrive(self.__repodir)[1] == os.sep:
120 return
121
122 self.activateWindow()
123 self.raise_()
124
125 self.conflictsList.clear()
126 self.__started = True
127 self.__getEntries()
128
129 def __getEntries(self):
130 """
131 Private method to get the conflict entries.
132 """
133 args = self.vcs.initCommand("resolve")
134 args.append('--list')
135
136 if self.__hgClient:
137 self.inputGroup.setEnabled(False)
138 self.inputGroup.hide()
139
140 out, err = self.__hgClient.runcommand(args)
141 if err:
142 self.__showError(err)
143 if out:
144 for line in out.splitlines():
145 self.__processOutputLine(line)
146 if self.__hgClient.wasCanceled():
147 break
148 self.__finish()
149 else:
150 self.process.kill()
151 self.process.setWorkingDirectory(self.__repodir)
152
153 self.process.start('hg', args)
154 procStarted = self.process.waitForStarted(5000)
155 if not procStarted:
156 self.inputGroup.setEnabled(False)
157 self.inputGroup.hide()
158 E5MessageBox.critical(
159 self,
160 self.tr('Process Generation Error'),
161 self.tr(
162 'The process {0} could not be started. '
163 'Ensure, that it is in the search path.'
164 ).format('hg'))
165 else:
166 self.inputGroup.setEnabled(True)
167 self.inputGroup.show()
168
169 def __finish(self):
170 """
171 Private slot called when the process finished or the user pressed
172 the button.
173 """
174 if self.process is not None and \
175 self.process.state() != QProcess.NotRunning:
176 self.process.terminate()
177 QTimer.singleShot(2000, self.process.kill)
178 self.process.waitForFinished(3000)
179
180 QApplication.restoreOverrideCursor()
181
182 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
183 self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(False)
184 self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
185
186 self.inputGroup.setEnabled(False)
187 self.inputGroup.hide()
188 self.refreshButton.setEnabled(True)
189
190 self.__resizeColumns()
191 self.__resort()
192 self.on_conflictsList_itemSelectionChanged()
193
194 @pyqtSlot(QAbstractButton)
195 def on_buttonBox_clicked(self, button):
196 """
197 Private slot called by a button of the button box clicked.
198
199 @param button button that was clicked (QAbstractButton)
200 """
201 if button == self.buttonBox.button(QDialogButtonBox.Close):
202 self.close()
203 elif button == self.buttonBox.button(QDialogButtonBox.Cancel):
204 if self.__hgClient:
205 self.__hgClient.cancel()
206 else:
207 self.__finish()
208 elif button == self.refreshButton:
209 self.on_refreshButton_clicked()
210
211 def __procFinished(self, exitCode, exitStatus):
212 """
213 Private slot connected to the finished signal.
214
215 @param exitCode exit code of the process (integer)
216 @param exitStatus exit status of the process (QProcess.ExitStatus)
217 """
218 self.__finish()
219
220 def __resort(self):
221 """
222 Private method to resort the tree.
223 """
224 self.conflictsList.sortItems(
225 self.conflictsList.sortColumn(),
226 self.conflictsList.header().sortIndicatorOrder())
227
228 def __resizeColumns(self):
229 """
230 Private method to resize the list columns.
231 """
232 self.conflictsList.header().resizeSections(
233 QHeaderView.ResizeToContents)
234 self.conflictsList.header().setStretchLastSection(True)
235
236 def __generateItem(self, status, name):
237 """
238 Private method to generate a tag item in the tag list.
239
240 @param status status of the file (string)
241 @param name name of the file (string)
242 """
243 itm = QTreeWidgetItem(self.conflictsList)
244 if status == "U":
245 itm.setText(0, self.tr("Unresolved"))
246 elif status == "R":
247 itm.setText(0, self.tr("Resolved"))
248 else:
249 itm.setText(0, self.tr("Unknown Status"))
250 itm.setText(1, name)
251
252 itm.setData(0, self.StatusRole, status)
253 itm.setData(0, self.FilenameRole, self.project.getAbsolutePath(name))
254
255 def __readStdout(self):
256 """
257 Private slot to handle the readyReadStdout signal.
258
259 It reads the output of the process, formats it and inserts it into
260 the contents pane.
261 """
262 self.process.setReadChannel(QProcess.StandardOutput)
263
264 while self.process.canReadLine():
265 s = str(self.process.readLine(), self.vcs.getEncoding(),
266 'replace').strip()
267 self.__processOutputLine(s)
268
269 def __processOutputLine(self, line):
270 """
271 Private method to process the lines of output.
272
273 @param line output line to be processed (string)
274 """
275 status, filename = line.strip().split(None, 1)
276 self.__generateItem(status, filename)
277
278 @pyqtSlot()
279 def on_refreshButton_clicked(self):
280 """
281 Private slot to refresh the log.
282 """
283 self.buttonBox.button(QDialogButtonBox.Close).setEnabled(False)
284 self.buttonBox.button(QDialogButtonBox.Cancel).setEnabled(True)
285 self.buttonBox.button(QDialogButtonBox.Cancel).setDefault(True)
286
287 self.inputGroup.setEnabled(True)
288 self.inputGroup.show()
289 self.refreshButton.setEnabled(False)
290 self.start(self.__repodir)
291
292 def __readStderr(self):
293 """
294 Private slot to handle the readyReadStderr signal.
295
296 It reads the error output of the process and inserts it into the
297 error pane.
298 """
299 if self.process is not None:
300 s = str(self.process.readAllStandardError(),
301 self.vcs.getEncoding(), 'replace')
302 self.__showError(s)
303
304 def __showError(self, out):
305 """
306 Private slot to show some error.
307
308 @param out error to be shown (string)
309 """
310 self.errorGroup.show()
311 self.errors.insertPlainText(out)
312 self.errors.ensureCursorVisible()
313
314 def on_passwordCheckBox_toggled(self, isOn):
315 """
316 Private slot to handle the password checkbox toggled.
317
318 @param isOn flag indicating the status of the check box (boolean)
319 """
320 if isOn:
321 self.input.setEchoMode(QLineEdit.Password)
322 else:
323 self.input.setEchoMode(QLineEdit.Normal)
324
325 @pyqtSlot()
326 def on_sendButton_clicked(self):
327 """
328 Private slot to send the input to the subversion process.
329 """
330 inputTxt = self.input.text()
331 inputTxt += os.linesep
332
333 if self.passwordCheckBox.isChecked():
334 self.errors.insertPlainText(os.linesep)
335 self.errors.ensureCursorVisible()
336 else:
337 self.errors.insertPlainText(inputTxt)
338 self.errors.ensureCursorVisible()
339
340 self.process.write(strToQByteArray(inputTxt))
341
342 self.passwordCheckBox.setChecked(False)
343 self.input.clear()
344
345 def on_input_returnPressed(self):
346 """
347 Private slot to handle the press of the return key in the input field.
348 """
349 self.intercept = True
350 self.on_sendButton_clicked()
351
352 def keyPressEvent(self, evt):
353 """
354 Protected slot to handle a key press event.
355
356 @param evt the key press event (QKeyEvent)
357 """
358 if self.intercept:
359 self.intercept = False
360 evt.accept()
361 return
362 super(HgConflictsListDialog, self).keyPressEvent(evt)
363
364 @pyqtSlot(QTreeWidgetItem, int)
365 def on_conflictsList_itemDoubleClicked(self, item, column):
366 """
367 Private slot to open the double clicked entry.
368
369 @param item reference to the double clicked item (QTreeWidgetItem)
370 @param column column that was double clicked (integer)
371 """
372 self.on_editButton_clicked()
373
374 @pyqtSlot()
375 def on_conflictsList_itemSelectionChanged(self):
376 """
377 Private slot to handle a change of selected conflict entries.
378 """
379 selectedCount = len(self.conflictsList.selectedItems())
380 unresolved = resolved = 0
381 for itm in self.conflictsList.selectedItems():
382 status = itm.data(0, self.StatusRole)
383 if status == "U":
384 unresolved += 1
385 elif status == "R":
386 resolved += 1
387
388 self.resolvedButton.setEnabled(unresolved > 0)
389 self.unresolvedButton.setEnabled(resolved > 0)
390 self.reMergeButton.setEnabled(unresolved > 0)
391 self.editButton.setEnabled(
392 selectedCount == 1 and
393 Utilities.MimeTypes.isTextFile(
394 self.conflictsList.selectedItems()[0].data(
395 0, self.FilenameRole)))
396
397 @pyqtSlot()
398 def on_resolvedButton_clicked(self):
399 """
400 Private slot to mark the selected entries as resolved.
401 """
402 names = [
403 itm.data(0, self.FilenameRole)
404 for itm in self.conflictsList.selectedItems()
405 if itm.data(0, self.StatusRole) == "U"
406 ]
407 if names:
408 self.vcs.hgResolved(names)
409 self.on_refreshButton_clicked()
410
411 @pyqtSlot()
412 def on_unresolvedButton_clicked(self):
413 """
414 Private slot to mark the selected entries as unresolved.
415 """
416 names = [
417 itm.data(0, self.FilenameRole)
418 for itm in self.conflictsList.selectedItems()
419 if itm.data(0, self.StatusRole) == "R"
420 ]
421 if names:
422 self.vcs.hgResolved(names, unresolve=True)
423 self.on_refreshButton_clicked()
424
425 @pyqtSlot()
426 def on_reMergeButton_clicked(self):
427 """
428 Private slot to re-merge the selected entries.
429 """
430 names = [
431 itm.data(0, self.FilenameRole)
432 for itm in self.conflictsList.selectedItems()
433 if itm.data(0, self.StatusRole) == "U"
434 ]
435 if names:
436 self.vcs.hgReMerge(names)
437
438 @pyqtSlot()
439 def on_editButton_clicked(self):
440 """
441 Private slot to open the selected file in an editor.
442 """
443 itm = self.conflictsList.selectedItems()[0]
444 filename = itm.data(0, self.FilenameRole)
445 if Utilities.MimeTypes.isTextFile(filename):
446 e5App().getObject("ViewManager").getEditor(filename)

eric ide

mercurial