199 lines
5.6 KiB
Python
199 lines
5.6 KiB
Python
|
'''
|
||
|
Sandbox
|
||
|
=======
|
||
|
|
||
|
.. versionadded:: 1.8.0
|
||
|
|
||
|
.. warning::
|
||
|
|
||
|
This is experimental and subject to change as long as this warning notice
|
||
|
is present.
|
||
|
|
||
|
This is a widget that runs itself and all of its children in a Sandbox. That
|
||
|
means if a child raises an Exception, it will be caught. The Sandbox
|
||
|
itself runs its own Clock, Cache, etc.
|
||
|
|
||
|
The SandBox widget is still experimental and required for the Kivy designer.
|
||
|
When the user designs their own widget, if they do something wrong (wrong size
|
||
|
value, invalid python code), it will be caught correctly without breaking
|
||
|
the whole application. Because it has been designed that way, we are still
|
||
|
enhancing this widget and the :mod:`kivy.context` module.
|
||
|
Don't use it unless you know what you are doing.
|
||
|
|
||
|
'''
|
||
|
|
||
|
__all__ = ('Sandbox', )
|
||
|
|
||
|
from functools import wraps
|
||
|
from kivy.context import Context
|
||
|
from kivy.base import ExceptionManagerBase
|
||
|
from kivy.clock import Clock
|
||
|
from kivy.uix.widget import Widget
|
||
|
from kivy.uix.floatlayout import FloatLayout
|
||
|
from kivy.uix.relativelayout import RelativeLayout
|
||
|
from kivy.lang import Builder
|
||
|
|
||
|
|
||
|
def sandbox(f):
|
||
|
@wraps(f)
|
||
|
def _f2(self, *args, **kwargs):
|
||
|
ret = None
|
||
|
with self:
|
||
|
ret = f(self, *args, **kwargs)
|
||
|
return ret
|
||
|
return _f2
|
||
|
|
||
|
|
||
|
class SandboxExceptionManager(ExceptionManagerBase):
|
||
|
|
||
|
def __init__(self, sandbox):
|
||
|
ExceptionManagerBase.__init__(self)
|
||
|
self.sandbox = sandbox
|
||
|
|
||
|
def handle_exception(self, e):
|
||
|
if not self.sandbox.on_exception(e):
|
||
|
return ExceptionManagerBase.RAISE
|
||
|
return ExceptionManagerBase.PASS
|
||
|
|
||
|
|
||
|
class SandboxContent(RelativeLayout):
|
||
|
pass
|
||
|
|
||
|
|
||
|
class Sandbox(FloatLayout):
|
||
|
'''Sandbox widget, used to trap all the exceptions raised by child
|
||
|
widgets.
|
||
|
'''
|
||
|
|
||
|
def __init__(self, **kwargs):
|
||
|
self._context = Context(init=True)
|
||
|
self._context['ExceptionManager'] = SandboxExceptionManager(self)
|
||
|
self._context.sandbox = self
|
||
|
self._context.push()
|
||
|
self.on_context_created()
|
||
|
self._container = None
|
||
|
super(Sandbox, self).__init__(**kwargs)
|
||
|
self._container = SandboxContent(size=self.size, pos=self.pos)
|
||
|
super(Sandbox, self).add_widget(self._container)
|
||
|
self._context.pop()
|
||
|
|
||
|
# force SandboxClock's scheduling
|
||
|
Clock.schedule_interval(self._clock_sandbox, 0)
|
||
|
Clock.schedule_once(self._clock_sandbox_draw, -1)
|
||
|
self.main_clock = object.__getattribute__(Clock, '_obj')
|
||
|
|
||
|
def __enter__(self):
|
||
|
self._context.push()
|
||
|
|
||
|
def __exit__(self, _type, value, traceback):
|
||
|
self._context.pop()
|
||
|
if _type is not None:
|
||
|
return self.on_exception(value, _traceback=traceback)
|
||
|
|
||
|
def on_context_created(self):
|
||
|
'''Override this method in order to load your kv file or do anything
|
||
|
else with the newly created context.
|
||
|
'''
|
||
|
pass
|
||
|
|
||
|
def on_exception(self, exception, _traceback=None):
|
||
|
'''Override this method in order to catch all the exceptions from
|
||
|
children.
|
||
|
|
||
|
If you return True, it will not reraise the exception.
|
||
|
If you return False, the exception will be raised to the parent.
|
||
|
'''
|
||
|
import traceback
|
||
|
traceback.print_tb(_traceback)
|
||
|
return True
|
||
|
|
||
|
on_motion = sandbox(Widget.on_motion)
|
||
|
on_touch_down = sandbox(Widget.on_touch_down)
|
||
|
on_touch_move = sandbox(Widget.on_touch_move)
|
||
|
on_touch_up = sandbox(Widget.on_touch_up)
|
||
|
|
||
|
@sandbox
|
||
|
def add_widget(self, *args, **kwargs):
|
||
|
self._container.add_widget(*args, **kwargs)
|
||
|
|
||
|
@sandbox
|
||
|
def remove_widget(self, *args, **kwargs):
|
||
|
self._container.remove_widget(*args, **kwargs)
|
||
|
|
||
|
@sandbox
|
||
|
def clear_widgets(self, *args, **kwargs):
|
||
|
self._container.clear_widgets(*args, **kwargs)
|
||
|
|
||
|
@sandbox
|
||
|
def on_size(self, *args):
|
||
|
if self._container:
|
||
|
self._container.size = self.size
|
||
|
|
||
|
@sandbox
|
||
|
def on_pos(self, *args):
|
||
|
if self._container:
|
||
|
self._container.pos = self.pos
|
||
|
|
||
|
@sandbox
|
||
|
def _clock_sandbox(self, dt):
|
||
|
# import pdb; pdb.set_trace()
|
||
|
Clock.tick()
|
||
|
Builder.sync()
|
||
|
|
||
|
@sandbox
|
||
|
def _clock_sandbox_draw(self, dt):
|
||
|
Clock.tick_draw()
|
||
|
Builder.sync()
|
||
|
self.main_clock.schedule_once(self._call_draw, 0)
|
||
|
|
||
|
def _call_draw(self, dt):
|
||
|
self.main_clock.schedule_once(self._clock_sandbox_draw, -1)
|
||
|
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
from kivy.base import runTouchApp
|
||
|
from kivy.uix.button import Button
|
||
|
|
||
|
class TestButton(Button):
|
||
|
|
||
|
def on_touch_up(self, touch):
|
||
|
# raise Exception('fdfdfdfdfdfdfd')
|
||
|
return super(TestButton, self).on_touch_up(touch)
|
||
|
|
||
|
def on_touch_down(self, touch):
|
||
|
# raise Exception('')
|
||
|
return super(TestButton, self).on_touch_down(touch)
|
||
|
|
||
|
s = Sandbox()
|
||
|
with s:
|
||
|
Builder.load_string('''
|
||
|
<TestButton>:
|
||
|
canvas:
|
||
|
Color:
|
||
|
rgb: (.3, .2, 0) if self.state == 'normal' else (.7, .7, 0)
|
||
|
Rectangle:
|
||
|
pos: self.pos
|
||
|
size: self.size
|
||
|
Color:
|
||
|
rgb: 1, 1, 1
|
||
|
Rectangle:
|
||
|
size: self.texture_size
|
||
|
pos: self.center_x - self.texture_size[0] / 2.,\
|
||
|
self.center_y - self.texture_size[1] / 2.
|
||
|
texture: self.texture
|
||
|
|
||
|
# invalid... for testing.
|
||
|
# on_touch_up: root.d()
|
||
|
# on_touch_down: root.f()
|
||
|
on_release: root.args()
|
||
|
# on_press: root.args()
|
||
|
''')
|
||
|
b = TestButton(text='Hello World')
|
||
|
s.add_widget(b)
|
||
|
|
||
|
# this exception is within the "with" block, but will be ignored by
|
||
|
# default because the sandbox on_exception will return True
|
||
|
raise Exception('hello')
|
||
|
|
||
|
runTouchApp(s)
|