diff --git a/GraphView/graphview.py b/GraphView/graphview.py index d5c5f9392..5e13e581b 100644 --- a/GraphView/graphview.py +++ b/GraphView/graphview.py @@ -43,6 +43,7 @@ from html import escape import gi from gi.repository import Gtk, Gdk, GdkPixbuf, GLib +import pickle #------------------------------------------------------------------------- # @@ -66,6 +67,7 @@ from gramps.gen.utils.libformatting import FormattingHelper from gramps.gen.utils.thumbnails import get_thumbnail_path +from gramps.gui.ddtargets import DdTargets from gramps.gui.dialog import OptionDialog, ErrorDialog, QuestionDialog2 from gramps.gui.display import display_url from gramps.gui.editors import EditPerson, EditFamily, EditTagList @@ -715,11 +717,13 @@ def __init__(self, view, dbstate, uistate): self._last_x = 0 self._last_y = 0 self._in_move = False + self._in_drag = False self.view = view self.dbstate = dbstate self.uistate = uistate self.parser = None self.active_person_handle = None + self.drag_person = None self.actions = Actions(dbstate, uistate, self.view.bookmarks) self.actions.connect('rebuild-graph', self.view.build_tree) @@ -874,6 +878,22 @@ def __init__(self, view, dbstate, uistate): # used for popup menu, prevent destroy menu as local variable self.menu = None + # setup drag and drop + drag_widget = self.get_widget() + drag_widget.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, [], + Gdk.DragAction.COPY) + drag_widget.connect("drag_data_get", self.cb_drag_data_get) + drag_widget.connect("drag_begin", self.cb_drag_begin) + drag_widget.connect("drag_end", self.cb_drag_end) + + tglist = Gtk.TargetList.new([]) + tglist.add(DdTargets.PERSON_LINK.atom_drag_type, + DdTargets.PERSON_LINK.target_flags, + DdTargets.PERSON_LINK.app_id) + # allow drag to a text document, info on drag_get will be 0L ! + tglist.add_text_targets(0) + drag_widget.drag_source_set_target_list(tglist) + def add_popover(self, widget, container): """ Add popover for button. @@ -1182,6 +1202,7 @@ def populate(self, active_person): # set the busy cursor, so the user knows that we are working self.uistate.set_busy_cursor(True) + self._in_drag = False self.clear() self.active_person_handle = active_person @@ -1316,6 +1337,7 @@ def button_release(self, item, target, event): """ Exit from scroll mode when button release. """ + self._in_drag = False button = event.get_button()[1] if((button == 1 or button == 2) and event.type == getattr(Gdk.EventType, "BUTTON_RELEASE")): @@ -1344,6 +1366,35 @@ def motion_notify_event(self, item, target, event): (event.y_root - self._last_y) * scale_coef) self.vadjustment.set_value(new_y) return True + + if self._in_drag and (event.type == Gdk.EventType.MOTION_NOTIFY): + # start drag when cursor moved more then 5 + # to separate it from simple click + if ((abs(self._last_x - event.x) > 5) + or (abs(self._last_x - event.x) > 5)): + self.uistate.set_busy_cursor(False) + # Remove all single click events + for click_item in self.click_events: + if not click_item.is_destroyed(): + GLib.source_remove(click_item.get_id()) + self.click_events.clear() + + # translate to drag_widget coords + drag_widget = self.get_widget() + scale_coef = self.canvas.get_scale() + bounds = self.canvas.get_root_item().get_bounds() + height_canvas = bounds.y2 - bounds.y1 + x = self._last_x * scale_coef - self.hadjustment.get_value() + y = ((height_canvas + self._last_y) * scale_coef - + self.vadjustment.get_value()) + + drag_widget.drag_begin_with_coordinates( + drag_widget.drag_source_get_target_list(), + Gdk.DragAction.COPY, + Gdk.ModifierType.BUTTON1_MASK, + event, + x, y) + return True return False def set_zoom(self, value): @@ -1387,6 +1438,7 @@ def select_node(self, item, target, event): if button == 1 and node_class == 'node': # left mouse self.uistate.set_busy_cursor(True) + self.drag_person = self.dbstate.db.get_person_from_handle(handle) if handle == self.active_person_handle: # Find a parent of the active person so that they can become # the active person, if no parents then leave as the current @@ -1397,7 +1449,6 @@ def select_node(self, item, target, event): else: # unset busy cursor as we don't change active person self.uistate.set_busy_cursor(False) - return True # redraw the graph based on the selected person # schedule after because double click can occur @@ -1407,6 +1458,11 @@ def select_node(self, item, target, event): context = GLib.main_context_default() self.click_events.append(context.find_source_by_id(click_event_id)) + # go to drag mode, applyed on motion event + self._in_drag = True + self._last_x = event.x + self._last_y = event.y + elif button == 3 and node_class: # right mouse if node_class == 'node': self.menu = PopupMenu(self, 'person', handle) @@ -1422,6 +1478,30 @@ def select_node(self, item, target, event): return True + def cb_drag_begin(self, widget, data): + """Set up some inital conditions for drag. Set up icon.""" + self._in_drag = True + widget.drag_source_set_icon_name('gramps-person') + + def cb_drag_end(self, widget, data): + """Set up some inital conditions for drag. Set up icon.""" + self._in_drag = False + + def cb_drag_data_get(self, widget, context, sel_data, info, time): + """ + Returned parameters after drag. + Specified for 'person-link', for others return text info about person. + """ + tgs = [x.name() for x in context.list_targets()] + if info == DdTargets.PERSON_LINK.app_id: + data = (DdTargets.PERSON_LINK.drag_type, + id(self), self.drag_person.handle, 0) + sel_data.set(sel_data.get_target(), 8, pickle.dumps(data)) + elif ('TEXT' in tgs or 'text/plain' in tgs) and info == 0: + format_helper = FormattingHelper(self.dbstate) + sel_data.set_text( + format_helper.format_person(self.drag_person, 11),-1) + def find_a_parent(self, handle): """ Locate a parent from the first family that the selected person is a