src/eric7/Preferences/ShortcutsDialog.py

branch
eric7
changeset 9209
b99e7fd55fd3
parent 8881
54e42bc2437a
child 9221
bf71ee032bb4
equal deleted inserted replaced
9208:3fc8dfeb6ebe 9209:b99e7fd55fd3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2003 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing a dialog for the configuration of eric's keyboard
8 shortcuts.
9 """
10
11 import re
12
13 from PyQt6.QtCore import pyqtSignal, pyqtSlot, Qt
14 from PyQt6.QtGui import QKeySequence
15 from PyQt6.QtWidgets import QHeaderView, QDialog, QTreeWidgetItem
16
17 from EricWidgets.EricApplication import ericApp
18 from EricWidgets import EricMessageBox
19
20 from .Ui_ShortcutsDialog import Ui_ShortcutsDialog
21
22 import Preferences
23 from Preferences import Shortcuts
24
25
26 class ShortcutsDialog(QDialog, Ui_ShortcutsDialog):
27 """
28 Class implementing a dialog for the configuration of eric's keyboard
29 shortcuts.
30
31 @signal updateShortcuts() emitted when the user pressed the dialogs OK
32 button
33 """
34 updateShortcuts = pyqtSignal()
35
36 objectNameRole = Qt.ItemDataRole.UserRole
37 noCheckRole = Qt.ItemDataRole.UserRole + 1
38 objectTypeRole = Qt.ItemDataRole.UserRole + 2
39
40 def __init__(self, parent=None):
41 """
42 Constructor
43
44 @param parent parent widget of this dialog
45 @type QWidget
46 """
47 super().__init__(parent)
48 self.setupUi(self)
49 self.setWindowFlags(Qt.WindowType.Window)
50
51 self.__helpViewer = None
52
53 self.shortcutsList.headerItem().setText(
54 self.shortcutsList.columnCount(), "")
55 self.shortcutsList.header().setSortIndicator(
56 0, Qt.SortOrder.AscendingOrder)
57
58 from .ShortcutDialog import ShortcutDialog
59 self.shortcutDialog = ShortcutDialog()
60 self.shortcutDialog.shortcutChanged.connect(self.__shortcutChanged)
61
62 def __resort(self):
63 """
64 Private method to resort the tree.
65 """
66 self.shortcutsList.sortItems(
67 self.shortcutsList.sortColumn(),
68 self.shortcutsList.header().sortIndicatorOrder())
69
70 def __resizeColumns(self):
71 """
72 Private method to resize the list columns.
73 """
74 self.shortcutsList.header().resizeSections(
75 QHeaderView.ResizeMode.ResizeToContents)
76 self.shortcutsList.header().setStretchLastSection(True)
77
78 def __generateCategoryItem(self, title):
79 """
80 Private method to generate a category item.
81
82 @param title title for the item (string)
83 @return reference to the category item (QTreeWidgetItem)
84 """
85 itm = QTreeWidgetItem(self.shortcutsList, [title])
86 itm.setExpanded(True)
87 return itm
88
89 def __generateShortcutItem(self, category, action,
90 noCheck=False, objectType=""):
91 """
92 Private method to generate a keyboard shortcut item.
93
94 @param category reference to the category item (QTreeWidgetItem)
95 @param action reference to the keyboard action (EricAction)
96 @param noCheck flag indicating that no uniqueness check should
97 be performed (boolean)
98 @param objectType type of the object (string). Objects of the same
99 type are not checked for duplicate shortcuts.
100 """
101 itm = QTreeWidgetItem(
102 category,
103 [action.iconText(), action.shortcut().toString(),
104 action.alternateShortcut().toString()])
105 itm.setIcon(0, action.icon())
106 itm.setData(0, self.objectNameRole, action.objectName())
107 itm.setData(0, self.noCheckRole, noCheck)
108 if objectType:
109 itm.setData(0, self.objectTypeRole, objectType)
110 else:
111 itm.setData(0, self.objectTypeRole, None)
112
113 def populate(self, helpViewer=None):
114 """
115 Public method to populate the dialog.
116
117 @param helpViewer reference to the help window object
118 """
119 self.searchEdit.clear()
120 self.searchEdit.setFocus()
121 self.shortcutsList.clear()
122 self.actionButton.setChecked(True)
123
124 self.__helpViewer = helpViewer
125
126 if helpViewer is None:
127 # let the plugin manager create on demand plugin objects
128 pm = ericApp().getObject("PluginManager")
129 pm.initOnDemandPlugins()
130
131 # populate the various lists
132 self.projectItem = self.__generateCategoryItem(self.tr("Project"))
133 for act in ericApp().getObject("Project").getActions():
134 self.__generateShortcutItem(self.projectItem, act)
135
136 self.uiItem = self.__generateCategoryItem(self.tr("General"))
137 for act in ericApp().getObject("UserInterface").getActions('ui'):
138 self.__generateShortcutItem(self.uiItem, act)
139
140 self.wizardsItem = self.__generateCategoryItem(self.tr("Wizards"))
141 for act in (
142 ericApp().getObject("UserInterface").getActions('wizards')
143 ):
144 self.__generateShortcutItem(self.wizardsItem, act)
145
146 self.debugItem = self.__generateCategoryItem(self.tr("Debug"))
147 for act in ericApp().getObject("DebugUI").getActions():
148 self.__generateShortcutItem(self.debugItem, act)
149
150 self.editItem = self.__generateCategoryItem(self.tr("Edit"))
151 for act in ericApp().getObject("ViewManager").getActions('edit'):
152 self.__generateShortcutItem(self.editItem, act)
153
154 self.fileItem = self.__generateCategoryItem(self.tr("File"))
155 for act in ericApp().getObject("ViewManager").getActions('file'):
156 self.__generateShortcutItem(self.fileItem, act)
157
158 self.searchItem = self.__generateCategoryItem(self.tr("Search"))
159 for act in ericApp().getObject("ViewManager").getActions('search'):
160 self.__generateShortcutItem(self.searchItem, act)
161
162 self.viewItem = self.__generateCategoryItem(self.tr("View"))
163 for act in ericApp().getObject("ViewManager").getActions('view'):
164 self.__generateShortcutItem(self.viewItem, act)
165
166 self.macroItem = self.__generateCategoryItem(self.tr("Macro"))
167 for act in ericApp().getObject("ViewManager").getActions('macro'):
168 self.__generateShortcutItem(self.macroItem, act)
169
170 self.bookmarkItem = self.__generateCategoryItem(
171 self.tr("Bookmarks"))
172 for act in (
173 ericApp().getObject("ViewManager").getActions('bookmark')
174 ):
175 self.__generateShortcutItem(self.bookmarkItem, act)
176
177 self.spellingItem = self.__generateCategoryItem(
178 self.tr("Spelling"))
179 for act in (
180 ericApp().getObject("ViewManager").getActions('spelling')
181 ):
182 self.__generateShortcutItem(self.spellingItem, act)
183
184 actions = ericApp().getObject("ViewManager").getActions('window')
185 if actions:
186 self.windowItem = self.__generateCategoryItem(
187 self.tr("Window"))
188 for act in actions:
189 self.__generateShortcutItem(self.windowItem, act)
190
191 self.pluginCategoryItems = []
192 for category, ref in ericApp().getPluginObjects():
193 if hasattr(ref, "getActions"):
194 categoryItem = self.__generateCategoryItem(category)
195 objectType = ericApp().getPluginObjectType(category)
196 for act in ref.getActions():
197 self.__generateShortcutItem(categoryItem, act,
198 objectType=objectType)
199 self.pluginCategoryItems.append(categoryItem)
200
201 else:
202 self.helpViewerItem = self.__generateCategoryItem(
203 self.tr("eric Web Browser"))
204 for act in helpViewer.getActions():
205 self.__generateShortcutItem(self.helpViewerItem, act, True)
206
207 self.__resort()
208 self.__resizeColumns()
209
210 self.__editTopItem = None
211
212 def on_shortcutsList_itemDoubleClicked(self, itm, column):
213 """
214 Private slot to handle a double click in the shortcuts list.
215
216 @param itm the list item that was double clicked (QTreeWidgetItem)
217 @param column the list item was double clicked in (integer)
218 """
219 if itm.childCount():
220 return
221
222 self.__editTopItem = itm.parent()
223
224 self.shortcutDialog.setKeys(
225 QKeySequence(itm.text(1)),
226 QKeySequence(itm.text(2)),
227 itm.data(0, self.noCheckRole),
228 itm.data(0, self.objectTypeRole))
229 self.shortcutDialog.show()
230
231 def on_shortcutsList_itemClicked(self, itm, column):
232 """
233 Private slot to handle a click in the shortcuts list.
234
235 @param itm the list item that was clicked (QTreeWidgetItem)
236 @param column the list item was clicked in (integer)
237 """
238 if itm.childCount() or column not in [1, 2]:
239 return
240
241 self.shortcutsList.openPersistentEditor(itm, column)
242
243 def on_shortcutsList_itemChanged(self, itm, column):
244 """
245 Private slot to handle the edit of a shortcut key.
246
247 @param itm reference to the item changed (QTreeWidgetItem)
248 @param column column changed (integer)
249 """
250 if column != 0:
251 keystr = itm.text(column).title()
252 if (
253 not itm.data(0, self.noCheckRole) and
254 not self.__checkShortcut(QKeySequence(keystr),
255 itm.data(0, self.objectTypeRole),
256 itm.parent())
257 ):
258 itm.setText(column, "")
259 else:
260 itm.setText(column, keystr)
261 self.shortcutsList.closePersistentEditor(itm, column)
262
263 def __shortcutChanged(self, keysequence, altKeysequence, noCheck,
264 objectType):
265 """
266 Private slot to handle the shortcutChanged signal of the shortcut
267 dialog.
268
269 @param keysequence the keysequence of the changed action (QKeySequence)
270 @param altKeysequence the alternative keysequence of the changed
271 action (QKeySequence)
272 @param noCheck flag indicating that no uniqueness check should
273 be performed (boolean)
274 @param objectType type of the object (string).
275 """
276 if (
277 not noCheck and
278 (not self.__checkShortcut(
279 keysequence, objectType, self.__editTopItem) or
280 not self.__checkShortcut(
281 altKeysequence, objectType, self.__editTopItem))
282 ):
283 return
284
285 self.shortcutsList.currentItem().setText(1, keysequence.toString())
286 self.shortcutsList.currentItem().setText(2, altKeysequence.toString())
287
288 self.__resort()
289 self.__resizeColumns()
290
291 def __checkShortcut(self, keysequence, objectType, origTopItem):
292 """
293 Private method to check a keysequence for uniqueness.
294
295 @param keysequence the keysequence to check (QKeySequence)
296 @param objectType type of the object (string). Entries with the same
297 object type are not checked for uniqueness.
298 @param origTopItem refrence to the parent of the item to be checked
299 (QTreeWidgetItem)
300 @return flag indicating uniqueness (boolean)
301 """
302 if keysequence.isEmpty():
303 return True
304
305 keystr = keysequence.toString()
306 keyname = self.shortcutsList.currentItem().text(0)
307 for topIndex in range(self.shortcutsList.topLevelItemCount()):
308 topItem = self.shortcutsList.topLevelItem(topIndex)
309 for index in range(topItem.childCount()):
310 itm = topItem.child(index)
311
312 # 1. shall a check be performed?
313 if itm.data(0, self.noCheckRole):
314 continue
315
316 # 2. check object type
317 itmObjectType = itm.data(0, self.objectTypeRole)
318 if (
319 itmObjectType and
320 itmObjectType == objectType and
321 topItem != origTopItem
322 ):
323 continue
324
325 # 3. check key name
326 if itm.text(0) != keyname:
327 for col in [1, 2]:
328 # check against primary, then alternative binding
329 itmseq = itm.text(col)
330 # step 1: check if shortcut is already allocated
331 if keystr == itmseq:
332 res = EricMessageBox.yesNo(
333 self,
334 self.tr("Edit shortcuts"),
335 self.tr(
336 """<p><b>{0}</b> has already been"""
337 """ allocated to the <b>{1}</b> action. """
338 """Remove this binding?</p>""")
339 .format(keystr, itm.text(0)),
340 icon=EricMessageBox.Warning)
341 if res:
342 itm.setText(col, "")
343 return True
344 else:
345 return False
346
347 if not itmseq:
348 continue
349
350 # step 2: check if shortcut hides an already allocated
351 if itmseq.startswith("{0}+".format(keystr)):
352 res = EricMessageBox.yesNo(
353 self,
354 self.tr("Edit shortcuts"),
355 self.tr(
356 """<p><b>{0}</b> hides the <b>{1}</b>"""
357 """ action. Remove this binding?</p>""")
358 .format(keystr, itm.text(0)),
359 icon=EricMessageBox.Warning)
360 if res:
361 itm.setText(col, "")
362 return True
363 else:
364 return False
365
366 # step 3: check if shortcut is hidden by an
367 # already allocated
368 if keystr.startswith("{0}+".format(itmseq)):
369 res = EricMessageBox.yesNo(
370 self,
371 self.tr("Edit shortcuts"),
372 self.tr(
373 """<p><b>{0}</b> is hidden by the """
374 """<b>{1}</b> action. """
375 """Remove this binding?</p>""")
376 .format(keystr, itm.text(0)),
377 icon=EricMessageBox.Warning)
378 if res:
379 itm.setText(col, "")
380 return True
381 else:
382 return False
383
384 return True
385
386 def __saveCategoryActions(self, category, actions):
387 """
388 Private method to save the actions for a category.
389
390 @param category reference to the category item (QTreeWidgetItem)
391 @param actions list of actions for the category (list of EricAction)
392 """
393 for index in range(category.childCount()):
394 itm = category.child(index)
395 txt = itm.data(0, self.objectNameRole)
396 for act in actions:
397 if txt == act.objectName():
398 act.setShortcut(QKeySequence(itm.text(1)))
399 act.setAlternateShortcut(
400 QKeySequence(itm.text(2)), removeEmpty=True)
401 break
402
403 def on_buttonBox_accepted(self):
404 """
405 Private slot to handle the OK button press.
406 """
407 if self.__helpViewer is None:
408 self.__saveCategoryActions(
409 self.projectItem,
410 ericApp().getObject("Project").getActions())
411 self.__saveCategoryActions(
412 self.uiItem,
413 ericApp().getObject("UserInterface").getActions('ui'))
414 self.__saveCategoryActions(
415 self.wizardsItem,
416 ericApp().getObject("UserInterface").getActions('wizards'))
417 self.__saveCategoryActions(
418 self.debugItem,
419 ericApp().getObject("DebugUI").getActions())
420 self.__saveCategoryActions(
421 self.editItem,
422 ericApp().getObject("ViewManager").getActions('edit'))
423 self.__saveCategoryActions(
424 self.fileItem,
425 ericApp().getObject("ViewManager").getActions('file'))
426 self.__saveCategoryActions(
427 self.searchItem,
428 ericApp().getObject("ViewManager").getActions('search'))
429 self.__saveCategoryActions(
430 self.viewItem,
431 ericApp().getObject("ViewManager").getActions('view'))
432 self.__saveCategoryActions(
433 self.macroItem,
434 ericApp().getObject("ViewManager").getActions('macro'))
435 self.__saveCategoryActions(
436 self.bookmarkItem,
437 ericApp().getObject("ViewManager").getActions('bookmark'))
438 self.__saveCategoryActions(
439 self.spellingItem,
440 ericApp().getObject("ViewManager").getActions('spelling'))
441
442 actions = ericApp().getObject("ViewManager").getActions('window')
443 if actions:
444 self.__saveCategoryActions(self.windowItem, actions)
445
446 for categoryItem in self.pluginCategoryItems:
447 category = categoryItem.text(0)
448 ref = ericApp().getPluginObject(category)
449 if ref is not None and hasattr(ref, "getActions"):
450 self.__saveCategoryActions(categoryItem, ref.getActions())
451
452 Shortcuts.saveShortcuts()
453
454 else:
455 self.__saveCategoryActions(
456 self.helpViewerItem, self.__helpViewer.getActions())
457 Shortcuts.saveShortcuts(helpViewer=self.__helpViewer)
458
459 Preferences.syncPreferences()
460
461 self.updateShortcuts.emit()
462 self.hide()
463
464 @pyqtSlot(str)
465 def on_searchEdit_textChanged(self, txt):
466 """
467 Private slot called, when the text in the search edit changes.
468
469 @param txt text of the search edit (string)
470 """
471 rx = re.compile(re.escape(txt), re.IGNORECASE)
472 for topIndex in range(self.shortcutsList.topLevelItemCount()):
473 topItem = self.shortcutsList.topLevelItem(topIndex)
474 childHiddenCount = 0
475 for index in range(topItem.childCount()):
476 itm = topItem.child(index)
477 if (
478 txt and (
479 (self.actionButton.isChecked() and
480 rx.search(itm.text(0)) is None) or
481 (self.shortcutButton.isChecked() and
482 txt.lower() not in itm.text(1).lower() and
483 txt.lower() not in itm.text(2).lower())
484 )
485 ):
486 itm.setHidden(True)
487 childHiddenCount += 1
488 else:
489 itm.setHidden(False)
490 topItem.setHidden(childHiddenCount == topItem.childCount())
491
492 @pyqtSlot(bool)
493 def on_actionButton_toggled(self, checked):
494 """
495 Private slot called, when the action radio button is toggled.
496
497 @param checked state of the action radio button (boolean)
498 """
499 if checked:
500 self.on_searchEdit_textChanged(self.searchEdit.text())
501
502 @pyqtSlot(bool)
503 def on_shortcutButton_toggled(self, checked):
504 """
505 Private slot called, when the shortcuts radio button is toggled.
506
507 @param checked state of the shortcuts radio button (boolean)
508 """
509 if checked:
510 self.on_searchEdit_textChanged(self.searchEdit.text())

eric ide

mercurial