43
43
from html import escape
44
44
import gi
45
45
from gi .repository import Gtk , Gdk , GdkPixbuf , GLib
46
+ import pickle
46
47
47
48
#-------------------------------------------------------------------------
48
49
#
66
67
from gramps .gen .utils .libformatting import FormattingHelper
67
68
from gramps .gen .utils .thumbnails import get_thumbnail_path
68
69
70
+ from gramps .gui .ddtargets import DdTargets
69
71
from gramps .gui .dialog import OptionDialog , ErrorDialog , QuestionDialog2
70
72
from gramps .gui .display import display_url
71
73
from gramps .gui .editors import EditPerson , EditFamily , EditTagList
@@ -715,11 +717,13 @@ def __init__(self, view, dbstate, uistate):
715
717
self ._last_x = 0
716
718
self ._last_y = 0
717
719
self ._in_move = False
720
+ self ._in_drag = False
718
721
self .view = view
719
722
self .dbstate = dbstate
720
723
self .uistate = uistate
721
724
self .parser = None
722
725
self .active_person_handle = None
726
+ self .drag_person = None
723
727
724
728
self .actions = Actions (dbstate , uistate , self .view .bookmarks )
725
729
self .actions .connect ('rebuild-graph' , self .view .build_tree )
@@ -874,6 +878,22 @@ def __init__(self, view, dbstate, uistate):
874
878
# used for popup menu, prevent destroy menu as local variable
875
879
self .menu = None
876
880
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
+
877
897
def add_popover (self , widget , container ):
878
898
"""
879
899
Add popover for button.
@@ -1182,6 +1202,7 @@ def populate(self, active_person):
1182
1202
# set the busy cursor, so the user knows that we are working
1183
1203
self .uistate .set_busy_cursor (True )
1184
1204
1205
+ self ._in_drag = False
1185
1206
self .clear ()
1186
1207
self .active_person_handle = active_person
1187
1208
@@ -1316,6 +1337,7 @@ def button_release(self, item, target, event):
1316
1337
"""
1317
1338
Exit from scroll mode when button release.
1318
1339
"""
1340
+ self ._in_drag = False
1319
1341
button = event .get_button ()[1 ]
1320
1342
if ((button == 1 or button == 2 ) and
1321
1343
event .type == getattr (Gdk .EventType , "BUTTON_RELEASE" )):
@@ -1344,6 +1366,35 @@ def motion_notify_event(self, item, target, event):
1344
1366
(event .y_root - self ._last_y ) * scale_coef )
1345
1367
self .vadjustment .set_value (new_y )
1346
1368
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
1347
1398
return False
1348
1399
1349
1400
def set_zoom (self , value ):
@@ -1387,6 +1438,7 @@ def select_node(self, item, target, event):
1387
1438
1388
1439
if button == 1 and node_class == 'node' : # left mouse
1389
1440
self .uistate .set_busy_cursor (True )
1441
+ self .drag_person = self .dbstate .db .get_person_from_handle (handle )
1390
1442
if handle == self .active_person_handle :
1391
1443
# Find a parent of the active person so that they can become
1392
1444
# the active person, if no parents then leave as the current
@@ -1397,7 +1449,6 @@ def select_node(self, item, target, event):
1397
1449
else :
1398
1450
# unset busy cursor as we don't change active person
1399
1451
self .uistate .set_busy_cursor (False )
1400
- return True
1401
1452
1402
1453
# redraw the graph based on the selected person
1403
1454
# schedule after because double click can occur
@@ -1407,6 +1458,11 @@ def select_node(self, item, target, event):
1407
1458
context = GLib .main_context_default ()
1408
1459
self .click_events .append (context .find_source_by_id (click_event_id ))
1409
1460
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
+
1410
1466
elif button == 3 and node_class : # right mouse
1411
1467
if node_class == 'node' :
1412
1468
self .menu = PopupMenu (self , 'person' , handle )
@@ -1422,6 +1478,30 @@ def select_node(self, item, target, event):
1422
1478
1423
1479
return True
1424
1480
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
+
1425
1505
def find_a_parent (self , handle ):
1426
1506
"""
1427
1507
Locate a parent from the first family that the selected person is a
0 commit comments