json, icon, and misc fixes and improvements made to ejpi/gonvert
[gc-dialer] / dialcentral / util / qtpieboard.py
1 #!/usr/bin/env python
2
3
4 from __future__ import division
5
6 import os
7 import warnings
8
9 import qt_compat
10 QtGui = qt_compat.import_module("QtGui")
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                 self._widget = QtGui.QWidget()
56                 self._widget.setLayout(self._layout)
57
58                 self.__cells = {}
59
60         @property
61         def toplevel(self):
62                 return self._widget
63
64         def add_pie(self, row, column, pieButton):
65                 assert len(pieButton) == 8
66                 self._layout.addWidget(pieButton, row, column)
67                 self.__cells[(row, column)] = pieButton
68
69         def get_pie(self, row, column):
70                 return self.__cells[(row, column)]
71
72
73 class KeyboardModifier(object):
74
75         def __init__(self, name):
76                 self.name = name
77                 self.lock = False
78                 self.once = False
79
80         @property
81         def isActive(self):
82                 return self.lock or self.once
83
84         def on_toggle_lock(self, *args, **kwds):
85                 self.lock = not self.lock
86
87         def on_toggle_once(self, *args, **kwds):
88                 self.once = not self.once
89
90         def reset_once(self):
91                 self.once = False
92
93
94 def parse_keyboard_data(text):
95         return eval(text)
96
97
98 def _enumerate_pie_slices(pieData, iconPaths):
99         for direction, directionName in zip(
100                 PieKeyboard.SLICE_DIRECTIONS, PieKeyboard.SLICE_DIRECTION_NAMES
101         ):
102                 if directionName in pieData:
103                         sliceData = pieData[directionName]
104
105                         action = QtGui.QAction(None)
106                         try:
107                                 action.setText(sliceData["text"])
108                         except KeyError:
109                                 pass
110                         try:
111                                 relativeIconPath = sliceData["path"]
112                         except KeyError:
113                                 pass
114                         else:
115                                 for iconPath in iconPaths:
116                                         absIconPath = os.path.join(iconPath, relativeIconPath)
117                                         if os.path.exists(absIconPath):
118                                                 action.setIcon(QtGui.QIcon(absIconPath))
119                                                 break
120                         pieItem = qtpie.QActionPieItem(action)
121                         actionToken = sliceData["action"]
122                 else:
123                         pieItem = qtpie.PieFiling.NULL_CENTER
124                         actionToken = ""
125                 yield direction, pieItem, actionToken
126
127
128 def load_keyboard(keyboardName, dataTree, keyboard, keyboardHandler, iconPaths):
129         for (row, column), pieData in dataTree.iteritems():
130                 pieItems = list(_enumerate_pie_slices(pieData, iconPaths))
131                 assert pieItems[0][0] == PieKeyboard.SLICE_CENTER, pieItems[0]
132                 _, center, centerAction = pieItems.pop(0)
133
134                 pieButton = qtpie.QPieButton(center)
135                 pieButton.set_center(center)
136                 keyboardHandler.map_slice_action(center, centerAction)
137                 for direction, pieItem, action in pieItems:
138                         pieButton.insertItem(pieItem)
139                         keyboardHandler.map_slice_action(pieItem, action)
140                 keyboard.add_pie(row, column, pieButton)
141
142
143 class KeyboardHandler(object):
144
145         def __init__(self, keyhandler):
146                 self.__keyhandler = keyhandler
147                 self.__commandHandlers = {}
148                 self.__modifiers = {}
149                 self.__sliceActions = {}
150
151                 self.register_modifier("Shift")
152                 self.register_modifier("Super")
153                 self.register_modifier("Control")
154                 self.register_modifier("Alt")
155
156         def register_command_handler(self, command, handler):
157                 # @todo Look into hooking these up directly to the pie actions
158                 self.__commandHandlers["[%s]" % command] = handler
159
160         def unregister_command_handler(self, command):
161                 # @todo Look into hooking these up directly to the pie actions
162                 del self.__commandHandlers["[%s]" % command]
163
164         def register_modifier(self, modifierName):
165                 mod = KeyboardModifier(modifierName)
166                 self.register_command_handler(modifierName, mod.on_toggle_lock)
167                 self.__modifiers["<%s>" % modifierName] = mod
168
169         def unregister_modifier(self, modifierName):
170                 self.unregister_command_handler(modifierName)
171                 del self.__modifiers["<%s>" % modifierName]
172
173         def map_slice_action(self, slice, action):
174                 callback = lambda: self(action)
175                 slice.action().triggered.connect(callback)
176                 self.__sliceActions[slice] = (action, callback)
177
178         def __call__(self, action):
179                 activeModifiers = [
180                         mod.name
181                         for mod in self.__modifiers.itervalues()
182                                 if mod.isActive
183                 ]
184
185                 needResetOnce = False
186                 if action.startswith("[") and action.endswith("]"):
187                         commandName = action[1:-1]
188                         if action in self.__commandHandlers:
189                                 self.__commandHandlers[action](commandName, activeModifiers)
190                                 needResetOnce = True
191                         else:
192                                 warnings.warn("Unknown command: [%s]" % commandName)
193                 elif action.startswith("<") and action.endswith(">"):
194                         modName = action[1:-1]
195                         for mod in self.__modifiers.itervalues():
196                                 if mod.name == modName:
197                                         mod.on_toggle_once()
198                                         break
199                         else:
200                                 warnings.warn("Unknown modifier: <%s>" % modName)
201                 else:
202                         self.__keyhandler(action, activeModifiers)
203                         needResetOnce = True
204
205                 if needResetOnce:
206                         for mod in self.__modifiers.itervalues():
207                                 mod.reset_once()