1from ipywidgets import widgets
2from traitlets import Unicode, Bool, Int
3
4
5# Register front end javascript
6from IPython import display
7from . import drawing_javascript
8display.display(display.Javascript(drawing_javascript.javascript))
9del drawing_javascript
10
11
12class DrawingWidget(widgets.DOMWidget):
13 _model_name = Unicode('DrawingModel').tag(sync=True)
14 _model_module = Unicode('drawingview').tag(sync=True)
15 _model_module_version = Unicode('0.1.0').tag(sync=True)
16 _view_name = Unicode('DrawingView').tag(sync=True)
17 _view_module = Unicode('drawingview').tag(sync=True)
18 _view_module_version = Unicode('0.1.0').tag(sync=True)
19 _image = Unicode().tag(sync=True)
20 _mousemove_blocked = Int(0).tag(sync=True)
21 _frame_blocked = Int(0).tag(sync=True)
22 throttle = Bool(True).tag(sync=True)
23 disable = Bool(False).tag(sync=True)
24 frame_delay = Int(-1).tag(sync=True)
25
26 def __init__(self, drawing, throttle=True, disable=False, frame_delay=-1):
27 '''An interactive Jupyter notebook widget.
28
29 This works similarly to displaying a Drawing as a cell output but
30 DrawingWidget can register callbacks for user mouse events. Within a
31 callback modify the drawing then call .refresh() to update the output in
32 real time.
33
34 Arguments:
35 drawing: The initial Drawing to display. Call .refresh() after
36 modifying or just assign a new Drawing.
37 throttle: If True, limit the rate of mousemove events. For drawings
38 with many elements, this will significantly reduce lag.
39 disable: While True, mouse events will be disabled.
40 frame_delay: If greater than or equal to zero, a timed callback will
41 occur frame_delay milliseconds after the previous drawing
42 update.
43 '''
44 super().__init__()
45 self.throttle = throttle
46 self.disable = disable
47 self.frame_delay = frame_delay
48 self.drawing = drawing
49 self.mousedown_callbacks = []
50 self.mousemove_callbacks = []
51 self.mouseup_callbacks = []
52 self.timed_callbacks = []
53 self.exception_callbacks = []
54
55 self.on_msg(self._receive_msg)
56
57 @property
58 def drawing(self):
59 return self._drawing
60
61 @drawing.setter
62 def drawing(self, drawing):
63 self._drawing = drawing
64 self.refresh()
65
66 def refresh(self):
67 '''
68 Redraw the displayed output with the current value of self.drawing.
69 '''
70 self._image = self.drawing.as_svg()
71
72 def _receive_msg(self, _, content, buffers):
73 if not isinstance(content, dict):
74 return
75 name = content.get('name')
76 callbacks = {
77 'mousedown': self.mousedown_callbacks,
78 'mousemove': self.mousemove_callbacks,
79 'mouseup': self.mouseup_callbacks,
80 'timed': self.timed_callbacks,
81 }.get(name, ())
82 try:
83 if callbacks:
84 if name == 'timed':
85 self._call_handlers(callbacks, content)
86 else:
87 self._call_handlers(callbacks, content.get('x'),
88 content.get('y'), content)
89 except BaseException as e:
90 suppress = any(
91 handler(self, e)
92 for handler in self.exception_callbacks
93 )
94 if not suppress:
95 raise
96 finally:
97 if name == 'timed':
98 self._frame_blocked += 1
99 else:
100 self._mousemove_blocked += 1
101
102
103 def mousedown(self, handler, remove=False):
104 '''
105 Register (or unregister) a handler for the mousedown event.
106
107 Arguments:
108 remove: If True, unregister, otherwise register.
109 '''
110 self.on_msg
111 self._register_handler(
112 self.mousedown_callbacks, handler, remove=remove)
113
114 def mousemove(self, handler, remove=False):
115 '''
116 Register (or unregister) a handler for the mousemove event.
117
118 Arguments:
119 remove: If True, unregister, otherwise register.
120 '''
121 self._register_handler(
122 self.mousemove_callbacks, handler, remove=remove)
123
124 def mouseup(self, handler, remove=False):
125 '''
126 Register (or unregister) a handler for the mouseup event.
127
128 Arguments:
129 remove: If True, unregister, otherwise register.
130 '''
131 self._register_handler(
132 self.mouseup_callbacks, handler, remove=remove)
133
134 def timed(self, handler, remove=False):
135 '''
136 Register (or unregister) a handler for the timed event.
137
138 Arguments:
139 remove: If True, unregister, otherwise register.
140 '''
141 self._register_handler(
142 self.timed_callbacks, handler, remove=remove)
143
144 def on_exception(self, handler, remove=False):
145 '''
146 Register (or unregister) a handler for exceptions in other handlers.
147
148 If any handler returns True, the exception is suppressed.
149
150 Arguments:
151 remove: If True, unregister, otherwise register.
152 '''
153 self._register_handler(
154 self.exception_callbacks, handler, remove=remove)
155
156 def _register_handler(self, callback_list, handler, remove=False):
157 if remove:
158 callback_list.remove(handler)
159 else:
160 callback_list.append(handler)
161
162 def _call_handlers(self, callback_list, *args, **kwargs):
163 for callback in callback_list:
164 callback(self, *args, **kwargs)