diff --git a/GraphView/drag_n_drop.py b/GraphView/drag_n_drop.py index 060099019..e0fc73311 100644 --- a/GraphView/drag_n_drop.py +++ b/GraphView/drag_n_drop.py @@ -3,9 +3,12 @@ import pickle from gi.repository import Gtk, Gdk +from gramps.gen.db import DbTxn from gramps.gen.display.name import displayer -from gramps.gui.ddtargets import DdTargets from gramps.gen.utils.libformatting import FormattingHelper +from gramps.gen.lib import ChildRef +from gramps.gui.ddtargets import DdTargets +from gramps.gui.dialog import QuestionDialog2 from gramps.gen.const import GRAMPS_LOCALE as glocale try: @@ -13,6 +16,7 @@ except ValueError: _trans = glocale.translation _ = _trans.gettext +ngettext = glocale.translation.ngettext class DragAndDrop(): @@ -20,11 +24,14 @@ class DragAndDrop(): Add Drag-n-Drop feature to GraphView addon. """ - def __init__(self, canvas, dbstate): + def __init__(self, canvas, dbstate, uistate, h_adj, v_adj): self.drag_person = None self.drag_family = None + self.h_adj = h_adj + self.v_adj = v_adj self.dbstate = dbstate + self.uistate = uistate self.canvas = canvas self.canvas.connect("drag_data_get", self.drag_data_get) self.canvas.connect("drag_begin", self.begin) @@ -32,6 +39,24 @@ def __init__(self, canvas, dbstate): self.enable_dnd(True) + # add drop support + self.canvas.drag_dest_set( + Gtk.DestDefaults.ALL, + [], + Gdk.DragAction.COPY + ) + tglist = Gtk.TargetList.new([]) + tglist.add(DdTargets.PERSON_LINK.atom_drag_type, + DdTargets.PERSON_LINK.target_flags, + DdTargets.PERSON_LINK.app_id, + ) + # TODO: add other targets. For now only person drop supported. + self.canvas.drag_dest_set_target_list(tglist) + self.canvas.connect("drag-motion", self.drag_motion) + self.canvas.connect("drag-data-received", self.drag_data_receive) + + self.item_cache = {} + def enable_dnd(self, state): """ Enable or disable drag-n-drop for canvas widget. @@ -55,6 +80,8 @@ def begin(self, widget, context): if DdTargets.FAMILY_LINK.drag_type in tgs: Gtk.drag_set_icon_name(context, 'gramps-family', 0, 0) + self.item_cache.clear() + def stop(self, *args): """ Called when drag is end. @@ -126,3 +153,92 @@ def drag_data_get(self, widget, context, sel_data, info, time): mother = '...' sel_data.set_text( _('Family of %s and %s') % (father, mother), -1) + + def drag_motion(self, widget, context, x, y, time): + """ + Monitor drag motion. And check if we can receive the data. + Disable drop if we are not at person or family node. + """ + if self.get_item_at_pos(x, y) is None: + # disable drop + Gdk.drag_status(context, 0, time) + + def drag_data_receive(self, widget, context, x, y, data, info, time): + """ + Handle drop event. + """ + receiver = self.get_item_at_pos(x, y) + + # unpickle data and get dropped person's handles + out_data = [] + p_data = pickle.loads(data.get_data()) + if isinstance(p_data[0], bytes): + for d in p_data: + tmp = pickle.loads(d) + if tmp[0] == 'person-link': + out_data.append(tmp[2]) + elif p_data[0] == 'person-link': + out_data.append(p_data[2]) + + # if person is dropped to family node then add them to this family + if receiver[0] == 'familynode' and out_data: + title = ngettext("Add child to family?", + "Add children to family?", + len(out_data)) + quest = ngettext("Do you want to add child to the family?", + "Do you want to add children to the family?", + len(out_data)) + dialog = QuestionDialog2(title, quest, _("Yes"), _("No"), + self.uistate.window) + if dialog.run(): + for person_handle in out_data: + self.__add_child_to_family(person_handle, receiver[1]) + + def get_item_at_pos(self, x, y): + """ + Get GooCanvas item at cursor position. + Return: (node_class, person/family object) or None. + """ + scale_coef = self.canvas.get_scale() + x_pos = (x + self.h_adj.get_value()) / scale_coef + y_pos = (y + self.v_adj.get_value()) / scale_coef + + item = self.canvas.get_item_at(x_pos, y_pos, True) + obj = self.item_cache.get(item) + if obj is not None: + return obj + try: + # data stored in GooCanvasGroup which is parent of the item + parent = item.get_parent() + handle = parent.title + node_class = parent.description + + if node_class == 'node' and handle: + obj = (node_class, + self.dbstate.db.get_person_from_handle(handle)) + elif node_class == 'familynode' and handle: + obj = (node_class, + self.dbstate.db.get_family_from_handle(handle)) + else: + return None + self.item_cache[item] = obj + return obj + except: + pass + return None + + def __add_child_to_family(self, person_handle, family): + """ + Write data to db. + """ + person = self.dbstate.db.get_person_from_handle(person_handle) + ref = ChildRef() + ref.ref = person_handle + family.add_child_ref(ref) + person.add_parent_family_handle(family.get_handle()) + + with DbTxn(_("Add Child to Family"), self.dbstate.db) as trans: + # default relationship is used + self.dbstate.db.commit_person(person, trans) + # add child to family + self.dbstate.db.commit_family(family, trans) diff --git a/GraphView/graphview.py b/GraphView/graphview.py index db5da42db..90af34651 100644 --- a/GraphView/graphview.py +++ b/GraphView/graphview.py @@ -1154,7 +1154,8 @@ def __init__(self, view, dbstate, uistate): self.bold_size = self.norm_size = 0 # font sizes to send to dot # setup drag and drop - self.dnd = DragAndDrop(self.canvas, self.dbstate) + self.dnd = DragAndDrop(self.canvas, self.dbstate, self.uistate, + self.hadjustment, self.vadjustment) self.canvas.connect("drag-begin", self.del_click_events) def add_popover(self, widget, container):