eric6/Preferences/ShortcutsDialog.py

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

eric ide

mercurial