diff --git a/enable/tools/drop_tool.py b/enable/tools/drop_tool.py index a6c86367a..a91ee7332 100644 --- a/enable/tools/drop_tool.py +++ b/enable/tools/drop_tool.py @@ -12,39 +12,110 @@ from __future__ import absolute_import, print_function, division +from traits.api import Enum + from enable.base_tool import BaseTool class BaseDropTool(BaseTool): """ Abstract base class for tools that handle drag and drop """ + default_drag_result = Enum("copy", "move", "link", "cancel", + "error", "none") + def normal_drag_over(self, event): """ Handle dragging over the component """ + if event.handled: + return try: - result = self.accepts_drop(event.obj) - self.component.window.set_drag_result(result) + result = self.get_drag_result((event.x, event.y), event.obj) + if result is not None: + self.component.window.set_drag_result(result) + event.handled = True except Exception: self.component.window.set_drag_result("error") raise def normal_dropped_on(self, event): - if self.accepts_drop(event.obj) != "none": - self.handle_drop(event.obj) + if event.handled: + return + position = (event.x, event.y) + if self.accept_drop(position, event.obj): + self.handle_drop(position, event.obj) + event.handled = True + + def get_drag_result(self, position, obj): + """ The type of drag that will happen + + By default, if the dragged objects are available this method calls + accept_drop() and returns "none" if the result is False, otherwise + it returns the value of default_drag_result. + + Parameters + ---------- + + position : + The coordinates of the drag over event + + obj : any + The object(s) being dragged, if available. Some backends (such as + Wx) may not be able to provide the object being dragged, in which + case `obj` will be `None`. + + Returns + ------- + + Either None, if the drop should be ignored by this tool and not + handled, or one of the keys of DRAG_RESULTS_MAP: "none", "copy, "move", + "link", "cancel" or "error". + + """ + if obj is not None: + # if we have the object, see if we can accept + if not self.accept_drop(position, obj): + return None - def accepts_drop(self, urls): - """ Whether or not to accept the drag, and the type of drag + return self.default_drag_result - The return value is either "none", if the drag is refused for the - dragged object types, or one of "copy", "move", or "link". + def accept_drop(self, position, obj): + """ Whether or not to accept the drop Subclasses should override this method. + Parameters + ---------- + + position : + The coordinates of the drag over event + + obj : any + The object(s) being dragged, if available. Some backends (such as + Wx) may not be able to provide the object being dragged, in which + case `obj` will be `None`. + + Returns + ------- + + True if the drop should be accepted, False otherwise. + """ raise NotImplementedError - def handle_drop(self, urls): + def handle_drop(self, position, obj): """ Handle objects being dropped on the component Subclasses should override this method. + + Parameters + ---------- + + position : + The coordinates of the drag over event + + obj : any + The object(s) being dragged, if available. Some backends (such as + Wx) may not be able to provide the object being dragged, in which + case `obj` will be `None`. + """ raise NotImplementedError diff --git a/examples/enable/tools/drop_tool.py b/examples/enable/tools/drop_tool.py new file mode 100644 index 000000000..1e8ed5e00 --- /dev/null +++ b/examples/enable/tools/drop_tool.py @@ -0,0 +1,50 @@ +""" +This demonstrates the use of the drop tool. +""" +from enable.example_support import DemoFrame, demo_main +from enable.api import Component, Container, Label, Window +from enable.tools.drop_tool import BaseDropTool + +class Box(Component): + + resizable = "" + + def _draw_mainlayer(self, gc, view_bounds=None, mode="default"): + with gc: + gc.set_fill_color((1.0, 0.0, 0.0, 1.0)) + dx, dy = self.bounds + x, y = self.position + gc.rect(x, y, dx, dy) + gc.fill_path() + +class TextDropTool(BaseDropTool): + """ Example implementation of a drop tool """ + + def accept_drop(self, location, obj): + return True + + def handle_drop(self, location, objs): + if not isinstance(objs, list): + objs = [objs] + x, y = location + for obj in objs: + label = Label(text=str(obj), position=[x, y], bounds=[100, 50]) + self.component.add(label) + self.component.request_redraw() + y += 15 + + +class MyFrame(DemoFrame): + + def _create_window(self): + box = Box(bounds=[100.0, 100.0], position=[50.0, 50.0]) + container = Container(bounds=[500,500]) + container.add(box) + drop_tool = TextDropTool(component=container) + container.tools.append(drop_tool) + return Window(self, -1, component=container) + +if __name__ == "__main__": + demo_main(MyFrame) + +# EOF