Trying to better follow DRY
[ejpi] / src / libraries / qtpieboard.py
1 #!/usr/bin/env python
2
3
4 from __future__ import division
5
6 import os
7 import warnings
8
9 from PyQt4 import QtGui
10 from PyQt4 import QtCore
11
12 import qtpie
13
14
15 class PieKeyboard(object):
16
17         SLICE_CENTER = -1
18         SLICE_NORTH = 0
19         SLICE_NORTH_WEST = 1
20         SLICE_WEST = 2
21         SLICE_SOUTH_WEST = 3
22         SLICE_SOUTH = 4
23         SLICE_SOUTH_EAST = 5
24         SLICE_EAST = 6
25         SLICE_NORTH_EAST = 7
26
27         MAX_ANGULAR_SLICES = 8
28
29         SLICE_DIRECTIONS = [
30                 SLICE_CENTER,
31                 SLICE_NORTH,
32                 SLICE_NORTH_WEST,
33                 SLICE_WEST,
34                 SLICE_SOUTH_WEST,
35                 SLICE_SOUTH,
36                 SLICE_SOUTH_EAST,
37                 SLICE_EAST,
38                 SLICE_NORTH_EAST,
39         ]
40
41         SLICE_DIRECTION_NAMES = [
42                 "CENTER",
43                 "NORTH",
44                 "NORTH_WEST",
45                 "WEST",
46                 "SOUTH_WEST",
47                 "SOUTH",
48                 "SOUTH_EAST",
49                 "EAST",
50                 "NORTH_EAST",
51         ]
52
53         def __init__(self):
54                 self._layout = QtGui.QGridLayout()
55
56                 self.__cells = {}
57
58         @property
59         def toplevel(self):
60                 return self._layout
61
62         def show(self):
63                 for cell in self.__cells.itervalues():
64                         cell.show()
65
66         def hide(self):
67                 for cell in self.__cells.itervalues():
68                         cell.hide()
69
70         def add_pie(self, row, column, pieButton):
71                 assert len(pieButton) == 8
72                 self._layout.addWidget(pieButton, row, column)
73                 self.__cells[(row, column)] = pieButton
74
75         def get_pie(self, row, column):
76                 return self.__cells[(row, column)]
77
78
79 class KeyboardModifier(object):
80
81         def __init__(self, name):
82                 self.name = name
83                 self.lock = False
84                 self.once = False
85
86         @property
87         def isActive(self):
88                 return self.lock or self.once
89
90         def on_toggle_lock(self, *args, **kwds):
91                 self.lock = not self.lock
92
93         def on_toggle_once(self, *args, **kwds):
94                 self.once = not self.once
95
96         def reset_once(self):
97                 self.once = False
98
99
100 def parse_keyboard_data(text):
101         return eval(text)
102
103
104 def _enumerate_pie_slices(pieData, iconPaths):
105         for direction, directionName in zip(
106                 PieKeyboard.SLICE_DIRECTIONS, PieKeyboard.SLICE_DIRECTION_NAMES
107         ):
108                 if directionName in pieData:
109                         sliceData = pieData[directionName]
110
111                         action = QtGui.QAction(None)
112                         try:
113                                 action.setText(sliceData["text"])
114                         except KeyError:
115                                 pass
116                         try:
117                                 relativeIconPath = sliceData["path"]
118                         except KeyError:
119                                 pass
120                         else:
121                                 for iconPath in iconPaths:
122                                         absIconPath = os.path.join(iconPath, relativeIconPath)
123                                         if os.path.exists(absIconPath):
124                                                 action.setIcon(QtGui.QIcon(absIconPath))
125                                                 break
126                         pieItem = qtpie.QActionPieItem(action)
127                         actionToken = sliceData["action"]
128                 else:
129                         pieItem = qtpie.PieFiling.NULL_CENTER
130                         actionToken = ""
131                 yield direction, pieItem, actionToken
132
133
134 def load_keyboard(keyboardName, dataTree, keyboard, keyboardHandler, iconPaths):
135         for (row, column), pieData in dataTree.iteritems():
136                 pieItems = list(_enumerate_pie_slices(pieData, iconPaths))
137                 assert pieItems[0][0] == PieKeyboard.SLICE_CENTER, pieItems[0]
138                 _, center, centerAction = pieItems.pop(0)
139
140                 pieButton = qtpie.QPieButton(center)
141                 pieButton.set_center(center)
142                 keyboardHandler.map_slice_action(center, centerAction)
143                 for direction, pieItem, action in pieItems:
144                         pieButton.insertItem(pieItem)
145                         keyboardHandler.map_slice_action(pieItem, action)
146                 keyboard.add_pie(row, column, pieButton)
147
148
149 class KeyboardHandler(object):
150
151         def __init__(self, keyhandler):
152                 self.__keyhandler = keyhandler
153                 self.__commandHandlers = {}
154                 self.__modifiers = {}
155                 self.__sliceActions = {}
156
157                 self.register_modifier("Shift")
158                 self.register_modifier("Super")
159                 self.register_modifier("Control")
160                 self.register_modifier("Alt")
161
162         def register_command_handler(self, command, handler):
163                 # @todo Look into hooking these up directly to the pie actions
164                 self.__commandHandlers["[%s]" % command] = handler
165
166         def unregister_command_handler(self, command):
167                 # @todo Look into hooking these up directly to the pie actions
168                 del self.__commandHandlers["[%s]" % command]
169
170         def register_modifier(self, modifierName):
171                 mod = KeyboardModifier(modifierName)
172                 self.register_command_handler(modifierName, mod.on_toggle_lock)
173                 self.__modifiers["<%s>" % modifierName] = mod
174
175         def unregister_modifier(self, modifierName):
176                 self.unregister_command_handler(modifierName)
177                 del self.__modifiers["<%s>" % modifierName]
178
179         def map_slice_action(self, slice, action):
180                 callback = lambda direction: self(direction, action)
181                 slice.action().triggered.connect(callback)
182                 self.__sliceActions[slice] = (action, callback)
183
184         def __call__(self, direction, action):
185                 activeModifiers = [
186                         mod.name
187                         for mod in self.__modifiers.itervalues()
188                                 if mod.isActive
189                 ]
190
191                 needResetOnce = False
192                 if action.startswith("[") and action.endswith("]"):
193                         commandName = action[1:-1]
194                         if action in self.__commandHandlers:
195                                 self.__commandHandlers[action](commandName, activeModifiers)
196                                 needResetOnce = True
197                         else:
198                                 warnings.warn("Unknown command: [%s]" % commandName)
199                 elif action.startswith("<") and action.endswith(">"):
200                         modName = action[1:-1]
201                         for mod in self.__modifiers.itervalues():
202                                 if mod.name == modName:
203                                         mod.on_toggle_once()
204                                         break
205                         else:
206                                 warnings.warn("Unknown modifier: <%s>" % modName)
207                 else:
208                         self.__keyhandler(action, activeModifiers)
209                         needResetOnce = True
210
211                 if needResetOnce:
212                         for mod in self.__modifiers.itervalues():
213                                 mod.reset_once()