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