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