Merge lp:~googol-deactivatedaccount/openlp/bug-768495 into lp:openlp

Proposed by Andreas Preikschat
Status: Merged
Approved by: Raoul Snyman
Approved revision: 1826
Merged at revision: 1840
Proposed branch: lp:~googol-deactivatedaccount/openlp/bug-768495
Merge into: lp:openlp
Diff against target: 173 lines (+102/-7)
2 files modified
openlp/core/ui/shortcutlistform.py (+5/-2)
openlp/core/utils/actions.py (+97/-5)
To merge this branch: bzr merge lp:~googol-deactivatedaccount/openlp/bug-768495
Reviewer Review Type Date Requested Status
Raoul Snyman Approve
Tim Bentley Approve
Review via email: mp+85891@code.launchpad.net

This proposal supersedes a proposal from 2011-12-14.

Commit message

bzr commit -m "fixed bug #768495 (Shortcut can be assigned twice in certain circumstances)" --fixes lp:768495

Description of the change

Hello,

Fixed bug 768495 (Shortcut can be assigned twice in certain circumstances)

Description of the fix:
1) Create a dict with the shortcuts as keys and a list of actions using this shortcut as value.
2) When we add a new action/shortcut we check if the shortcut is valid. If this is the case we add it to the dict and if not we remove the shortcut.

Possible space for improvements:
- notify the user that a shortcut has been disabled
- make sure that shortcuts with lower priority are disabled if there should be a conflict (currently we remove the shortcut from the last actions)

How to test this?
1) Assign "R" to the "Re-index tool" action
2) Disable the songs plugin
3) Assign "R" to another action
4) Enable the songs plugin

To post a comment you must log in.
Revision history for this message
Andreas Preikschat (googol-deactivatedaccount) wrote : Posted in a previous version of this proposal

Resolved conflict

Revision history for this message
Andreas Preikschat (googol-deactivatedaccount) wrote : Posted in a previous version of this proposal

- Fixed an i18n bug
- Remove shortcuts from the map if removed from the action list

Revision history for this message
Raoul Snyman (raoul-snyman) wrote : Posted in a previous version of this proposal

shortcuts = map(unicode, map(QtGui.QKeySequence.toString, shortcuts))

I think you have two spaces in the line above... ^^

review: Needs Fixing
Revision history for this message
Tim Bentley (trb143) :
review: Approve
Revision history for this message
Raoul Snyman (raoul-snyman) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'openlp/core/ui/shortcutlistform.py'
2--- openlp/core/ui/shortcutlistform.py 2011-07-07 21:22:41 +0000
3+++ openlp/core/ui/shortcutlistform.py 2011-12-15 15:40:29 +0000
4@@ -344,8 +344,11 @@
5 if category.name is None:
6 continue
7 for action in category.actions:
8- if self.changedActions .has_key(action):
9+ if action in self.changedActions:
10+ old_shortcuts = map(unicode,
11+ map(QtGui.QKeySequence.toString, action.shortcuts()))
12 action.setShortcuts(self.changedActions[action])
13+ self.action_list.update_shortcut_map(action, old_shortcuts)
14 settings.setValue(
15 action.objectName(), QtCore.QVariant(action.shortcuts()))
16 settings.endGroup()
17@@ -452,7 +455,7 @@
18 those shortcuts which are not saved yet but already assigned (as changes
19 are applied when closing the dialog).
20 """
21- if self.changedActions.has_key(action):
22+ if action in self.changedActions:
23 return self.changedActions[action]
24 return action.shortcuts()
25
26
27=== modified file 'openlp/core/utils/actions.py'
28--- openlp/core/utils/actions.py 2011-12-10 20:31:16 +0000
29+++ openlp/core/utils/actions.py 2011-12-15 15:40:29 +0000
30@@ -188,6 +188,7 @@
31 actions or categories.
32 """
33 instance = None
34+ shortcut_map = {}
35
36 def __init__(self):
37 self.categories = CategoryList()
38@@ -224,17 +225,45 @@
39 self.categories[category].actions.append(action)
40 else:
41 self.categories[category].actions.add(action, weight)
42- if not category:
43- # Stop here, as this action is not configurable.
44- return
45 # Load the shortcut from the config.
46 settings = QtCore.QSettings()
47 settings.beginGroup(u'shortcuts')
48 shortcuts = settings.value(action.objectName(),
49 QtCore.QVariant(action.shortcuts())).toStringList()
50+ settings.endGroup()
51+ if not shortcuts:
52+ action.setShortcuts([])
53+ return
54+ # We have to do this to ensure that the loaded shortcut list e. g.
55+ # STRG+O (German) is converted to CTRL+O, which is only done when we
56+ # convert the strings in this way (QKeySequence -> QString -> unicode).
57+ shortcuts = map(QtGui.QKeySequence, shortcuts)
58+ shortcuts = map(unicode, map(QtGui.QKeySequence.toString, shortcuts))
59+ # Check the alternate shortcut first, to avoid problems when the
60+ # alternate shortcut becomes the primary shortcut after removing the
61+ # (initial) primary shortcut due to conflicts.
62+ if len(shortcuts) == 2:
63+ existing_actions = ActionList.shortcut_map.get(shortcuts[1], [])
64+ # Check for conflicts with other actions considering the shortcut
65+ # context.
66+ if self._is_shortcut_available(existing_actions, action):
67+ actions = ActionList.shortcut_map.get(shortcuts[1], [])
68+ actions.append(action)
69+ ActionList.shortcut_map[shortcuts[1]] = actions
70+ else:
71+ shortcuts.remove(shortcuts[1])
72+ # Check the primary shortcut.
73+ existing_actions = ActionList.shortcut_map.get(shortcuts[0], [])
74+ # Check for conflicts with other actions considering the shortcut
75+ # context.
76+ if self._is_shortcut_available(existing_actions, action):
77+ actions = ActionList.shortcut_map.get(shortcuts[0], [])
78+ actions.append(action)
79+ ActionList.shortcut_map[shortcuts[0]] = actions
80+ else:
81+ shortcuts.remove(shortcuts[0])
82 action.setShortcuts(
83 [QtGui.QKeySequence(shortcut) for shortcut in shortcuts])
84- settings.endGroup()
85
86 def remove_action(self, action, category=None):
87 """
88@@ -242,7 +271,7 @@
89 automatically removed.
90
91 ``action``
92- The QAction object to be removed.
93+ The ``QAction`` object to be removed.
94
95 ``category``
96 The name (unicode string) of the category, which contains the
97@@ -254,6 +283,15 @@
98 # Remove empty categories.
99 if len(self.categories[category].actions) == 0:
100 self.categories.remove(category)
101+ shortcuts = map(unicode,
102+ map(QtGui.QKeySequence.toString, action.shortcuts()))
103+ for shortcut in shortcuts:
104+ # Remove action from the list of actions which are using this
105+ # shortcut.
106+ ActionList.shortcut_map[shortcut].remove(action)
107+ # Remove empty entries.
108+ if not ActionList.shortcut_map[shortcut]:
109+ del ActionList.shortcut_map[shortcut]
110
111 def add_category(self, name, weight):
112 """
113@@ -275,6 +313,60 @@
114 return
115 self.categories.add(name, weight)
116
117+ def update_shortcut_map(self, action, old_shortcuts):
118+ """
119+ Remove the action for the given ``old_shortcuts`` from the
120+ ``shortcut_map`` to ensure its up-to-dateness.
121+
122+ **Note**: The new action's shortcuts **must** be assigned to the given
123+ ``action`` **before** calling this method.
124+
125+ ``action``
126+ The action whose shortcuts are supposed to be updated in the
127+ ``shortcut_map``.
128+
129+ ``old_shortcuts``
130+ A list of unicode keysequences.
131+ """
132+ for old_shortcut in old_shortcuts:
133+ # Remove action from the list of actions which are using this
134+ # shortcut.
135+ ActionList.shortcut_map[old_shortcut].remove(action)
136+ # Remove empty entries.
137+ if not ActionList.shortcut_map[old_shortcut]:
138+ del ActionList.shortcut_map[old_shortcut]
139+ new_shortcuts = map(unicode,
140+ map(QtGui.QKeySequence.toString, action.shortcuts()))
141+ # Add the new shortcuts to the map.
142+ for new_shortcut in new_shortcuts:
143+ existing_actions = ActionList.shortcut_map.get(new_shortcut, [])
144+ existing_actions.append(action)
145+ ActionList.shortcut_map[new_shortcut] = existing_actions
146+
147+ def _is_shortcut_available(self, existing_actions, action):
148+ """
149+ Checks if the given ``action`` may use its assigned shortcut(s) or not.
150+ Returns ``True`` or ``False.
151+
152+ ``existing_actions``
153+ A list of actions which already use a particular shortcut.
154+
155+ ``action``
156+ The action which wants to use a particular shortcut.
157+ """
158+ for existing_action in existing_actions:
159+ if action is existing_action:
160+ continue
161+ if existing_action.parent() is action.parent():
162+ return False
163+ if existing_action.shortcutContext() in [QtCore.Qt.WindowShortcut,
164+ QtCore.Qt.ApplicationShortcut]:
165+ return False
166+ if action.shortcutContext() in [QtCore.Qt.WindowShortcut,
167+ QtCore.Qt.ApplicationShortcut]:
168+ return False
169+ return True
170+
171
172 class CategoryOrder(object):
173 """