|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2011 - 2022 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 PyQt6.QtCore import Qt, QCoreApplication |
|
11 from PyQt6.QtWidgets import ( |
|
12 QDialog, QDialogButtonBox, QHeaderView, QTreeWidgetItem |
|
13 ) |
|
14 |
|
15 from .Ui_HgQueuesListDialog import Ui_HgQueuesListDialog |
|
16 |
|
17 |
|
18 class HgQueuesListDialog(QDialog, Ui_HgQueuesListDialog): |
|
19 """ |
|
20 Class implementing a dialog to show a list of applied and unapplied |
|
21 patches. |
|
22 """ |
|
23 def __init__(self, vcs, parent=None): |
|
24 """ |
|
25 Constructor |
|
26 |
|
27 @param vcs reference to the vcs object |
|
28 @param parent parent widget (QWidget) |
|
29 """ |
|
30 super().__init__(parent) |
|
31 self.setupUi(self) |
|
32 self.setWindowFlags(Qt.WindowType.Window) |
|
33 |
|
34 self.buttonBox.button( |
|
35 QDialogButtonBox.StandardButton.Close).setEnabled(False) |
|
36 self.buttonBox.button( |
|
37 QDialogButtonBox.StandardButton.Cancel).setDefault(True) |
|
38 |
|
39 self.vcs = vcs |
|
40 self.__hgClient = vcs.getClient() |
|
41 |
|
42 self.patchesList.header().setSortIndicator( |
|
43 0, Qt.SortOrder.AscendingOrder) |
|
44 |
|
45 self.__statusDict = { |
|
46 "A": self.tr("applied"), |
|
47 "U": self.tr("not applied"), |
|
48 "G": self.tr("guarded"), |
|
49 "D": self.tr("missing"), |
|
50 } |
|
51 |
|
52 self.show() |
|
53 QCoreApplication.processEvents() |
|
54 |
|
55 def closeEvent(self, e): |
|
56 """ |
|
57 Protected slot implementing a close event handler. |
|
58 |
|
59 @param e close event (QCloseEvent) |
|
60 """ |
|
61 if self.__hgClient.isExecuting(): |
|
62 self.__hgClient.cancel() |
|
63 |
|
64 e.accept() |
|
65 |
|
66 def start(self): |
|
67 """ |
|
68 Public slot to start the list command. |
|
69 """ |
|
70 self.errorGroup.hide() |
|
71 self.activateWindow() |
|
72 |
|
73 self.__getSeries() |
|
74 |
|
75 def __getSeries(self, missing=False): |
|
76 """ |
|
77 Private slot to get the list of applied, unapplied and guarded patches |
|
78 and patches missing in the series file. |
|
79 |
|
80 @param missing flag indicating to get the patches missing in the |
|
81 series file (boolean) |
|
82 """ |
|
83 if missing: |
|
84 self.__mode = "missing" |
|
85 else: |
|
86 self.__mode = "qseries" |
|
87 |
|
88 args = self.vcs.initCommand("qseries") |
|
89 args.append('--summary') |
|
90 args.append('--verbose') |
|
91 if missing: |
|
92 args.append('--missing') |
|
93 |
|
94 out, err = self.__hgClient.runcommand(args) |
|
95 if err: |
|
96 self.__showError(err) |
|
97 if out: |
|
98 for line in out.splitlines(): |
|
99 self.__processOutputLine(line) |
|
100 if self.__hgClient.wasCanceled(): |
|
101 self.__mode = "" |
|
102 break |
|
103 if self.__mode == "qseries": |
|
104 self.__getSeries(True) |
|
105 elif self.__mode == "missing": |
|
106 self.__getTop() |
|
107 else: |
|
108 self.__finish() |
|
109 |
|
110 def __getTop(self): |
|
111 """ |
|
112 Private slot to get patch at the top of the stack. |
|
113 """ |
|
114 self.__mode = "qtop" |
|
115 |
|
116 args = self.vcs.initCommand("qtop") |
|
117 |
|
118 out, err = self.__hgClient.runcommand(args) |
|
119 if err: |
|
120 self.__showError(err) |
|
121 if out: |
|
122 for line in out.splitlines(): |
|
123 self.__processOutputLine(line) |
|
124 if self.__hgClient.wasCanceled(): |
|
125 break |
|
126 self.__finish() |
|
127 |
|
128 def __finish(self): |
|
129 """ |
|
130 Private slot called when the process finished or the user pressed |
|
131 the button. |
|
132 """ |
|
133 self.buttonBox.button( |
|
134 QDialogButtonBox.StandardButton.Close).setEnabled(True) |
|
135 self.buttonBox.button( |
|
136 QDialogButtonBox.StandardButton.Cancel).setEnabled(False) |
|
137 self.buttonBox.button( |
|
138 QDialogButtonBox.StandardButton.Close).setDefault(True) |
|
139 self.buttonBox.button( |
|
140 QDialogButtonBox.StandardButton.Close).setFocus( |
|
141 Qt.FocusReason.OtherFocusReason) |
|
142 |
|
143 if self.patchesList.topLevelItemCount() == 0: |
|
144 # no patches present |
|
145 self.__generateItem( |
|
146 0, "", self.tr("no patches found"), "", True) |
|
147 self.__resizeColumns() |
|
148 self.__resort() |
|
149 |
|
150 def on_buttonBox_clicked(self, button): |
|
151 """ |
|
152 Private slot called by a button of the button box clicked. |
|
153 |
|
154 @param button button that was clicked (QAbstractButton) |
|
155 """ |
|
156 if button == self.buttonBox.button( |
|
157 QDialogButtonBox.StandardButton.Close |
|
158 ): |
|
159 self.close() |
|
160 elif button == self.buttonBox.button( |
|
161 QDialogButtonBox.StandardButton.Cancel |
|
162 ): |
|
163 self.__mode = "" |
|
164 self.__hgClient.cancel() |
|
165 |
|
166 def __resort(self): |
|
167 """ |
|
168 Private method to resort the tree. |
|
169 """ |
|
170 self.patchesList.sortItems( |
|
171 self.patchesList.sortColumn(), |
|
172 self.patchesList.header().sortIndicatorOrder()) |
|
173 |
|
174 def __resizeColumns(self): |
|
175 """ |
|
176 Private method to resize the list columns. |
|
177 """ |
|
178 self.patchesList.header().resizeSections( |
|
179 QHeaderView.ResizeMode.ResizeToContents) |
|
180 self.patchesList.header().setStretchLastSection(True) |
|
181 |
|
182 def __generateItem(self, index, status, name, summary, error=False): |
|
183 """ |
|
184 Private method to generate a patch item in the list of patches. |
|
185 |
|
186 @param index index of the patch (integer, -1 for missing) |
|
187 @param status status of the patch (string) |
|
188 @param name name of the patch (string) |
|
189 @param summary first line of the patch header (string) |
|
190 @param error flag indicating an error entry (boolean) |
|
191 """ |
|
192 if error: |
|
193 itm = QTreeWidgetItem(self.patchesList, [ |
|
194 "", |
|
195 name, |
|
196 "", |
|
197 summary |
|
198 ]) |
|
199 else: |
|
200 if index == -1: |
|
201 index = "" |
|
202 try: |
|
203 statusStr = self.__statusDict[status] |
|
204 except KeyError: |
|
205 statusStr = self.tr("unknown") |
|
206 itm = QTreeWidgetItem(self.patchesList) |
|
207 itm.setData(0, Qt.ItemDataRole.DisplayRole, index) |
|
208 itm.setData(1, Qt.ItemDataRole.DisplayRole, name) |
|
209 itm.setData(2, Qt.ItemDataRole.DisplayRole, statusStr) |
|
210 itm.setData(3, Qt.ItemDataRole.DisplayRole, summary) |
|
211 if status == "A": |
|
212 # applied |
|
213 for column in range(itm.columnCount()): |
|
214 itm.setForeground(column, Qt.GlobalColor.blue) |
|
215 elif status == "D": |
|
216 # missing |
|
217 for column in range(itm.columnCount()): |
|
218 itm.setForeground(column, Qt.GlobalColor.red) |
|
219 |
|
220 itm.setTextAlignment(0, Qt.AlignmentFlag.AlignRight) |
|
221 itm.setTextAlignment(2, Qt.AlignmentFlag.AlignHCenter) |
|
222 |
|
223 def __markTopItem(self, name): |
|
224 """ |
|
225 Private slot to mark the top patch entry. |
|
226 |
|
227 @param name name of the patch (string) |
|
228 """ |
|
229 items = self.patchesList.findItems( |
|
230 name, Qt.MatchFlag.MatchCaseSensitive, 1) |
|
231 if items: |
|
232 itm = items[0] |
|
233 for column in range(itm.columnCount()): |
|
234 font = itm.font(column) |
|
235 font.setBold(True) |
|
236 itm.setFont(column, font) |
|
237 |
|
238 def __processOutputLine(self, line): |
|
239 """ |
|
240 Private method to process the lines of output. |
|
241 |
|
242 @param line output line to be processed (string) |
|
243 """ |
|
244 if self.__mode == "qtop": |
|
245 self.__markTopItem(line) |
|
246 else: |
|
247 li = line.split(": ", 1) |
|
248 if len(li) == 1: |
|
249 data, summary = li[0][:-1], "" |
|
250 else: |
|
251 data, summary = li[0], li[1] |
|
252 li = data.split(None, 2) |
|
253 if len(li) == 2: |
|
254 # missing entry |
|
255 index, status, name = -1, li[0], li[1] |
|
256 elif len(li) == 3: |
|
257 index, status, name = li[:3] |
|
258 else: |
|
259 return |
|
260 self.__generateItem(index, status, name, summary) |
|
261 |
|
262 def __showError(self, out): |
|
263 """ |
|
264 Private slot to show some error. |
|
265 |
|
266 @param out error to be shown (string) |
|
267 """ |
|
268 self.errorGroup.show() |
|
269 self.errors.insertPlainText(out) |
|
270 self.errors.ensureCursorVisible() |