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