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