eric7/Preferences/ShortcutsDialog.py

branch
eric7
changeset 8312
800c432b34c8
parent 8218
7c09585bd960
child 8318
962bce857696
equal deleted inserted replaced
8311:4e8b98454baa 8312:800c432b34c8
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2003 - 2021 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 PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt
14 from PyQt5.QtGui import QKeySequence
15 from PyQt5.QtWidgets import QHeaderView, QDialog, QTreeWidgetItem
16
17 from E5Gui.E5Application import e5App
18 from E5Gui import E5MessageBox
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 (E5Action)
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 = e5App().getObject("PluginManager")
129 pm.initOnDemandPlugins()
130
131 # populate the various lists
132 self.projectItem = self.__generateCategoryItem(self.tr("Project"))
133 for act in e5App().getObject("Project").getActions():
134 self.__generateShortcutItem(self.projectItem, act)
135
136 self.uiItem = self.__generateCategoryItem(self.tr("General"))
137 for act in e5App().getObject("UserInterface").getActions('ui'):
138 self.__generateShortcutItem(self.uiItem, act)
139
140 self.wizardsItem = self.__generateCategoryItem(self.tr("Wizards"))
141 for act in (
142 e5App().getObject("UserInterface").getActions('wizards')
143 ):
144 self.__generateShortcutItem(self.wizardsItem, act)
145
146 self.debugItem = self.__generateCategoryItem(self.tr("Debug"))
147 for act in e5App().getObject("DebugUI").getActions():
148 self.__generateShortcutItem(self.debugItem, act)
149
150 self.editItem = self.__generateCategoryItem(self.tr("Edit"))
151 for act in e5App().getObject("ViewManager").getActions('edit'):
152 self.__generateShortcutItem(self.editItem, act)
153
154 self.fileItem = self.__generateCategoryItem(self.tr("File"))
155 for act in e5App().getObject("ViewManager").getActions('file'):
156 self.__generateShortcutItem(self.fileItem, act)
157
158 self.searchItem = self.__generateCategoryItem(self.tr("Search"))
159 for act in e5App().getObject("ViewManager").getActions('search'):
160 self.__generateShortcutItem(self.searchItem, act)
161
162 self.viewItem = self.__generateCategoryItem(self.tr("View"))
163 for act in e5App().getObject("ViewManager").getActions('view'):
164 self.__generateShortcutItem(self.viewItem, act)
165
166 self.macroItem = self.__generateCategoryItem(self.tr("Macro"))
167 for act in e5App().getObject("ViewManager").getActions('macro'):
168 self.__generateShortcutItem(self.macroItem, act)
169
170 self.bookmarkItem = self.__generateCategoryItem(
171 self.tr("Bookmarks"))
172 for act in e5App().getObject("ViewManager").getActions('bookmark'):
173 self.__generateShortcutItem(self.bookmarkItem, act)
174
175 self.spellingItem = self.__generateCategoryItem(
176 self.tr("Spelling"))
177 for act in e5App().getObject("ViewManager").getActions('spelling'):
178 self.__generateShortcutItem(self.spellingItem, act)
179
180 actions = e5App().getObject("ViewManager").getActions('window')
181 if actions:
182 self.windowItem = self.__generateCategoryItem(
183 self.tr("Window"))
184 for act in actions:
185 self.__generateShortcutItem(self.windowItem, act)
186
187 self.pluginCategoryItems = []
188 for category, ref in e5App().getPluginObjects():
189 if hasattr(ref, "getActions"):
190 categoryItem = self.__generateCategoryItem(category)
191 objectType = e5App().getPluginObjectType(category)
192 for act in ref.getActions():
193 self.__generateShortcutItem(categoryItem, act,
194 objectType=objectType)
195 self.pluginCategoryItems.append(categoryItem)
196
197 else:
198 self.helpViewerItem = self.__generateCategoryItem(
199 self.tr("eric Web Browser"))
200 for act in helpViewer.getActions():
201 self.__generateShortcutItem(self.helpViewerItem, act, True)
202
203 self.__resort()
204 self.__resizeColumns()
205
206 self.__editTopItem = None
207
208 def on_shortcutsList_itemDoubleClicked(self, itm, column):
209 """
210 Private slot to handle a double click in the shortcuts list.
211
212 @param itm the list item that was double clicked (QTreeWidgetItem)
213 @param column the list item was double clicked in (integer)
214 """
215 if itm.childCount():
216 return
217
218 self.__editTopItem = itm.parent()
219
220 self.shortcutDialog.setKeys(
221 QKeySequence(itm.text(1)),
222 QKeySequence(itm.text(2)),
223 itm.data(0, self.noCheckRole),
224 itm.data(0, self.objectTypeRole))
225 self.shortcutDialog.show()
226
227 def on_shortcutsList_itemClicked(self, itm, column):
228 """
229 Private slot to handle a click in the shortcuts list.
230
231 @param itm the list item that was clicked (QTreeWidgetItem)
232 @param column the list item was clicked in (integer)
233 """
234 if itm.childCount() or column not in [1, 2]:
235 return
236
237 self.shortcutsList.openPersistentEditor(itm, column)
238
239 def on_shortcutsList_itemChanged(self, itm, column):
240 """
241 Private slot to handle the edit of a shortcut key.
242
243 @param itm reference to the item changed (QTreeWidgetItem)
244 @param column column changed (integer)
245 """
246 if column != 0:
247 keystr = itm.text(column).title()
248 if (
249 not itm.data(0, self.noCheckRole) and
250 not self.__checkShortcut(QKeySequence(keystr),
251 itm.data(0, self.objectTypeRole),
252 itm.parent())
253 ):
254 itm.setText(column, "")
255 else:
256 itm.setText(column, keystr)
257 self.shortcutsList.closePersistentEditor(itm, column)
258
259 def __shortcutChanged(self, keysequence, altKeysequence, noCheck,
260 objectType):
261 """
262 Private slot to handle the shortcutChanged signal of the shortcut
263 dialog.
264
265 @param keysequence the keysequence of the changed action (QKeySequence)
266 @param altKeysequence the alternative keysequence of the changed
267 action (QKeySequence)
268 @param noCheck flag indicating that no uniqueness check should
269 be performed (boolean)
270 @param objectType type of the object (string).
271 """
272 if (
273 not noCheck and
274 (not self.__checkShortcut(
275 keysequence, objectType, self.__editTopItem) or
276 not self.__checkShortcut(
277 altKeysequence, objectType, self.__editTopItem))
278 ):
279 return
280
281 self.shortcutsList.currentItem().setText(1, keysequence.toString())
282 self.shortcutsList.currentItem().setText(2, altKeysequence.toString())
283
284 self.__resort()
285 self.__resizeColumns()
286
287 def __checkShortcut(self, keysequence, objectType, origTopItem):
288 """
289 Private method to check a keysequence for uniqueness.
290
291 @param keysequence the keysequence to check (QKeySequence)
292 @param objectType type of the object (string). Entries with the same
293 object type are not checked for uniqueness.
294 @param origTopItem refrence to the parent of the item to be checked
295 (QTreeWidgetItem)
296 @return flag indicating uniqueness (boolean)
297 """
298 if keysequence.isEmpty():
299 return True
300
301 keystr = keysequence.toString()
302 keyname = self.shortcutsList.currentItem().text(0)
303 for topIndex in range(self.shortcutsList.topLevelItemCount()):
304 topItem = self.shortcutsList.topLevelItem(topIndex)
305 for index in range(topItem.childCount()):
306 itm = topItem.child(index)
307
308 # 1. shall a check be performed?
309 if itm.data(0, self.noCheckRole):
310 continue
311
312 # 2. check object type
313 itmObjectType = itm.data(0, self.objectTypeRole)
314 if (
315 itmObjectType and
316 itmObjectType == objectType and
317 topItem != origTopItem
318 ):
319 continue
320
321 # 3. check key name
322 if itm.text(0) != keyname:
323 for col in [1, 2]:
324 # check against primary, then alternative binding
325 itmseq = itm.text(col)
326 # step 1: check if shortcut is already allocated
327 if keystr == itmseq:
328 res = E5MessageBox.yesNo(
329 self,
330 self.tr("Edit shortcuts"),
331 self.tr(
332 """<p><b>{0}</b> has already been"""
333 """ allocated to the <b>{1}</b> action. """
334 """Remove this binding?</p>""")
335 .format(keystr, itm.text(0)),
336 icon=E5MessageBox.Warning)
337 if res:
338 itm.setText(col, "")
339 return True
340 else:
341 return False
342
343 if not itmseq:
344 continue
345
346 # step 2: check if shortcut hides an already allocated
347 if itmseq.startswith("{0}+".format(keystr)):
348 res = E5MessageBox.yesNo(
349 self,
350 self.tr("Edit shortcuts"),
351 self.tr(
352 """<p><b>{0}</b> hides the <b>{1}</b>"""
353 """ action. Remove this binding?</p>""")
354 .format(keystr, itm.text(0)),
355 icon=E5MessageBox.Warning)
356 if res:
357 itm.setText(col, "")
358 return True
359 else:
360 return False
361
362 # step 3: check if shortcut is hidden by an
363 # already allocated
364 if keystr.startswith("{0}+".format(itmseq)):
365 res = E5MessageBox.yesNo(
366 self,
367 self.tr("Edit shortcuts"),
368 self.tr(
369 """<p><b>{0}</b> is hidden by the """
370 """<b>{1}</b> action. """
371 """Remove this binding?</p>""")
372 .format(keystr, itm.text(0)),
373 icon=E5MessageBox.Warning)
374 if res:
375 itm.setText(col, "")
376 return True
377 else:
378 return False
379
380 return True
381
382 def __saveCategoryActions(self, category, actions):
383 """
384 Private method to save the actions for a category.
385
386 @param category reference to the category item (QTreeWidgetItem)
387 @param actions list of actions for the category (list of E5Action)
388 """
389 for index in range(category.childCount()):
390 itm = category.child(index)
391 txt = itm.data(0, self.objectNameRole)
392 for act in actions:
393 if txt == act.objectName():
394 act.setShortcut(QKeySequence(itm.text(1)))
395 act.setAlternateShortcut(
396 QKeySequence(itm.text(2)), removeEmpty=True)
397 break
398
399 def on_buttonBox_accepted(self):
400 """
401 Private slot to handle the OK button press.
402 """
403 if self.__helpViewer is None:
404 self.__saveCategoryActions(
405 self.projectItem,
406 e5App().getObject("Project").getActions())
407 self.__saveCategoryActions(
408 self.uiItem,
409 e5App().getObject("UserInterface").getActions('ui'))
410 self.__saveCategoryActions(
411 self.wizardsItem,
412 e5App().getObject("UserInterface").getActions('wizards'))
413 self.__saveCategoryActions(
414 self.debugItem,
415 e5App().getObject("DebugUI").getActions())
416 self.__saveCategoryActions(
417 self.editItem,
418 e5App().getObject("ViewManager").getActions('edit'))
419 self.__saveCategoryActions(
420 self.fileItem,
421 e5App().getObject("ViewManager").getActions('file'))
422 self.__saveCategoryActions(
423 self.searchItem,
424 e5App().getObject("ViewManager").getActions('search'))
425 self.__saveCategoryActions(
426 self.viewItem,
427 e5App().getObject("ViewManager").getActions('view'))
428 self.__saveCategoryActions(
429 self.macroItem,
430 e5App().getObject("ViewManager").getActions('macro'))
431 self.__saveCategoryActions(
432 self.bookmarkItem,
433 e5App().getObject("ViewManager").getActions('bookmark'))
434 self.__saveCategoryActions(
435 self.spellingItem,
436 e5App().getObject("ViewManager").getActions('spelling'))
437
438 actions = e5App().getObject("ViewManager").getActions('window')
439 if actions:
440 self.__saveCategoryActions(self.windowItem, actions)
441
442 for categoryItem in self.pluginCategoryItems:
443 category = categoryItem.text(0)
444 ref = e5App().getPluginObject(category)
445 if ref is not None and hasattr(ref, "getActions"):
446 self.__saveCategoryActions(categoryItem, ref.getActions())
447
448 Shortcuts.saveShortcuts()
449
450 else:
451 self.__saveCategoryActions(
452 self.helpViewerItem, self.__helpViewer.getActions())
453 Shortcuts.saveShortcuts(helpViewer=self.__helpViewer)
454
455 Preferences.syncPreferences()
456
457 self.updateShortcuts.emit()
458 self.hide()
459
460 @pyqtSlot(str)
461 def on_searchEdit_textChanged(self, txt):
462 """
463 Private slot called, when the text in the search edit changes.
464
465 @param txt text of the search edit (string)
466 """
467 rx = re.compile(re.escape(txt), re.IGNORECASE)
468 for topIndex in range(self.shortcutsList.topLevelItemCount()):
469 topItem = self.shortcutsList.topLevelItem(topIndex)
470 childHiddenCount = 0
471 for index in range(topItem.childCount()):
472 itm = topItem.child(index)
473 if (
474 (self.actionButton.isChecked() and
475 rx.search(itm.text(0)) is not None) or
476 (self.shortcutButton.isChecked() and
477 txt.lower() not in itm.text(1).lower() and
478 txt.lower() not in itm.text(2).lower())
479 ):
480 itm.setHidden(True)
481 childHiddenCount += 1
482 else:
483 itm.setHidden(False)
484 topItem.setHidden(childHiddenCount == topItem.childCount())
485
486 @pyqtSlot(bool)
487 def on_actionButton_toggled(self, checked):
488 """
489 Private slot called, when the action radio button is toggled.
490
491 @param checked state of the action radio button (boolean)
492 """
493 if checked:
494 self.on_searchEdit_textChanged(self.searchEdit.text())
495
496 @pyqtSlot(bool)
497 def on_shortcutButton_toggled(self, checked):
498 """
499 Private slot called, when the shortcuts radio button is toggled.
500
501 @param checked state of the shortcuts radio button (boolean)
502 """
503 if checked:
504 self.on_searchEdit_textChanged(self.searchEdit.text())

eric ide

mercurial