1 from kivy
.uix
.widget
import Widget
2 from kivy
.properties
import AliasProperty
, BooleanProperty
, \
3 ListProperty
, StringProperty
4 from kivy
.uix
.behaviors
import ButtonBehavior
6 from .action
import Action
7 from .helpers
import debug_print
10 from transitions
.extensions
import HierarchicalMachine
as Machine
12 # All drawing operations should happen in the main thread
13 # https://github.com/kivy/kivy/wiki/Working-with-Python-threads-inside-a-Kivy-application
14 from kivy
.clock
import mainthread
36 'trigger': 'configure',
42 'source': 'configuring',
44 'after': 'key_loaded_callback'
48 'source': 'configuring',
53 'trigger': 'no_config',
54 'source': 'configuring',
55 'dest': 'loaded_no_config',
56 'after': 'key_loaded_callback'
60 'source': 'configured',
67 'after': 'key_loaded_callback'
73 'after': 'key_loaded_callback'
76 'trigger': 'no_actions',
78 'dest': 'loaded_no_actions',
79 'after': 'key_loaded_callback'
83 'source': ['loaded','failed'],
84 'dest': 'configuring',
85 'after': 'key_loaded_callback'
90 'dest': 'loaded_running',
91 'after': ['run_actions', 'finish'],
92 # if a child, like loaded_no_actions, has no transitions, then it
93 # is bubbled to the parent, and we don't want that.
94 'conditions': ['is_loaded']
98 'source': 'loaded_running',
99 'dest': 'loaded_protecting_repeat'
102 'trigger': 'repeat_protection_finished',
103 'source': 'loaded_protecting_repeat',
105 'after': 'callback_action_state_changed'
109 def __setattr__(self
, name
, value
):
110 if hasattr(self
, 'initialized') and name
== 'state':
111 self
.key
.update_state(value
)
112 super().__setattr
__(name
, value
)
114 def __init__(self
, key
, **kwargs
):
117 Machine(model
=self
, states
=self
.STATES
,
118 transitions
=self
.TRANSITIONS
, initial
='initial',
119 ignore_invalid_triggers
=True, queued
=True)
121 self
.initialized
= True
123 # Machine states / events
124 def is_loaded_or_failed(self
):
125 return self
.is_loaded(allow_substates
=True) or self
.is_failed()
127 def is_loaded_inactive(self
):
128 return self
.is_loaded_no_config() or self
.is_loaded_no_actions()
131 def on_enter_configuring(self
):
132 self
.destroy_actions()
133 self
.key
.unset_description()
134 self
.key
.unset_color()
136 if self
.key
.key_sym
in self
.key
.parent
.key_config
:
137 self
.key
.config
= self
.key
.parent
.key_config
[self
.key
.key_sym
]
139 for key_action
in self
.key
.config
['actions']:
140 self
.key
.add_action(key_action
[0], **key_action
[1])
142 if 'description' in self
.key
.config
['properties']:
143 self
.key
.set_description(self
.key
.config
['properties']['description'])
144 if 'color' in self
.key
.config
['properties']:
145 self
.key
.set_color(self
.key
.config
['properties']['color'])
150 def on_enter_loading(self
):
151 if len(self
.key
.actions
) > 0:
152 for action
in self
.key
.actions
:
157 def destroy_actions(self
):
158 for action
in self
.key
.actions
:
160 self
.key
.actions
= []
162 def run_actions(self
, modifiers
):
163 self
.key
.parent
.parent
.ids
['KeyList'].append(self
.key
.key_sym
)
164 debug_print("running actions for {}".format(self
.key
.key_sym
))
165 start_time
= time
.time()
166 self
.key
.parent
.start_running(self
.key
, start_time
)
167 for self
.key
.current_action
in self
.key
.actions
:
168 if self
.key
.parent
.keep_running(self
.key
, start_time
):
169 self
.key
.list_actions()
170 self
.key
.current_action
.run(start_time
)
171 self
.key
.list_actions(last_action_finished
=True)
173 self
.key
.parent
.finished_running(self
.key
, start_time
)
175 def on_enter_loaded_protecting_repeat(self
, modifiers
):
176 if self
.key
.repeat_delay
> 0:
177 self
.key
.protecting_repeat_timer
= threading
.Timer(
178 self
.key
.repeat_delay
,
179 self
.key
.repeat_protection_finished
)
180 self
.key
.protecting_repeat_timer
.start()
182 self
.key
.repeat_protection_finished()
186 def key_loaded_callback(self
):
187 self
.key
.parent
.key_loaded_callback()
189 def callback_action_state_changed(self
):
190 if self
.state
not in ['failed', 'loading', 'loaded']:
193 if any(action
.is_failed() for action
in self
.key
.actions
):
195 elif any(action
.is_loading() for action
in self
.key
.actions
):
199 self
.key_loaded_callback()
201 class Key(ButtonBehavior
, Widget
):
203 key_sym
= StringProperty(None)
204 custom_color
= ListProperty([0, 1, 0])
205 description_title
= StringProperty("")
206 description
= ListProperty([])
207 machine_state
= StringProperty("")
209 def get_alias_line_cross_color(self
):
210 if not self
.is_failed() and (
211 not self
.is_loaded(allow_substates
=True)\
212 or self
.is_loaded_running()\
213 or self
.is_loaded_protecting_repeat()):
214 return [120/255, 120/255, 120/255, 1]
218 def set_alias_line_cross_color(self
):
221 line_cross_color
= AliasProperty(
222 get_alias_line_cross_color
,
223 set_alias_line_cross_color
,
224 bind
=['machine_state'])
226 def get_alias_line_color(self
):
227 if self
.is_loaded_running():
230 return [120/255, 120/255, 120/255, 1]
232 def set_alias_line_color(self
):
235 line_color
= AliasProperty(get_alias_line_color
, set_alias_line_color
,
236 bind
=['machine_state'])
238 def get_alias_color(self
):
239 if self
.is_loaded_inactive():
241 elif self
.is_loaded_protecting_repeat():
242 return [*self
.custom_color
, 100/255]
243 elif self
.is_loaded_running():
244 return [*self
.custom_color
, 100/255]
245 elif self
.is_loaded(allow_substates
=True):
246 return [*self
.custom_color
, 1]
247 elif self
.is_failed():
250 return [*self
.custom_color
, 100/255]
251 def set_alias_color(self
):
254 color
= AliasProperty(get_alias_color
, set_alias_color
,
255 bind
=['machine_state', 'custom_color'])
257 def __getattr__(self
, name
):
258 if hasattr(self
.machine
, name
):
259 return getattr(self
.machine
, name
)
263 def __init__(self
, **kwargs
):
265 self
.current_action
= None
266 self
.machine
= KeyMachine(self
)
268 super(Key
, self
).__init
__(**kwargs
)
272 def update_state(self
, value
):
273 self
.machine_state
= value
275 def on_key_sym(self
, key
, key_sym
):
282 # This one cannot be in the Machine state since it would be queued to run
283 # *after* the loop is ended...
285 self
.current_action
.interrupt()
288 def set_description(self
, description
):
289 if description
[0] is not None:
290 self
.description_title
= str(description
[0])
291 self
.description
= []
292 for desc
in description
[1 :]:
294 self
.description
.append("")
296 self
.description
.append(str(desc
).replace(" ", " "))
298 def unset_description(self
):
299 self
.description_title
= ""
300 self
.description
= []
302 def set_color(self
, color
):
303 color
= [x
/ 255 for x
in color
]
304 self
.custom_color
= color
306 def unset_color(self
):
307 self
.custom_color
= [0, 1, 0]
311 def repeat_delay(self
):
312 if hasattr(self
, 'config') and\
313 'repeat_delay' in self
.config
['properties']:
314 return self
.config
['properties']['repeat_delay']
319 def add_action(self
, action_name
, **arguments
):
320 self
.actions
.append(Action(action_name
, self
, **arguments
))
322 def list_actions(self
, last_action_finished
=False):
323 not_running
= (not self
.is_loaded_running())
324 current_action_seen
= False
325 action_descriptions
= []
326 for action
in self
.actions
:
329 elif last_action_finished
:
331 elif current_action_seen
:
333 elif action
== self
.current_action
:
334 current_action_seen
= True
338 action_descriptions
.append([action
.description(), state
])
339 self
.parent
.parent
.ids
['ActionList'].update_list(