Plugins/VcsPlugins/vcsMercurial/HgConflictsListDialog.py

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

eric ide

mercurial