def __init__(self, df, to_show, widths, wrap_around, **kwargs): super().__init__(**kwargs) self.add_class("content") d = Event(source=self, watched_events=['wheel', "mousemove", "mouseleave"]) d.on_dom_event(self.event_handler) self.to_show = to_show self.num_rows = min(len(df), to_show if to_show % 2 == 0 else to_show + 1) self.idx = 0 self.wrap_around = wrap_around self.df = df self.records = df.to_records() def row_on_click(event): if event["new"] != -1: self.value = event["new"] self.rows = deque([ _Row(data=self.records[i], widths=widths, on_click=row_on_click, style=["row_even", "row_odd"][i % 2]) for i in range(self.num_rows) ]) self.children = list(self.rows)[0:self.to_show]
class MouseDataWidget(MouseData): """Get data from mouse input, using ipywidget/ipyevent methods (Jupyter: %matplotlib inline) usage: data = MouseDataWidget(); data.display(); ... after input, stores 'data.m' points, with locations 'data.X' and targets 'data.Y' plot image widget available as "data.image" """ def __init__(self, *args, **kwargs): from ipywidgets import Image from ipyevents import Event #self.plot = None # set in super? super().__init__(*args, **kwargs) self.image = Image(value=blank_png, format='png') self.no_drag = Event(source=self.image, watched_events=['dragstart'], prevent_default_action=True) self.events = Event(source=self.image, watched_events=['click', 'contextmenu'], prevent_default_action=True) self.events.on_dom_event(self.__on_click) self.update_plot() def __get_coord(self, event): return np.array([event['dataX'] / self.w, 1 - event['dataY'] / self.h ]) * 2 / (1 - 2 * self.border) - 1 - self.border def update_plot(self): import matplotlib.pyplot as plt from .plot import plotClassify2D fig = plt.figure(figsize=(6, 6)) plt.gcf().add_axes((self.border, self.border, 1 - 2 * self.border, 1 - 2 * self.border)) # full width figure plt.axis(self.lim) plt.gca().set_xticks([]) plt.gca().set_yticks([]) if (self.m > 0) or (self.plot is not None): if self.plot is not None: self.plot(self.X, self.Y) # user-provided plot f'n else: fig.gca().scatter(self.X[:, 0], self.X[:, 1], c=self.Y) # or default self.image.value = plot_to_png(plt.gcf()) self.w, self.h = plt.gcf().canvas.get_renderer( ).get_canvas_width_height() fig.clear() def display(self, fig=None, **kwargs): from IPython.display import display if self.plot is None: print( "Default plot. Left-click to add data; shift/alt/ctrl combos determine target value; right click to remove data." ) display(self.image) def __on_click(self, event): if (event['type'] == 'click'): # left click = add point self.add_point(self.__get_coord(event), wkeys(event)) elif (event['type'] == 'contextmenu') and (self.m > 0): # right click = remove point self.remove_nearest(self.__get_coord(event)) self.update_plot()
def test_callbacks(): # Test that the initial callbacks look right, that we can add one, and # that clearing removes them all. event_widget = Event() assert len(event_widget._dom_handlers.callbacks) == 0 def noop(event): pass def noop2(event): pass event_widget.on_dom_event(noop) assert noop in event_widget._dom_handlers.callbacks assert len(event_widget._dom_handlers.callbacks) == 1 # Add noop2.... event_widget.on_dom_event(noop2) assert event_widget._dom_handlers.callbacks == [noop, noop2] # Try removing noop2 event_widget.on_dom_event(noop2, remove=True) assert event_widget._dom_handlers.callbacks == [noop] # Finally, clear all callbacks event_widget.reset_callbacks() assert len(event_widget._dom_handlers.callbacks) == 0
class Icon(WidgetWrapper): def __init__(self, fontawesome_icon: str): """Clickable icon Parameters ---------- fontawesome_icon: str icon string from the fontawesome https://fontawesome.com. For example, "fab fa-500px" """ self.el_icon = widgets.HTML( value=f'<i class="{fontawesome_icon}"></i>') self.on_click_callback = lambda: 1 self.event_listener = Event(source=self.el_icon, watched_events=['click']) self.event_listener.on_dom_event(self.fire_on_click_event) @property def widget(self): return self.el_icon def on_click(self, callback: Callable[[], None]): self.on_click_callback = callback def fire_on_click_event(self, event: dict): if event['type'] == 'click': self.on_click_callback()
def test_setting_xy_coordinates_good_value(): # Setting to a value should just work event_widget = Event() good_name = 'data' event_widget.xy_coordinate_system = good_name assert event_widget.xy_coordinate_system == good_name
def test_negative_wait_raises_error(): # negative wait should raise an error. event_widget = Event() with pytest.raises(ValueError) as e: event_widget.wait = -20 assert 'wait must be set to a non-negative integer. ' in str(e)
def test_floating_point_wait_raises_error(): # A floating point value should reaise a TraitletError event_widget = Event() with pytest.raises(traitlets.traitlets.TraitError) as e: event_widget.wait = 15.0 assert "'wait' trait of an Event instance" in str(e)
def construct_arrow_handler(self): """ Should only be run once """ disp_events = Event(source=self, watched_events=['keydown']) def handle_disp(event): if event["type"] == "keydown": self.key_funcs[event["key"]]() disp_events.on_dom_event(handle_disp)
def test_invalid_event_name(): # An unrecognized event name should generate a ValueError event_widget = Event() bad_name = 'this is not a valid event name' with pytest.raises(ValueError) as e: event_widget.watched_events = [bad_name] assert bad_name in str(e) assert 'not supported. The supported ' in str(e)
def __init__(self, data, widths, on_click, style, _types=None, **kwargs): super().__init__(**kwargs) self.add_class(style) self.observe(on_click, "value") d = Event(source=self, watched_events=['click']) d.on_dom_event(self.on_click) self.data = data self.cells = [_Cell(x, w) for x, w in zip(data, widths)] self.cells[0].add_class("index") self.children = self.cells
def __init__(self, **kwargs): """Same constructor as `ipywidgets.widgets.Button`""" self.el_btn = widgets.Button(**kwargs) self.el_btn.on_click(self.fire_on_click_event) self.on_click_callback = lambda x: x self.on_mouseenter_callback = lambda x: x self.on_mouseleave_callback = lambda x: x self.event_listener = Event( source=self.el_btn, watched_events=['mouseenter', 'mouseleave']) self.event_listener.on_dom_event(self.fire_mouse_event)
def test_setting_xy_coordinate_bad_value(): # Setting xy_coordinate_system to a bad value should raise a ValueError event_widget = Event() bad_name = 'this is not a valid name' with pytest.raises(ValueError) as e: event_widget.xy_coordinate_system = bad_name assert "are not supported. The supported coordinates are" in str(e) assert bad_name in str(e)
def test_invalid_slow_method_raises_error(): # Setting throttle_or_debounce to an invalid name should raise # a ValueError. event_widget = Event() bad_name = 'this is not a valid name' with pytest.raises(ValueError) as e: event_widget.throttle_or_debounce = bad_name assert bad_name in str(e) assert 'The event rate limiting method' in str(e)
def __init__(self, scene_proxy, bridge, *args, **kw): super(IPyRemoteWidget, self).__init__(scene_proxy, bridge, *args, **kw) self.image = Image(format='PNG') self.event = Event(source=self.image, watched_events=[ 'dragstart', 'mouseenter', 'mouseleave', 'mousedown', 'mouseup', 'mousemove', 'wheel', 'keyup', 'keydown' ], prevent_default_action=True) self.event.on_dom_event(self.handle_ipyevent) self._update_image()
def construct_click_handler(self): """ Should only be run once """ disp_events = Event(source=self, watched_events=['click']) def handle_disp(event): if event["type"] == "click": x = self.images[self.page_index]._size[0] if event["relativeX"] > (x / 2): self.next_page() elif event["relativeX"] < (x / 2): self.previous_page() disp_events.on_dom_event(handle_disp)
def setup_jupyter(self): """ Sets up elements on the page for use with a Jupyter notebook. """ self._label = Label('Move the cursor over the cell and use the left and right arrow keys to navigate') self._hbox = HBox() self._html = HTML('<h3>Label position?</h3>') self._inner = VBox() self._vbox = VBox([self._html, self._inner, self._label]) self._event = Event(source=self._vbox, watched_events=['keydown']) self._event.on_dom_event(self.handle_event) return self
def __init__(self, *args, **kwargs): from ipywidgets import Image from ipyevents import Event #self.plot = None # set in super? super().__init__(*args, **kwargs) self.image = Image(value=blank_png, format='png') self.no_drag = Event(source=self.image, watched_events=['dragstart'], prevent_default_action=True) self.events = Event(source=self.image, watched_events=['click', 'contextmenu'], prevent_default_action=True) self.events.on_dom_event(self.__on_click) self.update_plot()
def __init__(self, fontawesome_icon: str): """Clickable icon Parameters ---------- fontawesome_icon: str icon string from the fontawesome https://fontawesome.com. For example, "fab fa-500px" """ self.el_icon = widgets.HTML( value=f'<i class="{fontawesome_icon}"></i>') self.on_click_callback = lambda: 1 self.event_listener = Event(source=self.el_icon, watched_events=['click']) self.event_listener.on_dom_event(self.fire_on_click_event)
def test_setting_wait_with_debounce_set_preserves_debounce(slow_method): # If debounce is set but wait is zero and wait is then set to something # non-zero then throttle_or_debounce should still stay debounce. event_widget = Event() event_widget.throttle_or_debounce = slow_method # Make sure wait is currently zero... assert event_widget.wait == 0 # This shouldn't change throttle or debounce event_widget.wait = 20 assert event_widget.throttle_or_debounce == slow_method
def test_setting_wait_with_no_throttle_or_debounce(): # If wait is set to something non-zero AND neither throttle # nor debounce has been set then it should be set to # throttle. event_widget = Event() # Make sure throttle_or_debounce is not set... assert not event_widget.throttle_or_debounce # ...and that wait is currently zero assert event_widget.wait == 0 # This implicitly sets throttle_or_debounce event_widget.wait = 20 assert event_widget.throttle_or_debounce == 'throttle'
class HTML(WidgetWrapper): def __init__(self, html: str): self.el = widgets.HTML(value=html) self.on_click_callback = lambda: 1 self.event_listener = Event(source=self.el, watched_events=['click']) self.event_listener.on_dom_event(self.fire_on_click_event) @property def widget(self): return self.el def on_click(self, callback: Callable[[], None]): self.on_click_callback = callback def fire_on_click_event(self, event: dict): if event['type'] == 'click': self.on_click_callback()
def test_properties_work(): # These are a little silly, but will get this to 100% test coverage event_widget = Event() # Note the extra underscore on the right hand sides assert (event_widget.supported_key_events == event_widget._supported_key_events) assert (event_widget.supported_mouse_events == event_widget._supported_mouse_events)
def __init__(self, globals_dict): self.status_text = display(Code(""), display_id=True) self._globals_dict = globals_dict self._methods = {} self.stop_button = Button(description="Stop") self.stop_button.on_click(self.on_stop_button_clicked) self._globals_dict["canvas"] = Canvas() self.kb_mon = Event(source=self.canvas, watched_events=['keydown', 'keyup'], wait=1000 // FRAME_RATE, prevent_default_actions=True) self.output_text = "" self.color_strings = {"default": "#888888"} match_255 = r"(?:(?:2(?:(?:5[0-5])|(?:[0-4][0-9])))|(?:[01]?[0-9]{1,2}))" match_alpha = r"(?:(?:1(?:\.0*)?)|(?:0(?:\.[0-9]*)?))" match_360 = r"(?:(?:3[0-5][0-9])|(?:[0-2]?[0-9]{1,2}))" match_100 = r"(?:100|[0-9]{1,2})" self.regexes = [ re.compile(r"#[0-9A-Fa-f]{6}"), re.compile(r"rgb\({},{},{}\)".format(match_255, match_255, match_255)), re.compile(r"rgba\({},{},{},{}\)".format(match_255, match_255, match_255, match_alpha)), re.compile(r"hsl\({},{}%,{}%\)".format(match_360, match_100, match_100)), re.compile(r"hsla\({},{}%,{}%,{}\)".format(match_360, match_100, match_100, match_alpha)) ] self.width, self.height = DEFAULT_CANVAS_SIZE self.mouse_x = 0 self.mouse_y = 0 self.mouse_is_pressed = False self.key = "" self._keys_held = {} # Settings for drawing text (https://ipycanvas.readthedocs.io/en/latest/drawing_text.html). self.font_settings = { 'size': 12.0, 'font': 'sans-serif', 'baseline': 'top', 'align': 'left' }
class Button(WidgetWrapper): """An extended button widget that support mouse over event. """ def __init__(self, **kwargs): """Same constructor as `ipywidgets.widgets.Button`""" self.el_btn = widgets.Button(**kwargs) self.el_btn.on_click(self.fire_on_click_event) self.on_click_callback = lambda x: x self.on_mouseenter_callback = lambda x: x self.on_mouseleave_callback = lambda x: x self.event_listener = Event( source=self.el_btn, watched_events=['mouseenter', 'mouseleave']) self.event_listener.on_dom_event(self.fire_mouse_event) @property def widget(self): return self.el_btn def on_click(self, callback: Callable[['Button'], None]): """Register for on click event""" self.on_click_callback = callback def on_mouseenter(self, callback: Callable[['Button'], None]): """Register for on mouse enter event""" self.on_mouseenter_callback = callback def on_mouseleave(self, callback: Callable[['Button'], None]): """Register for on mouse leave event""" self.on_mouseleave_callback = callback def fire_on_click_event(self, _btn: widgets.Button): """Fire the on_click event when users click the button""" self.on_click_callback(self) def fire_mouse_event(self, event: dict): """Show the content on mouse over""" if event['type'] == 'mouseenter': self.on_mouseenter_callback(self) else: self.on_mouseleave_callback(self)
def __init__(self, im_path=None, label=None, im_name=None, im_index=None, display_label=True, image_width='50px', image_height=None): self.display_label = display_label self.label = 'None' self.image = Image(layout=Layout(display='flex', justify_content='center', align_items='center', align_content='center', width=image_width, height=image_height), ) if self.display_label: # both image and label self.label = HTML( value='?', layout=Layout(display='flex', justify_content='center', align_items='center', align_content='center'), ) else: # no label (capture image case) self.im_name = im_name self.im_index = im_index self.image.layout.border = 'solid 1px gray' self.image.layout.object_fit = 'contain' super().__init__( layout=Layout(align_items='center', margin='3px', padding='2px')) if not im_path: self.clear() self.d = Event(source=self, watched_events=['click'])
def __init__(self, scene_proxy, bridge, *args, **kw): super(IPyRemoteWidget, self).__init__(scene_proxy, bridge, *args, **kw) self.image = Image(format='PNG') self.event = Event( source=self.image, watched_events=[ 'dragstart', 'mouseenter', 'mouseleave', 'mousedown', 'mouseup', 'mousemove', 'wheel', 'keyup', 'keydown' ], prevent_default_action=True ) self.event.on_dom_event(self.handle_ipyevent) self._update_image()
def __init__(self, label_name): self.clix = 0 # current landmark index self.length = len(db) self.n_panels = 5 self.panels = [ImageAndLabel() for _ in range(self.n_panels)] self.label_name = label_name self.landmarks = Landmarks({k: None for k in db.keys()}) self.c = widgets.HTML('Click or type on me!') button = widgets.Button(description="Save", layout=widgets.Layout(width='auto')) button.on_click(self.save) w = widgets.HBox([*self.panels]) w = widgets.VBox([w, self.c, button]) self.widget = w self.d = Event(source=self.widget, watched_events=['keydown']) self.d.on_dom_event(self.handle_event) self.target_label = 'q' self.render() display(self.widget)
def __init__(self, obj): self.value = obj # we should compute it -- or use it -- as a 'member' if hasattr(obj, '_latex_list'): s = obj._latex_list() elif hasattr(obj, '_latex_'): s = obj._latex_() else: s = obj.__str__() h = HTMLMath('$%s$' % s) h.add_class('explorable-value') self.clc = Event(source=h, watched_events=['click']) super(ExplorableValue, self).__init__( (h,), layout = Layout(border='1px solid green', padding='2px 50px 2px 2px') )
def set_widget(self, jp_imgw): """Call this method with the Jupyter image widget (image_w) that will be used. """ super(ImageViewEvent, self).set_widget(jp_imgw) self.jp_evt = EventListener(source=jp_imgw) self.jp_evt.watched_events = [ 'keydown', 'keyup', 'mouseenter', 'mouseleave', 'mousedown', 'mouseup', 'mousemove', 'wheel', 'contextmenu' ] self.jp_evt.prevent_default_action = True self.jp_evt.on_dom_event(self._handle_event) self.logger.info("installed event handlers") return self.make_callback('map')
def init(*args, **kwargs): global flag, downflag, shiftflag w = widgets.Image(value=m.system.image_data(), width=600) d = Event(source=w, watched_events=[ 'mousedown', 'mouseup', 'mousemove', 'keyup', 'keydown', 'wheel' ]) no_drag = Event(source=w, watched_events=['dragstart'], prevent_default_action=True) d.on_dom_event(listen_mouse) run = widgets.ToggleButton( value=False, description='Run', disabled=False, button_style='', # 'success', 'info', 'warning', 'danger' or '' tooltip='run the simulation', icon='play') pause = widgets.ToggleButton( value=False, description='Pause', disabled=False, button_style='', # 'success', 'info', 'warning', 'danger' or '' tooltip='pause the simulation', icon='pause') reset = widgets.ToggleButton( value=False, description='Reset', disabled=False, button_style='', # 'success', 'info', 'warning', 'danger' or '' tooltip='reset the simulation', icon='stop') def onToggleRun(b): global flag if run.value: run.button_style = 'success' pause.value = False pause.button_style = '' reset.value = False reset.button_style = '' flag = True else: run.button_style = '' flag = False def onTogglePause(b): global flag if pause.value: pause.button_style = 'success' run.value = False run.button_style = '' reset.value = False reset.button_style = '' flag = False else: pause.button_style = '' flag = True def onToggleReset(b): global flag if reset.value: reset.button_style = 'success' pause.value = False pause.button_style = '' run.value = False run.button_style = '' flag = False m.Universe.reset() else: reset.button_style = '' #w = create_simulation() buttons = widgets.HBox([run, pause, reset]) run.observe(onToggleRun, 'value') pause.observe(onTogglePause, 'value') reset.observe(onToggleReset, 'value') box = widgets.VBox([w, buttons]) display(box) def background_threading(): global flag while True: m.Simulator.context_make_current() if flag: m.step() w.value = m.system.image_data() m.Simulator.context_release() time.sleep(0.01) t = threading.Thread(target=background_threading) t.start()
class Core: # All constants that will be injected into global scope in the user"s cell global_constants = { "pi": pi } ignite_globals = _ignite_globals def __init__(self, globals_dict): self.status_text = display(Code(""), display_id=True) self._globals_dict = globals_dict self._methods = {} self.stop_button = Button(description="Stop") self.stop_button.on_click(self.on_stop_button_clicked) self._globals_dict["canvas"] = Canvas() self.kb_mon = Event(source=self.canvas, watched_events=['keydown', 'keyup'], wait=1000 // FRAME_RATE, prevent_default_actions=True) self.output_text = "" self.color_strings = { "default": "#888888" } match_255 = r"(?:(?:2(?:(?:5[0-5])|(?:[0-4][0-9])))|(?:[01]?[0-9]{1,2}))" match_alpha = r"(?:(?:1(?:\.0*)?)|(?:0(?:\.[0-9]*)?))" match_360 = r"(?:(?:3[0-5][0-9])|(?:[0-2]?[0-9]{1,2}))" match_100 = r"(?:100|[0-9]{1,2})" self.regexes = [ re.compile(r"#[0-9A-Fa-f]{6}"), re.compile(r"rgb\({},{},{}\)".format(match_255, match_255, match_255)), re.compile(r"rgba\({},{},{},{}\)".format(match_255, match_255, match_255, match_alpha)), re.compile(r"hsl\({},{}%,{}%\)".format(match_360, match_100, match_100)), re.compile(r"hsla\({},{}%,{}%,{}\)".format(match_360, match_100, match_100, match_alpha)) ] self.width, self.height = DEFAULT_CANVAS_SIZE self.mouse_x = 0 self.mouse_y = 0 self.mouse_is_pressed = False self.key = "" self._keys_held = {} # Settings for drawing text (https://ipycanvas.readthedocs.io/en/latest/drawing_text.html). self.font_settings = { 'size': 12.0, 'font': 'sans-serif', 'baseline': 'top', 'align': 'left' } ### Properties ### @property @ignite_global def canvas(self) -> Canvas: return self._globals_dict["canvas"] @property @ignite_global def mouse_x(self): return self._globals_dict["mouse_x"] @mouse_x.setter def mouse_x(self, val): self._globals_dict["mouse_x"] = val @property @ignite_global def mouse_y(self): return self._globals_dict["mouse_y"] @mouse_y.setter def mouse_y(self, val): self._globals_dict["mouse_y"] = val @property @ignite_global def mouse_is_pressed(self): return self._globals_dict["mouse_is_pressed"] @mouse_is_pressed.setter def mouse_is_pressed(self, val): self._globals_dict["mouse_is_pressed"] = val @property @ignite_global def key(self): return self._globals_dict["key"] @key.setter def key(self, val): self._globals_dict["key"] = val @property @ignite_global def width(self): return self._globals_dict["width"] @width.setter def width(self, val): self._globals_dict["width"] = val self.canvas.width = val @property @ignite_global def height(self): return self._globals_dict["height"] @height.setter def height(self, val): self._globals_dict["height"] = val self.canvas.height = val ### Library init ### # Updates last activity time @staticmethod def refresh_last_activity(): global _sparkplug_last_activity _sparkplug_last_activity = time.time() # Creates canvas and starts thread def start(self, methods): self._methods = methods draw = self._methods.get("draw", None) if draw: self.print_status("Running...") display(self.stop_button) display(self.canvas) self.output_text_code = display(Code(self.output_text), display_id=True) self.canvas.on_mouse_down(self.on_mouse_down) self.canvas.on_mouse_up(self.on_mouse_up) self.canvas.on_mouse_move(self.on_mouse_move) self.kb_mon.on_dom_event(self.handle_kb_event) # Initialize text drawing settings for the canvas. () self.canvas.font = f"{self.font_settings['size']}px {self.font_settings['font']}" self.canvas.text_baseline = 'top' self.canvas.text_align = 'left' thread = threading.Thread(target=self.loop) thread.start() def stop(self, message="Stopped"): global _sparkplug_running if not _sparkplug_running: return _sparkplug_running = False self.print_status(message) self.kb_mon.reset_callbacks() self.kb_mon.close() # Assuming we're using IPython to draw the canvas through the display() function. # Commenting this out for now, it throws exception since it does not derive BaseException # raise IpyExit # Loop method that handles drawing and setup def loop(self): global _sparkplug_active_thread_id, _sparkplug_running # Set active thread to this thread. This will stop any other active thread. current_thread_id = threading.current_thread().native_id _sparkplug_active_thread_id = current_thread_id _sparkplug_running = True self.refresh_last_activity() draw = self._methods.get("draw", None) setup = self._methods.get("setup", None) if setup: try: setup() except Exception as e: self.stop("Error in setup() function: " + str(e)) return while _sparkplug_running: if _sparkplug_active_thread_id != current_thread_id \ or time.time() - _sparkplug_last_activity > NO_ACTIVITY_THRESHOLD: self.stop("Stopped due to inactivity") return if not draw: self.stop("Done drawing.") return with hold_canvas(self.canvas): try: draw() except Exception as e: self.stop("Error in draw() function: " + str(e)) return time.sleep(1 / FRAME_RATE) # Prints status to embedded error box def print_status(self, msg): self.status_text.update(Code(msg)) # Prints output to embedded output box # Can't use @validate_args decorator for functions actually accepting variable arguments @ignite_global def print(self, *args, sep=' ', end='\n', flush=True): global _sparkplug_running self.output_text += sep.join([str(arg) for arg in args]) + end if _sparkplug_running and flush: self.output_text_code.update(Code(self.output_text)) # Update mouse_x, mouse_y, and call mouse_down handler def on_mouse_down(self, x, y): self.refresh_last_activity() self.mouse_x, self.mouse_y = int(x), int(y) self.mouse_is_pressed = True mouse_down = self._methods.get("mouse_down", None) if mouse_down: mouse_down() # Update mouse_x, mouse_y, and call mouse_up handler def on_mouse_up(self, x, y): self.refresh_last_activity() self.mouse_x, self.mouse_y = int(x), int(y) self.mouse_is_pressed = False mouse_up = self._methods.get("mouse_up", None) if mouse_up: mouse_up() # Update mouse_x, mouse_y, and call mouse_moved handler def on_mouse_move(self, x, y): self.refresh_last_activity() self.mouse_x, self.mouse_y = int(x), int(y) mouse_moved = self._methods.get("mouse_moved", None) if mouse_moved: mouse_moved() def on_stop_button_clicked(self, button): self.stop() @extern def handle_kb_event(self, event): pass ### User overrideable functions ### # The function bodies here do not matter, they are discarded @ignite_global(mutable=True) def setup(self): pass @ignite_global(mutable=True) def draw(self): pass @ignite_global(mutable=True) def mouse_up(self): pass @ignite_global(mutable=True) def mouse_down(self): pass @ignite_global(mutable=True) def mouse_moved(self): pass @ignite_global(mutable=True) def key_pressed(self): pass @ignite_global(mutable=True) def key_released(self): pass @ignite_global(mutable=True) def key_repeated(self): pass ### Global functions ### # From .util.helper_functions.keyboard_functions @extern def keys_held(self, *args): pass @extern def key_held(self, *args): pass # From .util.helper_functions.canvas_functions @extern def size(self, *args): pass @extern def fill_style(self): pass @extern def stroke_style(self, *args): pass @extern def clear(self, *args): pass @extern def background(self, *args): pass # From util.helper_functions.rect_functions @extern def rect(self, *args): pass @extern def fill_rect(self, *args): pass @extern def stroke_rect(self, *args): pass @extern def clear_rect(self, *args): pass # From util.helper_functions.square_functions @extern def square(self, *args): pass @extern def stroke_square(self, *args): pass @extern def fill_square(self, *args): pass # From util.helper_functions.circle_functions @extern def circle(self, *args): pass @extern def fill_circle(self, *args): pass @extern def stroke_circle(self, *args): pass # From util.helper_functions.ellipse_functions @extern def ellipse(self, *args): pass @extern def fill_ellipse(self, *args): pass @extern def stroke_ellipse(self, *args): pass # From util.helper_functions.arc_functions @extern def arc(self, *args): pass @extern def fill_arc(self, *args): pass @extern def stroke_arc(self, *args): pass # From util.helper_functions.triangle_functions @extern def triangle(self, *args): pass @extern def fill_triangle(self, *args): pass @extern def stroke_triangle(self, *args): pass # From util.helper_functions.text_functions @extern def text_size(self, *args): pass @extern def text_align(self, *args): pass @extern def text(self, *args): pass # From util.helper_functions.line_functions @extern def draw_line(self, *args): pass @extern def line(self, *args): pass @extern def line_width(self, *args): pass # An alias to line_width @extern def stroke_width(self, *args): pass ### Helper Functions ### # From util.helper_functions.misc_functions @extern def parse_color(self, *args, func_name="parse_color"): pass @extern def color(self, *args): pass @extern def parse_color_string(self, func_name, s): pass @extern def arc_args(self, *args): pass @extern def random(self, *args): pass @extern def randint(self, *args): pass
class IPyRemoteWidget(RemoteWidget): def __init__(self, scene_proxy, bridge, *args, **kw): super(IPyRemoteWidget, self).__init__(scene_proxy, bridge, *args, **kw) self.image = Image(format='PNG') self.event = Event( source=self.image, watched_events=[ 'dragstart', 'mouseenter', 'mouseleave', 'mousedown', 'mouseup', 'mousemove', 'wheel', 'keyup', 'keydown' ], prevent_default_action=True ) self.event.on_dom_event(self.handle_ipyevent) self._update_image() def _ipython_display_(self): display(self.image) # ###### Public protocol ############## def show(self): pass def show_image(self, data, format='PNG'): self.image.format = format self.image.value = data # ##### VTK Event handling ########## def on_render(self, data): self.show_image(base64_to_bytes(data['data']), format=data.get('format', 'PNG')) def on_cursor_changed(self, data): # self.setCursor(cursor) pass def handle_ipyevent(self, event): type = event['type'] shift = event.get('shiftKey', False) ctrl = event.get('ctrlKey', False) btn_map = {0: 'left', 1: 'right', 2: 'middle'} if type == 'mouseenter': self.on_enter(ctrl, shift) elif type == 'mouseleave': self.on_leave(ctrl, shift) elif type == 'mouseleave': self.on_leave(ctrl, shift) elif type == 'mousedown': repeat = 0 button = btn_map.get(event.get('button'), 'none') x, y = event.get('relativeX'), event.get('relativeY') self.on_mouse_press(ctrl, shift, x, y, button, repeat) elif type == 'mouseup': x, y = event.get('relativeX'), event.get('relativeY') self.on_mouse_release(ctrl, shift, x, y) elif type == 'mousemove': button = btn_map.get(event.get('button'), 'none') x, y = event.get('relativeX'), event.get('relativeY') self.on_mouse_move(ctrl, shift, button, x, y) elif type == 'wheel': dx = event.get('deltaX') dy = event.get('deltaY') dz = event.get('deltaZ') delta = dx + dy + dz self.on_wheel(delta) elif type == 'keydown': key = event.get('key') # Need to map the special keys correctly. key_sym = key self.on_key_press(ctrl, shift, key, key_sym) elif type == 'keyup': key = event.get('key') # Need to map the special keys correctly. key_sym = key self.on_key_release(ctrl, shift, key)
class ImageViewEvent(ImageViewJpw): def __init__(self, logger=None, rgbmap=None, settings=None): ImageViewJpw.__init__(self, logger=logger, rgbmap=rgbmap, settings=settings) self._button = 0 # maps EventListener events to callback handlers self._evt_dispatch = { 'mousedown': self.button_press_event, 'mouseup': self.button_release_event, 'mousemove': self.motion_notify_event, 'wheel': self.scroll_event, 'mouseenter': self.enter_notify_event, 'mouseleave': self.leave_notify_event, 'keydown': self.key_press_event, 'keyup': self.key_release_event, } # mapping from EventListener key events to ginga key events self._keytbl = { 'shiftleft': 'shift_l', 'shiftright': 'shift_r', 'controlleft': 'control_l', 'controlright': 'control_r', 'altleft': 'alt_l', 'altright': 'alt_r', 'osleft': 'super_l', 'osright': 'super_r', 'contextmenu': 'menu_r', 'backslash': 'backslash', 'space': 'space', 'escape': 'escape', 'enter': 'return', 'tab': 'tab', 'arrowright': 'right', 'arrowleft': 'left', 'arrowup': 'up', 'arrowdown': 'down', 'pageup': 'page_up', 'pagedown': 'page_down', 'f1': 'f1', 'f2': 'f2', 'f3': 'f3', 'f4': 'f4', 'f5': 'f5', 'f6': 'f6', 'f7': 'f7', 'f8': 'f8', 'f9': 'f9', 'f10': 'f10', 'f11': 'f11', 'f12': 'f12', } self._keytbl2 = { '`': 'backquote', '"': 'doublequote', "'": 'singlequote', } # Define cursors for pick and pan #hand = openHandCursor() hand = 'fleur' self.define_cursor('pan', hand) cross = 'cross' self.define_cursor('pick', cross) for name in ('motion', 'button-press', 'button-release', 'key-press', 'key-release', 'drag-drop', 'scroll', 'map', 'focus', 'enter', 'leave', 'pinch', 'rotate', 'pan', 'swipe', 'tap'): self.enable_callback(name) def set_widget(self, jp_imgw): """Call this method with the Jupyter image widget (image_w) that will be used. """ super(ImageViewEvent, self).set_widget(jp_imgw) self.jp_evt = EventListener(source=jp_imgw) self.jp_evt.watched_events = [ 'keydown', 'keyup', 'mouseenter', 'mouseleave', 'mousedown', 'mouseup', 'mousemove', 'wheel', 'contextmenu' ] self.jp_evt.prevent_default_action = True self.jp_evt.on_dom_event(self._handle_event) self.logger.info("installed event handlers") return self.make_callback('map') def _handle_event(self, event): # TODO: need focus events and maybe a map event # TODO: Set up widget as a drag and drop destination evt_kind = event['type'] handler = self._evt_dispatch.get(evt_kind, None) if handler is not None: return handler(event) return False def transkey(self, keycode, keyname=None): keycode = str(keycode).lower() if keyname is None: keyname = keycode self.logger.debug("key code in jupyter '%s'" % (keycode)) res = self._keytbl.get(keycode, None) if res is None: res = self._keytbl2.get(keyname, keyname) return res def get_key_table(self): return self._keytbl def focus_event(self, event, has_focus): return self.make_callback('focus', has_focus) def enter_notify_event(self, event): enter_focus = self.t_.get('enter_focus', False) if enter_focus: # TODO: set focus on canvas pass return self.make_callback('enter') def leave_notify_event(self, event): self.logger.debug("leaving widget...") return self.make_callback('leave') def key_press_event(self, event): keyname = self.transkey(event['code'], keyname=event['key']) self.logger.debug("key press event, key=%s" % (keyname)) return self.make_ui_callback('key-press', keyname) def key_release_event(self, event): keyname = self.transkey(event['code'], keyname=event['key']) self.logger.debug("key release event, key=%s" % (keyname)) return self.make_ui_callback('key-release', keyname) def button_press_event(self, event): x, y = event['dataX'], event['dataY'] self.last_win_x, self.last_win_y = x, y button = 0 button |= 0x1 << event['button'] self._button = button self.logger.debug("button event at %dx%d, button=%x" % (x, y, button)) data_x, data_y = self.check_cursor_location() return self.make_ui_callback('button-press', button, data_x, data_y) def button_release_event(self, event): x, y = event['dataX'], event['dataY'] self.last_win_x, self.last_win_y = x, y button = 0 button |= 0x1 << event['button'] self._button = 0 self.logger.debug("button release at %dx%d button=%x" % (x, y, button)) data_x, data_y = self.check_cursor_location() return self.make_ui_callback('button-release', button, data_x, data_y) def motion_notify_event(self, event): button = self._button x, y = event['dataX'], event['dataY'] self.last_win_x, self.last_win_y = x, y self.logger.debug("motion event at %dx%d, button=%x" % (x, y, button)) data_x, data_y = self.check_cursor_location() return self.make_ui_callback('motion', button, data_x, data_y) def scroll_event(self, event): x, y = event['dataX'], event['dataY'] self.last_win_x, self.last_win_y = x, y dx, dy = event['deltaX'], event['deltaY'] if (dx != 0 or dy != 0): # <= This browser gives us deltas for x and y # Synthesize this as a pan gesture event self.make_ui_callback('pan', 'start', 0, 0) self.make_ui_callback('pan', 'move', -dx, -dy) return self.make_ui_callback('pan', 'stop', 0, 0) # <= This code path should not be followed under normal # circumstances. # we leave it here in case we want to make the scroll # callback configurable in the future # TODO: calculate actual angle of direction if dy < 0: direction = 0.0 # up elif dy > 0: direction = 180.0 # down else: return False # 15 deg is standard 1-click turn for a wheel mouse num_deg = 15.0 self.logger.debug("scroll deg=%f direction=%f" % ( num_deg, direction)) data_x, data_y = self.check_cursor_location() return self.make_ui_callback('scroll', direction, num_deg, data_x, data_y)