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