# Python imports # Lib imports import gi gi.require_version('Gtk', '3.0') gi.require_version('Gdk', '3.0') gi.require_version('GtkSource', '4') from gi.repository import Gtk from gi.repository import Gdk from gi.repository import GtkSource from gi.repository import Gio # Application imports from .template.dynamic_widget_template import DynamicWidget class DragArea(Gtk.Fixed): def __init__(self): super(DragArea, self).__init__() self._children_content = [] self._setup_styling() self._setup_signals() self._set_up_dnd() self._subscribe_to_events() self._load_widgets() def _setup_styling(self): self.set_size_request(1800, 1800) def _setup_signals(self): self.connect("drag-data-received", self._on_drag_data_received) ... def _subscribe_to_events(self): ... def _load_widgets(self): ... def _set_up_dnd(self): # https://python-gtk-3-tutorial.readthedocs.io/en/latest/drag_and_drop.html # https://www.openshot.org/blog/2008/09/12/drag-drop-with-gtk-and-python/ # Gtk.DestDefaults.MOTION - This checks if the drag matches this widget's list of possible targets and actions, then calls the drag_status() as appropriate. # Gtk.DestDefaults.HIGHLIGHT - This draws a highlight on this widget as long as a drag is over this widget # Gtk.DestDefaults.DROP - When a drop occurs, if the drag matches this widget's list of possible targets and actions call drag_get_data() on behalf of the widget. Whether or not the drop is successful, call drag_finish(). If the action was a move and the drag was successful, then TRUE will be passed for the delete parameter to drag_finish(). # Gtk.DestDefaults.ALL - If set, specifies that all default actions should be taken flags = Gtk.DestDefaults.ALL # The target is a list of tuples containing target information that is human-understandable descriptions of the data type. # If TargetFlags set to 0, it means there are no constraints. # Gtk.TargetFlags.SAME_APP (1) - The target will only be selected for drags within a single application. # Gtk.TargetFlags.SAME_WIDGET (2) - The target will only be selected for drags within a single widget. # Gtk.TargetFlags.OTHER_APP (4) - The target will not be selected for drags within a single application # Gtk.TargetFlags.OTHER_WIDGET (8) - The target will not be selected for drags withing a single widget. # A random id for the target types. Note: Can be used in code to determin action flow PLAIN_TEXT_TARGET_TYPE = 70 URI_TARGET_TYPE = 80 text_target = Gtk.TargetEntry.new('text/plain', Gtk.TargetFlags(0), PLAIN_TEXT_TARGET_TYPE) uri_target = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE) targets = [ uri_target, text_target ] # The actions argument is a bitmask of or a combination of one or more of the following values − # Gdk.DragAction.COPY - data provided by the source will be copied to the destination widget. # Gdk.DragAction.MOVE - data provided by the source will be moved to the destination widget. # Gdk.DragAction.LINK - the destination widget will create a link to the provided data, rather than copy its contents. # Gdk.DragAction.PRIVATE - the destination widget is free to do anything with the copy of the data provided. # Gdk.DragAction.ASK - allows the destination widget to ask the user which action should be performed. action = Gdk.DragAction.COPY # Set the app to allow drops from any type of file. Supposedly works but doesn't for me. # self.drawArea.drag_dest_set(7, [], 2) self.drag_dest_set(flags, targets, action) def _on_drag_data_received(self, widget, drag_context, _x, _y, data, info, time): if info == 70: self._load_dnd_text(data, _x, _y) return if info == 80: self._load_dnd_files(data, _x, _y) return def _load_dnd_text(self, data, _x, _y): event_system.emit("set_widget_type", "TextAreaWidget") dynamic_widget = self.add_or_select_widget(x = _x, y = _y) body = dynamic_widget.get_body() body.buffer.set_text(data.get_text()) dynamic_widget.save() def _load_dnd_files(self, data, _x, _y): uris = data.get_uris() if len(uris) == 0: uris = data.get_text().split("\n") for uri in uris: event_system.emit("set_widget_type", "FileWidget") dynamic_widget = self.add_or_select_widget(x = _x, y = _y) dynamic_widget.get_body().set_file_path(uri) dynamic_widget.save() _y += 85 def _move_callback(self, widget = None, x = None, y = None): self.move(widget.get_parent(), x, y) def wrap_widget_in_trap(self, widget): eve_trap_box = Gtk.EventBox() eve_trap_box.connect("button-release-event", self._release_event) eve_trap_box.add(widget) eve_trap_box.show() return eve_trap_box def _release_event(self, widget = None, eve = None): return True def _handle_add(self, widget, x, y): widget_wrapped = self.wrap_widget_in_trap(widget) self._children_content.append(widget) self.put(widget_wrapped, x, y) def add_fixed_base_widgets(self, name = None, date = None, update_header_callback = None): entry = Gtk.Entry(text = name) wrapped_entry = self.wrap_widget_in_trap( entry ) date_label = Gtk.Label(label = date) wrapped_date = self.wrap_widget_in_trap( date_label ) ctx = entry.get_style_context() ctx.add_class("drag-area-title") entry.connect("changed", update_header_callback) entry.set_width_chars(25) entry.set_max_length(25) self.put(wrapped_entry, 30, 30) self.put(wrapped_date, 30, 65) def add_or_select_widget(self, widget = None, eve = None, x = None, y = None): if eve: x = eve.x y = eve.y dynamic_widget = DynamicWidget(self._move_callback, None, x, y) self._handle_add(dynamic_widget, x, y) dynamic_widget.save() return dynamic_widget def load_path_to_widget(self, path): dynamic_widget = DynamicWidget(self._move_callback, path) x = dynamic_widget.get_header()._current_x y = dynamic_widget.get_header()._current_y self._handle_add(dynamic_widget, x, y)