|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2008 - 2019 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a toolbar configuration dialog. |
|
8 """ |
|
9 |
|
10 from __future__ import unicode_literals |
|
11 |
|
12 from PyQt5.QtCore import pyqtSlot, Qt |
|
13 from PyQt5.QtGui import QColor |
|
14 from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QTreeWidgetItem, \ |
|
15 QInputDialog, QLineEdit, QListWidgetItem, QAbstractButton |
|
16 |
|
17 from E5Gui import E5MessageBox |
|
18 |
|
19 from .Ui_E5ToolBarDialog import Ui_E5ToolBarDialog |
|
20 |
|
21 import UI.PixmapCache |
|
22 |
|
23 |
|
24 class E5ToolBarItem(object): |
|
25 """ |
|
26 Class storing data belonging to a toolbar entry of the toolbar dialog. |
|
27 """ |
|
28 def __init__(self, toolBarId, actionIDs, default): |
|
29 """ |
|
30 Constructor |
|
31 |
|
32 @param toolBarId id of the toolbar object (integer) |
|
33 @param actionIDs list of action IDs belonging to the toolbar |
|
34 (list of integer) |
|
35 @param default flag indicating a default toolbar (boolean) |
|
36 """ |
|
37 self.toolBarId = toolBarId |
|
38 self.actionIDs = actionIDs[:] |
|
39 self.isDefault = default |
|
40 self.title = "" |
|
41 self.isChanged = False |
|
42 |
|
43 |
|
44 class E5ToolBarDialog(QDialog, Ui_E5ToolBarDialog): |
|
45 """ |
|
46 Class implementing a toolbar configuration dialog. |
|
47 """ |
|
48 ActionIdRole = Qt.UserRole |
|
49 WidgetActionRole = Qt.UserRole + 1 |
|
50 |
|
51 def __init__(self, toolBarManager, parent=None): |
|
52 """ |
|
53 Constructor |
|
54 |
|
55 @param toolBarManager reference to a toolbar manager object |
|
56 (E5ToolBarManager) |
|
57 @param parent reference to the parent widget (QWidget) |
|
58 """ |
|
59 super(E5ToolBarDialog, self).__init__(parent) |
|
60 self.setupUi(self) |
|
61 |
|
62 self.__manager = toolBarManager |
|
63 self.__toolbarItems = {} |
|
64 # maps toolbar item IDs to toolbar items |
|
65 self.__currentToolBarItem = None |
|
66 self.__removedToolBarIDs = [] |
|
67 # remember custom toolbars to be deleted |
|
68 |
|
69 self.__widgetActionToToolBarItemID = {} |
|
70 # maps widget action IDs to toolbar item IDs |
|
71 self.__toolBarItemToWidgetActionID = {} |
|
72 # maps toolbar item IDs to widget action IDs |
|
73 |
|
74 self.upButton.setIcon(UI.PixmapCache.getIcon("1uparrow.png")) |
|
75 self.downButton.setIcon(UI.PixmapCache.getIcon("1downarrow.png")) |
|
76 self.leftButton.setIcon(UI.PixmapCache.getIcon("1leftarrow.png")) |
|
77 self.rightButton.setIcon(UI.PixmapCache.getIcon("1rightarrow.png")) |
|
78 |
|
79 self.__restoreDefaultsButton = \ |
|
80 self.buttonBox.button(QDialogButtonBox.RestoreDefaults) |
|
81 self.__resetButton = self.buttonBox.button(QDialogButtonBox.Reset) |
|
82 |
|
83 self.actionsTree.header().hide() |
|
84 self.__separatorText = self.tr("--Separator--") |
|
85 itm = QTreeWidgetItem(self.actionsTree, [self.__separatorText]) |
|
86 self.actionsTree.setCurrentItem(itm) |
|
87 |
|
88 for category in sorted(self.__manager.categories()): |
|
89 categoryItem = QTreeWidgetItem(self.actionsTree, [category]) |
|
90 for action in self.__manager.categoryActions(category): |
|
91 item = QTreeWidgetItem(categoryItem) |
|
92 item.setText(0, action.text()) |
|
93 item.setIcon(0, action.icon()) |
|
94 item.setTextAlignment(0, Qt.AlignLeft | Qt.AlignVCenter) |
|
95 item.setData(0, E5ToolBarDialog.ActionIdRole, int(id(action))) |
|
96 item.setData(0, E5ToolBarDialog.WidgetActionRole, False) |
|
97 if self.__manager.isWidgetAction(action): |
|
98 item.setData(0, E5ToolBarDialog.WidgetActionRole, True) |
|
99 item.setData(0, Qt.TextColorRole, QColor(Qt.blue)) |
|
100 self.__widgetActionToToolBarItemID[id(action)] = None |
|
101 categoryItem.setExpanded(True) |
|
102 |
|
103 for tbID, actions in list(self.__manager.toolBarsActions().items()): |
|
104 tb = self.__manager.toolBarById(tbID) |
|
105 default = self.__manager.isDefaultToolBar(tb) |
|
106 tbItem = E5ToolBarItem(tbID, [], default) |
|
107 self.__toolbarItems[id(tbItem)] = tbItem |
|
108 self.__toolBarItemToWidgetActionID[id(tbItem)] = [] |
|
109 actionIDs = [] |
|
110 for action in actions: |
|
111 if action is None: |
|
112 actionIDs.append(None) |
|
113 else: |
|
114 aID = id(action) |
|
115 actionIDs.append(aID) |
|
116 if aID in self.__widgetActionToToolBarItemID: |
|
117 self.__widgetActionToToolBarItemID[aID] = id(tbItem) |
|
118 self.__toolBarItemToWidgetActionID[id(tbItem)]\ |
|
119 .append(aID) |
|
120 tbItem.actionIDs = actionIDs |
|
121 self.toolbarComboBox.addItem(tb.windowTitle(), int(id(tbItem))) |
|
122 if default: |
|
123 self.toolbarComboBox.setItemData( |
|
124 self.toolbarComboBox.count() - 1, |
|
125 QColor(Qt.darkGreen), Qt.ForegroundRole) |
|
126 self.toolbarComboBox.model().sort(0) |
|
127 |
|
128 self.toolbarComboBox.currentIndexChanged[int].connect( |
|
129 self.__toolbarComboBox_currentIndexChanged) |
|
130 self.toolbarComboBox.setCurrentIndex(0) |
|
131 |
|
132 @pyqtSlot() |
|
133 def on_newButton_clicked(self): |
|
134 """ |
|
135 Private slot to create a new toolbar. |
|
136 """ |
|
137 name, ok = QInputDialog.getText( |
|
138 self, |
|
139 self.tr("New Toolbar"), |
|
140 self.tr("Toolbar Name:"), |
|
141 QLineEdit.Normal) |
|
142 if ok and name: |
|
143 if self.toolbarComboBox.findText(name) != -1: |
|
144 # toolbar with this name already exists |
|
145 E5MessageBox.critical( |
|
146 self, |
|
147 self.tr("New Toolbar"), |
|
148 self.tr( |
|
149 """A toolbar with the name <b>{0}</b> already""" |
|
150 """ exists.""") |
|
151 .format(name)) |
|
152 return |
|
153 |
|
154 tbItem = E5ToolBarItem(None, [], False) |
|
155 tbItem.title = name |
|
156 tbItem.isChanged = True |
|
157 self.__toolbarItems[id(tbItem)] = tbItem |
|
158 self.__toolBarItemToWidgetActionID[id(tbItem)] = [] |
|
159 self.toolbarComboBox.addItem(name, int(id(tbItem))) |
|
160 self.toolbarComboBox.model().sort(0) |
|
161 self.toolbarComboBox.setCurrentIndex( |
|
162 self.toolbarComboBox.findText(name)) |
|
163 |
|
164 @pyqtSlot() |
|
165 def on_removeButton_clicked(self): |
|
166 """ |
|
167 Private slot to remove a custom toolbar. |
|
168 """ |
|
169 name = self.toolbarComboBox.currentText() |
|
170 res = E5MessageBox.yesNo( |
|
171 self, |
|
172 self.tr("Remove Toolbar"), |
|
173 self.tr( |
|
174 """Should the toolbar <b>{0}</b> really be removed?""") |
|
175 .format(name)) |
|
176 if res: |
|
177 index = self.toolbarComboBox.currentIndex() |
|
178 tbItemID = self.toolbarComboBox.itemData(index) |
|
179 tbItem = self.__toolbarItems[tbItemID] |
|
180 if tbItem.toolBarId is not None and \ |
|
181 tbItem.toolBarId not in self.__removedToolBarIDs: |
|
182 self.__removedToolBarIDs.append(tbItem.toolBarId) |
|
183 del self.__toolbarItems[tbItemID] |
|
184 for widgetActionID in self.__toolBarItemToWidgetActionID[tbItemID]: |
|
185 self.__widgetActionToToolBarItemID[widgetActionID] = None |
|
186 del self.__toolBarItemToWidgetActionID[tbItemID] |
|
187 self.toolbarComboBox.removeItem(index) |
|
188 |
|
189 @pyqtSlot() |
|
190 def on_renameButton_clicked(self): |
|
191 """ |
|
192 Private slot to rename a custom toolbar. |
|
193 """ |
|
194 oldName = self.toolbarComboBox.currentText() |
|
195 newName, ok = QInputDialog.getText( |
|
196 self, |
|
197 self.tr("Rename Toolbar"), |
|
198 self.tr("New Toolbar Name:"), |
|
199 QLineEdit.Normal, |
|
200 oldName) |
|
201 if ok and newName: |
|
202 if oldName == newName: |
|
203 return |
|
204 if self.toolbarComboBox.findText(newName) != -1: |
|
205 # toolbar with this name already exists |
|
206 E5MessageBox.critical( |
|
207 self, |
|
208 self.tr("Rename Toolbar"), |
|
209 self.tr( |
|
210 """A toolbar with the name <b>{0}</b> already""" |
|
211 """ exists.""") |
|
212 .format(newName)) |
|
213 return |
|
214 index = self.toolbarComboBox.currentIndex() |
|
215 self.toolbarComboBox.setItemText(index, newName) |
|
216 tbItem = \ |
|
217 self.__toolbarItems[self.toolbarComboBox.itemData(index)] |
|
218 tbItem.title = newName |
|
219 tbItem.isChanged = True |
|
220 |
|
221 def __setupButtons(self): |
|
222 """ |
|
223 Private slot to set the buttons state. |
|
224 """ |
|
225 index = self.toolbarComboBox.currentIndex() |
|
226 if index > -1: |
|
227 itemID = self.toolbarComboBox.itemData(index) |
|
228 self.__currentToolBarItem = self.__toolbarItems[itemID] |
|
229 self.renameButton.setEnabled( |
|
230 not self.__currentToolBarItem.isDefault) |
|
231 self.removeButton.setEnabled( |
|
232 not self.__currentToolBarItem.isDefault) |
|
233 self.__restoreDefaultsButton.setEnabled( |
|
234 self.__currentToolBarItem.isDefault) |
|
235 self.__resetButton.setEnabled( |
|
236 self.__currentToolBarItem.toolBarId is not None) |
|
237 |
|
238 row = self.toolbarActionsList.currentRow() |
|
239 self.upButton.setEnabled(row > 0) |
|
240 self.downButton.setEnabled(row < self.toolbarActionsList.count() - 1) |
|
241 self.leftButton.setEnabled(self.toolbarActionsList.count() > 0) |
|
242 rightEnable = self.actionsTree.currentItem().parent() is not None or \ |
|
243 self.actionsTree.currentItem().text(0) == self.__separatorText |
|
244 self.rightButton.setEnabled(rightEnable) |
|
245 |
|
246 @pyqtSlot(int) |
|
247 def __toolbarComboBox_currentIndexChanged(self, index): |
|
248 """ |
|
249 Private slot called upon a selection of the current toolbar. |
|
250 |
|
251 @param index index of the new current toolbar (integer) |
|
252 """ |
|
253 itemID = self.toolbarComboBox.itemData(index) |
|
254 self.__currentToolBarItem = self.__toolbarItems[itemID] |
|
255 self.toolbarActionsList.clear() |
|
256 for actionID in self.__currentToolBarItem.actionIDs: |
|
257 item = QListWidgetItem(self.toolbarActionsList) |
|
258 if actionID is None: |
|
259 item.setText(self.__separatorText) |
|
260 else: |
|
261 action = self.__manager.actionById(actionID) |
|
262 item.setText(action.text()) |
|
263 item.setIcon(action.icon()) |
|
264 item.setTextAlignment(Qt.AlignLeft | Qt.AlignVCenter) |
|
265 item.setData(E5ToolBarDialog.ActionIdRole, int(id(action))) |
|
266 item.setData(E5ToolBarDialog.WidgetActionRole, False) |
|
267 if self.__manager.isWidgetAction(action): |
|
268 item.setData(E5ToolBarDialog.WidgetActionRole, True) |
|
269 item.setData(Qt.TextColorRole, QColor(Qt.blue)) |
|
270 self.toolbarActionsList.setCurrentRow(0) |
|
271 |
|
272 self.__setupButtons() |
|
273 |
|
274 @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem) |
|
275 def on_actionsTree_currentItemChanged(self, current, previous): |
|
276 """ |
|
277 Private slot called, when the currently selected action changes. |
|
278 |
|
279 @param current reference to the current item (QTreeWidgetItem) |
|
280 @param previous reference to the previous current item |
|
281 (QTreeWidgetItem) |
|
282 """ |
|
283 self.__setupButtons() |
|
284 |
|
285 @pyqtSlot(QListWidgetItem, QListWidgetItem) |
|
286 def on_toolbarActionsList_currentItemChanged(self, current, previous): |
|
287 """ |
|
288 Private slot to handle a change of the current item. |
|
289 |
|
290 @param current reference to the current item (QListWidgetItem) |
|
291 @param previous reference to the previous current item |
|
292 (QListWidgetItem) |
|
293 """ |
|
294 self.__setupButtons() |
|
295 |
|
296 @pyqtSlot() |
|
297 def on_upButton_clicked(self): |
|
298 """ |
|
299 Private slot used to move an action up in the list. |
|
300 """ |
|
301 row = self.toolbarActionsList.currentRow() |
|
302 if row == 0: |
|
303 # we're already at the top |
|
304 return |
|
305 |
|
306 actionID = self.__currentToolBarItem.actionIDs.pop(row) |
|
307 self.__currentToolBarItem.actionIDs.insert(row - 1, actionID) |
|
308 self.__currentToolBarItem.isChanged = True |
|
309 itm = self.toolbarActionsList.takeItem(row) |
|
310 self.toolbarActionsList.insertItem(row - 1, itm) |
|
311 self.toolbarActionsList.setCurrentItem(itm) |
|
312 self.__setupButtons() |
|
313 |
|
314 @pyqtSlot() |
|
315 def on_downButton_clicked(self): |
|
316 """ |
|
317 Private slot used to move an action down in the list. |
|
318 """ |
|
319 row = self.toolbarActionsList.currentRow() |
|
320 if row == self.toolbarActionsList.count() - 1: |
|
321 # we're already at the end |
|
322 return |
|
323 |
|
324 actionID = self.__currentToolBarItem.actionIDs.pop(row) |
|
325 self.__currentToolBarItem.actionIDs.insert(row + 1, actionID) |
|
326 self.__currentToolBarItem.isChanged = True |
|
327 itm = self.toolbarActionsList.takeItem(row) |
|
328 self.toolbarActionsList.insertItem(row + 1, itm) |
|
329 self.toolbarActionsList.setCurrentItem(itm) |
|
330 self.__setupButtons() |
|
331 |
|
332 @pyqtSlot() |
|
333 def on_leftButton_clicked(self): |
|
334 """ |
|
335 Private slot to delete an action from the list. |
|
336 """ |
|
337 row = self.toolbarActionsList.currentRow() |
|
338 actionID = self.__currentToolBarItem.actionIDs.pop(row) |
|
339 self.__currentToolBarItem.isChanged = True |
|
340 if actionID in self.__widgetActionToToolBarItemID: |
|
341 self.__widgetActionToToolBarItemID[actionID] = None |
|
342 self.__toolBarItemToWidgetActionID[id(self.__currentToolBarItem)]\ |
|
343 .remove(actionID) |
|
344 itm = self.toolbarActionsList.takeItem(row) |
|
345 del itm |
|
346 self.toolbarActionsList.setCurrentRow(row) |
|
347 self.__setupButtons() |
|
348 |
|
349 @pyqtSlot() |
|
350 def on_rightButton_clicked(self): |
|
351 """ |
|
352 Private slot to add an action to the list. |
|
353 """ |
|
354 row = self.toolbarActionsList.currentRow() + 1 |
|
355 |
|
356 item = QListWidgetItem() |
|
357 if self.actionsTree.currentItem().text(0) == self.__separatorText: |
|
358 item.setText(self.__separatorText) |
|
359 actionID = None |
|
360 else: |
|
361 actionID = self.actionsTree.currentItem()\ |
|
362 .data(0, E5ToolBarDialog.ActionIdRole) |
|
363 action = self.__manager.actionById(actionID) |
|
364 item.setText(action.text()) |
|
365 item.setIcon(action.icon()) |
|
366 item.setTextAlignment(Qt.AlignLeft | Qt.AlignVCenter) |
|
367 item.setData(E5ToolBarDialog.ActionIdRole, int(id(action))) |
|
368 item.setData(E5ToolBarDialog.WidgetActionRole, False) |
|
369 if self.__manager.isWidgetAction(action): |
|
370 item.setData(E5ToolBarDialog.WidgetActionRole, True) |
|
371 item.setData(Qt.TextColorRole, QColor(Qt.blue)) |
|
372 oldTbItemID = self.__widgetActionToToolBarItemID[actionID] |
|
373 if oldTbItemID is not None: |
|
374 self.__toolbarItems[oldTbItemID].actionIDs.remove(actionID) |
|
375 self.__toolbarItems[oldTbItemID].isChanged = True |
|
376 self.__toolBarItemToWidgetActionID[oldTbItemID]\ |
|
377 .remove(actionID) |
|
378 self.__widgetActionToToolBarItemID[actionID] = \ |
|
379 id(self.__currentToolBarItem) |
|
380 self.__toolBarItemToWidgetActionID[ |
|
381 id(self.__currentToolBarItem)].append(actionID) |
|
382 self.toolbarActionsList.insertItem(row, item) |
|
383 self.__currentToolBarItem.actionIDs.insert(row, actionID) |
|
384 self.__currentToolBarItem.isChanged = True |
|
385 self.toolbarActionsList.setCurrentRow(row) |
|
386 self.__setupButtons() |
|
387 |
|
388 @pyqtSlot(QAbstractButton) |
|
389 def on_buttonBox_clicked(self, button): |
|
390 """ |
|
391 Private slot called, when a button of the button box was clicked. |
|
392 |
|
393 @param button reference to the button clicked (QAbstractButton) |
|
394 """ |
|
395 if button == self.buttonBox.button(QDialogButtonBox.Cancel): |
|
396 self.reject() |
|
397 elif button == self.buttonBox.button(QDialogButtonBox.Apply): |
|
398 self.__saveToolBars() |
|
399 self.__setupButtons() |
|
400 elif button == self.buttonBox.button(QDialogButtonBox.Ok): |
|
401 self.__saveToolBars() |
|
402 self.accept() |
|
403 elif button == self.buttonBox.button(QDialogButtonBox.Reset): |
|
404 self.__resetCurrentToolbar() |
|
405 self.__setupButtons() |
|
406 elif button == self.buttonBox.button(QDialogButtonBox.RestoreDefaults): |
|
407 self.__restoreCurrentToolbarToDefault() |
|
408 self.__setupButtons() |
|
409 |
|
410 def __saveToolBars(self): |
|
411 """ |
|
412 Private method to save the configured toolbars. |
|
413 |
|
414 @exception RuntimeError raised to indicate an invalid action |
|
415 """ |
|
416 # step 1: remove toolbars marked for deletion |
|
417 for tbID in self.__removedToolBarIDs: |
|
418 tb = self.__manager.toolBarById(tbID) |
|
419 self.__manager.deleteToolBar(tb) |
|
420 self.__removedToolBarIDs = [] |
|
421 |
|
422 # step 2: save configured toolbars |
|
423 for tbItem in list(self.__toolbarItems.values()): |
|
424 if not tbItem.isChanged: |
|
425 continue |
|
426 |
|
427 if tbItem.toolBarId is None: |
|
428 # new custom toolbar |
|
429 tb = self.__manager.createToolBar(tbItem.title) |
|
430 tbItem.toolBarId = id(tb) |
|
431 else: |
|
432 tb = self.__manager.toolBarById(tbItem.toolBarId) |
|
433 if not tbItem.isDefault and tbItem.title: |
|
434 self.__manager.renameToolBar(tb, tbItem.title) |
|
435 |
|
436 actions = [] |
|
437 for actionID in tbItem.actionIDs: |
|
438 if actionID is None: |
|
439 actions.append(None) |
|
440 else: |
|
441 action = self.__manager.actionById(actionID) |
|
442 if action is None: |
|
443 raise RuntimeError( |
|
444 "No such action, id: 0x{0:x}".format(actionID)) |
|
445 actions.append(action) |
|
446 self.__manager.setToolBar(tb, actions) |
|
447 tbItem.isChanged = False |
|
448 |
|
449 def __restoreCurrentToolbar(self, actions): |
|
450 """ |
|
451 Private methdo to restore the current toolbar to the given list of |
|
452 actions. |
|
453 |
|
454 @param actions list of actions to set for the current toolbar |
|
455 (list of QAction) |
|
456 """ |
|
457 tbItemID = id(self.__currentToolBarItem) |
|
458 for widgetActionID in self.__toolBarItemToWidgetActionID[tbItemID]: |
|
459 self.__widgetActionToToolBarItemID[widgetActionID] = None |
|
460 self.__toolBarItemToWidgetActionID[tbItemID] = [] |
|
461 self.__currentToolBarItem.actionIDs = [] |
|
462 |
|
463 for action in actions: |
|
464 if action is None: |
|
465 self.__currentToolBarItem.actionIDs.append(None) |
|
466 else: |
|
467 actionID = id(action) |
|
468 self.__currentToolBarItem.actionIDs.append(actionID) |
|
469 if actionID in self.__widgetActionToToolBarItemID: |
|
470 oldTbItemID = self.__widgetActionToToolBarItemID[actionID] |
|
471 if oldTbItemID is not None: |
|
472 self.__toolbarItems[oldTbItemID].actionIDs.remove( |
|
473 actionID) |
|
474 self.__toolbarItems[oldTbItemID].isChanged = True |
|
475 self.__toolBarItemToWidgetActionID[oldTbItemID].remove( |
|
476 actionID) |
|
477 self.__widgetActionToToolBarItemID[actionID] = tbItemID |
|
478 self.__toolBarItemToWidgetActionID[tbItemID].append( |
|
479 actionID) |
|
480 self.__toolbarComboBox_currentIndexChanged( |
|
481 self.toolbarComboBox.currentIndex()) |
|
482 |
|
483 def __resetCurrentToolbar(self): |
|
484 """ |
|
485 Private method to revert all changes made to the current toolbar. |
|
486 """ |
|
487 tbID = self.__currentToolBarItem.toolBarId |
|
488 actions = self.__manager.toolBarActions(tbID) |
|
489 self.__restoreCurrentToolbar(actions) |
|
490 self.__currentToolBarItem.isChanged = False |
|
491 |
|
492 def __restoreCurrentToolbarToDefault(self): |
|
493 """ |
|
494 Private method to set the current toolbar to its default configuration. |
|
495 """ |
|
496 if not self.__currentToolBarItem.isDefault: |
|
497 return |
|
498 |
|
499 tbID = self.__currentToolBarItem.toolBarId |
|
500 actions = self.__manager.defaultToolBarActions(tbID) |
|
501 self.__restoreCurrentToolbar(actions) |
|
502 self.__currentToolBarItem.isChanged = True |