|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2016 - 2019 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a dialog to edit the history modification plan. |
|
8 """ |
|
9 |
|
10 from __future__ import unicode_literals |
|
11 |
|
12 from PyQt5.QtCore import pyqtSlot, QCoreApplication |
|
13 from PyQt5.QtWidgets import QDialog, QTreeWidgetItem, QComboBox |
|
14 |
|
15 from E5Gui import E5MessageBox |
|
16 |
|
17 from Ui_HgHisteditPlanEditor import Ui_HgHisteditPlanEditor |
|
18 |
|
19 import UI.PixmapCache |
|
20 |
|
21 |
|
22 class HgHisteditPlanActionComboBox(QComboBox): |
|
23 """ |
|
24 Class implementing a combo box to select the action in the plan tree. |
|
25 """ |
|
26 def __init__(self, item, column): |
|
27 """ |
|
28 Constructor |
|
29 |
|
30 @param item reference to the item |
|
31 @type QTreeWidgetItem |
|
32 @param column column number inside the tree widget item |
|
33 @type int |
|
34 """ |
|
35 super(HgHisteditPlanActionComboBox, self).__init__() |
|
36 |
|
37 self.__item = item |
|
38 self.__column = column |
|
39 |
|
40 self.addItems(["pick", "drop", "mess", "fold", "roll", "edit"]) |
|
41 txt = self.__item.text(self.__column) |
|
42 index = self.findText(txt) |
|
43 if index > -1: |
|
44 self.setCurrentIndex(index) |
|
45 |
|
46 self.currentIndexChanged.connect(self.__changeItem) |
|
47 |
|
48 @pyqtSlot(int) |
|
49 def __changeItem(self, index): |
|
50 """ |
|
51 Private slot to handle the selection of a plan action. |
|
52 |
|
53 This method sets the text of the associated item for the specified |
|
54 cell in order to be able to retrieve it with a call of the text() |
|
55 method of the item. |
|
56 |
|
57 @param index index of the selected action |
|
58 @type int |
|
59 """ |
|
60 self.__item.setText(self.__column, self.currentText()) |
|
61 self.__item.treeWidget().setCurrentItem(self.__item) |
|
62 |
|
63 def showPopup(self): |
|
64 """ |
|
65 Public method to show the list of items of the combo box. |
|
66 |
|
67 This is reimplemented in order to set the associated item as the |
|
68 current item of the tree widget. |
|
69 """ |
|
70 self.__item.treeWidget().setCurrentItem(self.__item) |
|
71 super(HgHisteditPlanActionComboBox, self).showPopup() |
|
72 |
|
73 |
|
74 class HgHisteditPlanEditor(QDialog, Ui_HgHisteditPlanEditor): |
|
75 """ |
|
76 Class implementing a dialog to edit the history modification plan. |
|
77 """ |
|
78 def __init__(self, fileName, parent=None): |
|
79 """ |
|
80 Constructor |
|
81 |
|
82 @param fileName name of the file containing the history edit plan |
|
83 to be edited |
|
84 @type str |
|
85 @param parent reference to the parent widget |
|
86 @type QWidget |
|
87 """ |
|
88 super(HgHisteditPlanEditor, self).__init__(parent) |
|
89 self.setupUi(self) |
|
90 |
|
91 self.upButton.setIcon(UI.PixmapCache.getIcon("1uparrow.png")) |
|
92 self.downButton.setIcon(UI.PixmapCache.getIcon("1downarrow.png")) |
|
93 |
|
94 self.planTreeWidget.headerItem().setText( |
|
95 self.planTreeWidget.columnCount(), "") |
|
96 |
|
97 self.__fileName = fileName |
|
98 self.__readFile() |
|
99 |
|
100 self.__updateButtons() |
|
101 |
|
102 def __readFile(self): |
|
103 """ |
|
104 Private method to read the file containing the edit plan and |
|
105 populate the dialog. |
|
106 """ |
|
107 try: |
|
108 f = open(self.__fileName, "r") |
|
109 txt = f.read() |
|
110 f.close() |
|
111 except (IOError, OSError) as err: |
|
112 E5MessageBox.critical( |
|
113 self, |
|
114 self.tr("Edit Plan"), |
|
115 self.tr("""<p>The file <b>{0}</b> could not be read.</p>""" |
|
116 """<p>Reason: {1}</p>""").format( |
|
117 self.__fileName, str(err))) |
|
118 self.on_buttonBox_rejected() |
|
119 return |
|
120 |
|
121 infoLines = [] |
|
122 for line in txt.splitlines(): |
|
123 if line.startswith("#"): |
|
124 infoLines.append(line[1:].lstrip()) |
|
125 else: |
|
126 self.__createPlanItem(line) |
|
127 self.infoEdit.setPlainText("\n".join(infoLines)) |
|
128 |
|
129 self.__resizeSections() |
|
130 |
|
131 def __addActionCombo(self, item): |
|
132 """ |
|
133 Private method to add an edit action combo to an item. |
|
134 |
|
135 @param item reference to the tree widget item |
|
136 @type QTreeWidgetItem |
|
137 """ |
|
138 actionCombo = HgHisteditPlanActionComboBox(item, 0) |
|
139 self.planTreeWidget.setItemWidget(item, 0, actionCombo) |
|
140 item.setSizeHint(0, actionCombo.sizeHint()) |
|
141 |
|
142 def __createPlanItem(self, text): |
|
143 """ |
|
144 Private method to create an edit plan tree item. |
|
145 |
|
146 @param text line of text to be parsed |
|
147 @type str |
|
148 """ |
|
149 if not text.lstrip(): |
|
150 return |
|
151 |
|
152 parts = text.split(" ", 3) |
|
153 action = parts[0] |
|
154 try: |
|
155 rev = int(parts[2]) |
|
156 if len(parts) > 3: |
|
157 summary = parts[3] |
|
158 else: |
|
159 summary = "" |
|
160 except ValueError: |
|
161 rev = -1 |
|
162 summary = " ".join(parts[2:]) |
|
163 if rev > -1: |
|
164 revision = "{0:>7}:{1}".format(rev, parts[1]) |
|
165 else: |
|
166 revision = parts[1] |
|
167 |
|
168 itm = QTreeWidgetItem(self.planTreeWidget, [ |
|
169 action, |
|
170 revision, |
|
171 summary, |
|
172 ]) |
|
173 self.__addActionCombo(itm) |
|
174 |
|
175 def __resizeSections(self): |
|
176 """ |
|
177 Private method to resize the tree widget sections. |
|
178 """ |
|
179 for column in range(self.planTreeWidget.columnCount()): |
|
180 self.planTreeWidget.resizeColumnToContents(column) |
|
181 self.planTreeWidget.header().setStretchLastSection(True) |
|
182 |
|
183 def __updateButtons(self): |
|
184 """ |
|
185 Private method to set the enabled state of the up and down buttons. |
|
186 """ |
|
187 if self.planTreeWidget.currentItem() is None: |
|
188 self.upButton.setEnabled(False) |
|
189 self.downButton.setEnabled(False) |
|
190 else: |
|
191 row = self.planTreeWidget.indexOfTopLevelItem( |
|
192 self.planTreeWidget.currentItem()) |
|
193 self.upButton.setEnabled(row > 0) |
|
194 self.downButton.setEnabled( |
|
195 row < self.planTreeWidget.topLevelItemCount() - 1) |
|
196 |
|
197 @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem) |
|
198 def on_planTreeWidget_currentItemChanged(self, current, previous): |
|
199 """ |
|
200 Private slot handling the change of the current edit plan item. |
|
201 |
|
202 @param current reference to the current edit plan item |
|
203 @type QTreeWidgetItem |
|
204 @param previous reference to the previous current edit plan item |
|
205 @type QTreeWidgetItem |
|
206 """ |
|
207 self.__updateButtons() |
|
208 |
|
209 @pyqtSlot() |
|
210 def on_upButton_clicked(self): |
|
211 """ |
|
212 Private slot to move the current entry up one line. |
|
213 """ |
|
214 row = self.planTreeWidget.indexOfTopLevelItem( |
|
215 self.planTreeWidget.currentItem()) |
|
216 if row > 0: |
|
217 targetRow = row - 1 |
|
218 itm = self.planTreeWidget.takeTopLevelItem(row) |
|
219 self.planTreeWidget.insertTopLevelItem(targetRow, itm) |
|
220 self.__addActionCombo(itm) |
|
221 self.planTreeWidget.setCurrentItem(itm) |
|
222 |
|
223 @pyqtSlot() |
|
224 def on_downButton_clicked(self): |
|
225 """ |
|
226 Private slot to move the current entry down one line. |
|
227 """ |
|
228 row = self.planTreeWidget.indexOfTopLevelItem( |
|
229 self.planTreeWidget.currentItem()) |
|
230 if row < self.planTreeWidget.topLevelItemCount() - 1: |
|
231 targetRow = row + 1 |
|
232 itm = self.planTreeWidget.takeTopLevelItem(row) |
|
233 self.planTreeWidget.insertTopLevelItem(targetRow, itm) |
|
234 self.__addActionCombo(itm) |
|
235 self.planTreeWidget.setCurrentItem(itm) |
|
236 |
|
237 @pyqtSlot() |
|
238 def on_buttonBox_accepted(self): |
|
239 """ |
|
240 Private slot called by the buttonBox accepted signal. |
|
241 """ |
|
242 text = self.__assembleEditPlan() |
|
243 try: |
|
244 f = open(self.__fileName, "w") |
|
245 f.write(text) |
|
246 f.close() |
|
247 except (IOError, OSError) as err: |
|
248 E5MessageBox.critical( |
|
249 self, |
|
250 self.tr("Edit Plan"), |
|
251 self.tr("""<p>The file <b>{0}</b> could not be read.</p>""" |
|
252 """<p>Reason: {1}</p>""").format( |
|
253 self.__fileName, str(err))) |
|
254 self.on_buttonBox_rejected() |
|
255 return |
|
256 |
|
257 self.close() |
|
258 QCoreApplication.exit(0) |
|
259 |
|
260 @pyqtSlot() |
|
261 def on_buttonBox_rejected(self): |
|
262 """ |
|
263 Private slot called by the buttonBox rejected signal. |
|
264 """ |
|
265 self.close() |
|
266 QCoreApplication.exit(1) |
|
267 |
|
268 def __assembleEditPlan(self): |
|
269 """ |
|
270 Private method to assemble the edit plan into text suitable for the |
|
271 histedit file. |
|
272 |
|
273 @return assembled edit plan text |
|
274 @rtype str |
|
275 """ |
|
276 lines = [] |
|
277 for row in range(self.planTreeWidget.topLevelItemCount()): |
|
278 itm = self.planTreeWidget.topLevelItem(row) |
|
279 if ":" in itm.text(1): |
|
280 rev, changeset = itm.text(1).split(":", 1) |
|
281 rev = "{0} {1}".format(changeset.strip(), rev.strip()) |
|
282 else: |
|
283 rev = itm.text(1).strip() |
|
284 |
|
285 lines.append("{0} {1} {2}".format(itm.text(0).strip(), rev, |
|
286 itm.text(2))) |
|
287 |
|
288 return "\n".join(lines) |