Mercurial > urwid-satext
comparison urwid_satext/keys.py @ 84:9f683df69a4c
shortcut keys are now managed in separate module, with a class checking for conflicts
author | Goffi <goffi@goffi.org> |
---|---|
date | Thu, 04 Sep 2014 16:50:12 +0200 |
parents | |
children | e0c8274f9b1c |
comparison
equal
deleted
inserted
replaced
83:12b5b1435e17 | 84:9f683df69a4c |
---|---|
1 #!/usr/bin/python | |
2 # -*- coding: utf-8 -*- | |
3 | |
4 # Urwid SàT extensions | |
5 # Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Jérôme Poisson (goffi@goffi.org) | |
6 # | |
7 # This program is free software: you can redistribute it and/or modify | |
8 # it under the terms of the GNU Lesser General Public License as published by | |
9 # the Free Software Foundation, either version 3 of the License, or | |
10 # (at your option) any later version. | |
11 # | |
12 # This program is distributed in the hope that it will be useful, | |
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 # GNU Lesser General Public License for more details. | |
16 # | |
17 # You should have received a copy of the GNU Lesser General Public License | |
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 | |
20 """This module manage action <==> key mapping and can be extended to add new actions""" | |
21 | |
22 | |
23 class ConflictError(Exception): | |
24 pass | |
25 | |
26 | |
27 class ActionMap(dict): | |
28 """Object which manage mapping betwwen actions and keys""" | |
29 | |
30 def __init__(self, source_dict=None): | |
31 """ Initialise the map | |
32 | |
33 @param source_dict: dictionary-like object with actions to import | |
34 """ | |
35 self._namespaces_actions = {} # key = namespace, values (set) = actions | |
36 self._close_namespaces = tuple() | |
37 self._alway_check_namespaces = None | |
38 if source_dict is not None: | |
39 self.update(source_dict) | |
40 | |
41 def __setitem__(self, action, shortcut): | |
42 """set an action avoiding conflicts | |
43 | |
44 @param action (str,tuple): either an action (str) or a (namespace, action) tuple. action without namespace will not be checked by cbeck_namespaces(). namespace can also be a tuple itself, the action will the be assigned to several namespaces. | |
45 @param shortcut (str): key shortcut for this action | |
46 @raise: ConflictError if the action already exists | |
47 """ | |
48 if isinstance(action, tuple): | |
49 namespaces, action = action | |
50 if not isinstance(namespaces, tuple): | |
51 namespaces = (namespaces,) | |
52 for namespace in namespaces: | |
53 namespace_map = self._namespaces_actions.setdefault(namespace.lower(), set()) | |
54 namespace_map.add(action) | |
55 | |
56 if action in self: | |
57 raise ConflictError("The action [{}] already exists".format(action)) | |
58 return super(ActionMap, self).__setitem__(action, shortcut.lower()) | |
59 | |
60 def __delitem__(self, action): | |
61 # we don't want to delete actions | |
62 raise NotImplementedError | |
63 | |
64 def update(self, dict_like): | |
65 """Update actions with an other dictionary | |
66 | |
67 @param dict_like: dictionary like object to update actions | |
68 @raise: ConflictError if at least one of the new actions already exists | |
69 """ | |
70 if not isinstance(dict_like, dict): | |
71 raise ValueError("only dictionary subclasses are accepted for update") | |
72 conflict = dict_like.viewkeys() & self.viewkeys() | |
73 if conflict: | |
74 raise ConflictError("The actions [{}] already exists".format(','.join(conflict))) | |
75 for action, shortcut in dict_like.iteritems(): | |
76 self[action] = shortcut | |
77 | |
78 def set_close_namespaces(self, close_namespaces, always_check=None): | |
79 """Set namespaces where conflicting shortcut should not happen | |
80 | |
81 used by check_namespaces to see if the same shortcut is not used in two close namespaces (e.g. 'tab' used in edit_bar and globally) | |
82 @param close_namespaces (tuple of tuples): tuple indicating namespace where shortcut should not conflict. e.g.: (('global', 'edit'), ('confirm', 'popup', 'global')) indicate that shortcut in 'global' and 'edit' should not be the same, nor the ones between 'confirm', 'popup' and 'global'. | |
83 @param always_check (tuple): if not None, these namespaces will be close to every other ones (useful for global namespace) | |
84 """ | |
85 assert isinstance(close_namespaces, tuple) | |
86 if always_check is not None: | |
87 assert isinstance(always_check, tuple) | |
88 self._close_namespaces = close_namespaces | |
89 self._alway_check_namespaces = always_check | |
90 | |
91 def check_namespaces(self): | |
92 """Check that shortcuts are not conflicting in close namespaces""" | |
93 # we first check each namespace individually | |
94 checked = set() | |
95 | |
96 def check_namespaces(namespaces): | |
97 # for each namespace which save keys used | |
98 # if 1 key is used several times, we raise | |
99 # a ConflictError | |
100 set_shortcuts = {} | |
101 | |
102 to_check = set(namespaces + self._alway_check_namespaces) | |
103 | |
104 for namespace in to_check: | |
105 checked.add(namespace) | |
106 for action in self._namespaces_actions[namespace]: | |
107 shortcut = self[action] | |
108 if shortcut in set_shortcuts: | |
109 set_namespace = set_shortcuts[shortcut] | |
110 if set_namespace == namespace: | |
111 msg = 'shortcut [{}] is not unique in namespace "{}"'.format(shortcut, namespace) | |
112 else: | |
113 msg = 'shortcut [{}] is used both in namespaces "{}" and "{}"'.format(shortcut, set_namespace, namespace) | |
114 raise ConflictError(msg) | |
115 set_shortcuts[shortcut] = namespace | |
116 | |
117 # we first check close namespaces | |
118 for close_namespaces in self._close_namespaces: | |
119 check_namespaces(close_namespaces) | |
120 | |
121 # then the remaining ones | |
122 for namespace in set(self._namespaces_actions.keys()).difference(checked): | |
123 check_namespaces((namespace,)) | |
124 | |
125 | |
126 keys = { | |
127 ("edit", "EDIT_HOME"): 'ctrl a', | |
128 ("edit", "EDIT_END"): 'ctrl e', | |
129 ("edit", "EDIT_DELETE_TO_END"): 'ctrl k', | |
130 ("edit", "EDIT_DELETE_LAST_WORD"): 'ctrl w', | |
131 ("edit", "EDIT_ENTER"): 'enter', | |
132 ("edit", "EDIT_COMPLETE"): 'shift tab', | |
133 (("edit", "modal"), "MODAL_ESCAPE"): 'esc', | |
134 ("selectable", "TEXT_SELECT"): ' ', | |
135 ("selectable", "TEXT_SELECT2"): 'enter', | |
136 ("menu_box", "MENU_BOX_UP"): 'up', | |
137 ("menu_box", "MENU_BOX_LEFT"): 'left', | |
138 ("menu_box", "MENU_BOX_RIGHT"): 'right', | |
139 ("menu", "MENU_DOWN"): 'down', | |
140 ("menu", "MENU_UP"): 'up', | |
141 ("menu_roller", "MENU_ROLLER_UP"): 'up', | |
142 ("menu_roller", "MENU_ROLLER_DOWN"): 'down', | |
143 ("menu_roller", "MENU_ROLLER_RIGHT"): 'right', | |
144 ("columns_roller", "COLUMNS_ROLLER_LEFT"): 'left', | |
145 ("columns_roller", "COLUMNS_ROLLER_RIGHT"): 'right', | |
146 ("focus", "FOCUS_SWITCH"): 'tab', | |
147 } | |
148 | |
149 action_key_map = ActionMap(keys) |