eric6/Plugins/VcsPlugins/vcsMercurial/QueuesExtension/HgQueuesListDialog.py

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

eric ide

mercurial