4343from html import escape
4444import gi
4545from gi .repository import Gtk , Gdk , GdkPixbuf , GLib
46+ import pickle
4647
4748#-------------------------------------------------------------------------
4849#
6667from gramps .gen .utils .libformatting import FormattingHelper
6768from gramps .gen .utils .thumbnails import get_thumbnail_path
6869
70+ from gramps .gui .ddtargets import DdTargets
6971from gramps .gui .dialog import OptionDialog , ErrorDialog , QuestionDialog2
7072from gramps .gui .display import display_url
7173from gramps .gui .editors import EditPerson , EditFamily , EditTagList
@@ -715,11 +717,13 @@ def __init__(self, view, dbstate, uistate):
715717 self ._last_x = 0
716718 self ._last_y = 0
717719 self ._in_move = False
720+ self ._in_drag = False
718721 self .view = view
719722 self .dbstate = dbstate
720723 self .uistate = uistate
721724 self .parser = None
722725 self .active_person_handle = None
726+ self .drag_person = None
723727
724728 self .actions = Actions (dbstate , uistate , self .view .bookmarks )
725729 self .actions .connect ('rebuild-graph' , self .view .build_tree )
@@ -874,6 +878,22 @@ def __init__(self, view, dbstate, uistate):
874878 # used for popup menu, prevent destroy menu as local variable
875879 self .menu = None
876880
881+ # setup drag and drop
882+ drag_widget = self .get_widget ()
883+ drag_widget .drag_source_set (Gdk .ModifierType .BUTTON1_MASK , [],
884+ Gdk .DragAction .COPY )
885+ drag_widget .connect ("drag_data_get" , self .cb_drag_data_get )
886+ drag_widget .connect ("drag_begin" , self .cb_drag_begin )
887+ drag_widget .connect ("drag_end" , self .cb_drag_end )
888+
889+ tglist = Gtk .TargetList .new ([])
890+ tglist .add (DdTargets .PERSON_LINK .atom_drag_type ,
891+ DdTargets .PERSON_LINK .target_flags ,
892+ DdTargets .PERSON_LINK .app_id )
893+ # allow drag to a text document, info on drag_get will be 0L !
894+ tglist .add_text_targets (0 )
895+ drag_widget .drag_source_set_target_list (tglist )
896+
877897 def add_popover (self , widget , container ):
878898 """
879899 Add popover for button.
@@ -1182,6 +1202,7 @@ def populate(self, active_person):
11821202 # set the busy cursor, so the user knows that we are working
11831203 self .uistate .set_busy_cursor (True )
11841204
1205+ self ._in_drag = False
11851206 self .clear ()
11861207 self .active_person_handle = active_person
11871208
@@ -1316,6 +1337,7 @@ def button_release(self, item, target, event):
13161337 """
13171338 Exit from scroll mode when button release.
13181339 """
1340+ self ._in_drag = False
13191341 button = event .get_button ()[1 ]
13201342 if ((button == 1 or button == 2 ) and
13211343 event .type == getattr (Gdk .EventType , "BUTTON_RELEASE" )):
@@ -1344,6 +1366,35 @@ def motion_notify_event(self, item, target, event):
13441366 (event .y_root - self ._last_y ) * scale_coef )
13451367 self .vadjustment .set_value (new_y )
13461368 return True
1369+
1370+ if self ._in_drag and (event .type == Gdk .EventType .MOTION_NOTIFY ):
1371+ # start drag when cursor moved more then 5
1372+ # to separate it from simple click
1373+ if ((abs (self ._last_x - event .x ) > 5 )
1374+ or (abs (self ._last_x - event .x ) > 5 )):
1375+ self .uistate .set_busy_cursor (False )
1376+ # Remove all single click events
1377+ for click_item in self .click_events :
1378+ if not click_item .is_destroyed ():
1379+ GLib .source_remove (click_item .get_id ())
1380+ self .click_events .clear ()
1381+
1382+ # translate to drag_widget coords
1383+ drag_widget = self .get_widget ()
1384+ scale_coef = self .canvas .get_scale ()
1385+ bounds = self .canvas .get_root_item ().get_bounds ()
1386+ height_canvas = bounds .y2 - bounds .y1
1387+ x = self ._last_x * scale_coef - self .hadjustment .get_value ()
1388+ y = ((height_canvas + self ._last_y ) * scale_coef -
1389+ self .vadjustment .get_value ())
1390+
1391+ drag_widget .drag_begin_with_coordinates (
1392+ drag_widget .drag_source_get_target_list (),
1393+ Gdk .DragAction .COPY ,
1394+ Gdk .ModifierType .BUTTON1_MASK ,
1395+ event ,
1396+ x , y )
1397+ return True
13471398 return False
13481399
13491400 def set_zoom (self , value ):
@@ -1387,6 +1438,7 @@ def select_node(self, item, target, event):
13871438
13881439 if button == 1 and node_class == 'node' : # left mouse
13891440 self .uistate .set_busy_cursor (True )
1441+ self .drag_person = self .dbstate .db .get_person_from_handle (handle )
13901442 if handle == self .active_person_handle :
13911443 # Find a parent of the active person so that they can become
13921444 # the active person, if no parents then leave as the current
@@ -1397,7 +1449,6 @@ def select_node(self, item, target, event):
13971449 else :
13981450 # unset busy cursor as we don't change active person
13991451 self .uistate .set_busy_cursor (False )
1400- return True
14011452
14021453 # redraw the graph based on the selected person
14031454 # schedule after because double click can occur
@@ -1407,6 +1458,11 @@ def select_node(self, item, target, event):
14071458 context = GLib .main_context_default ()
14081459 self .click_events .append (context .find_source_by_id (click_event_id ))
14091460
1461+ # go to drag mode, applyed on motion event
1462+ self ._in_drag = True
1463+ self ._last_x = event .x
1464+ self ._last_y = event .y
1465+
14101466 elif button == 3 and node_class : # right mouse
14111467 if node_class == 'node' :
14121468 self .menu = PopupMenu (self , 'person' , handle )
@@ -1422,6 +1478,30 @@ def select_node(self, item, target, event):
14221478
14231479 return True
14241480
1481+ def cb_drag_begin (self , widget , data ):
1482+ """Set up some inital conditions for drag. Set up icon."""
1483+ self ._in_drag = True
1484+ widget .drag_source_set_icon_name ('gramps-person' )
1485+
1486+ def cb_drag_end (self , widget , data ):
1487+ """Set up some inital conditions for drag. Set up icon."""
1488+ self ._in_drag = False
1489+
1490+ def cb_drag_data_get (self , widget , context , sel_data , info , time ):
1491+ """
1492+ Returned parameters after drag.
1493+ Specified for 'person-link', for others return text info about person.
1494+ """
1495+ tgs = [x .name () for x in context .list_targets ()]
1496+ if info == DdTargets .PERSON_LINK .app_id :
1497+ data = (DdTargets .PERSON_LINK .drag_type ,
1498+ id (self ), self .drag_person .handle , 0 )
1499+ sel_data .set (sel_data .get_target (), 8 , pickle .dumps (data ))
1500+ elif ('TEXT' in tgs or 'text/plain' in tgs ) and info == 0 :
1501+ format_helper = FormattingHelper (self .dbstate )
1502+ sel_data .set_text (
1503+ format_helper .format_person (self .drag_person , 11 ),- 1 )
1504+
14251505 def find_a_parent (self , handle ):
14261506 """
14271507 Locate a parent from the first family that the selected person is a
0 commit comments