44
44
from html import escape
45
45
import gi
46
46
from gi .repository import Gtk , Gdk , GdkPixbuf , GLib , Pango
47
+ import pickle
47
48
48
49
#-------------------------------------------------------------------------
49
50
#
67
68
from gramps .gen .utils .libformatting import FormattingHelper
68
69
from gramps .gen .utils .thumbnails import get_thumbnail_path
69
70
71
+ from gramps .gui .ddtargets import DdTargets
70
72
from gramps .gui .dialog import OptionDialog , ErrorDialog , QuestionDialog2
71
73
from gramps .gui .display import display_url
72
74
from gramps .gui .editors import EditPerson , EditFamily , EditTagList
@@ -799,11 +801,13 @@ def __init__(self, view, dbstate, uistate):
799
801
self ._last_x = 0
800
802
self ._last_y = 0
801
803
self ._in_move = False
804
+ self ._in_drag = False
802
805
self .view = view
803
806
self .dbstate = dbstate
804
807
self .uistate = uistate
805
808
self .parser = None
806
809
self .active_person_handle = None
810
+ self .drag_person = None
807
811
808
812
self .actions = Actions (dbstate , uistate , self .view .bookmarks )
809
813
self .actions .connect ('rebuild-graph' , self .view .build_tree )
@@ -960,6 +964,22 @@ def __init__(self, view, dbstate, uistate):
960
964
self .retest_font = True # flag indicates need to resize font
961
965
self .bold_size = self .norm_size = 0 # font sizes to send to dot
962
966
967
+ # setup drag and drop
968
+ drag_widget = self .get_widget ()
969
+ drag_widget .drag_source_set (Gdk .ModifierType .BUTTON1_MASK , [],
970
+ Gdk .DragAction .COPY )
971
+ drag_widget .connect ("drag_data_get" , self .cb_drag_data_get )
972
+ drag_widget .connect ("drag_begin" , self .cb_drag_begin )
973
+ drag_widget .connect ("drag_end" , self .cb_drag_end )
974
+
975
+ tglist = Gtk .TargetList .new ([])
976
+ tglist .add (DdTargets .PERSON_LINK .atom_drag_type ,
977
+ DdTargets .PERSON_LINK .target_flags ,
978
+ DdTargets .PERSON_LINK .app_id )
979
+ # allow drag to a text document, info on drag_get will be 0L !
980
+ tglist .add_text_targets (0 )
981
+ drag_widget .drag_source_set_target_list (tglist )
982
+
963
983
def add_popover (self , widget , container ):
964
984
"""
965
985
Add popover for button.
@@ -1268,6 +1288,7 @@ def populate(self, active_person):
1268
1288
# set the busy cursor, so the user knows that we are working
1269
1289
self .uistate .set_busy_cursor (True )
1270
1290
1291
+ self ._in_drag = False
1271
1292
self .clear ()
1272
1293
self .active_person_handle = active_person
1273
1294
@@ -1407,6 +1428,7 @@ def button_release(self, item, target, event):
1407
1428
"""
1408
1429
Exit from scroll mode when button release.
1409
1430
"""
1431
+ self ._in_drag = False
1410
1432
button = event .get_button ()[1 ]
1411
1433
if ((button == 1 or button == 2 ) and
1412
1434
event .type == getattr (Gdk .EventType , "BUTTON_RELEASE" )):
@@ -1435,6 +1457,35 @@ def motion_notify_event(self, _item, _target, event):
1435
1457
(event .y_root - self ._last_y ) * scale_coef )
1436
1458
self .vadjustment .set_value (new_y )
1437
1459
return True
1460
+
1461
+ if self ._in_drag and (event .type == Gdk .EventType .MOTION_NOTIFY ):
1462
+ # start drag when cursor moved more then 5
1463
+ # to separate it from simple click
1464
+ if ((abs (self ._last_x - event .x ) > 5 )
1465
+ or (abs (self ._last_x - event .x ) > 5 )):
1466
+ self .uistate .set_busy_cursor (False )
1467
+ # Remove all single click events
1468
+ for click_item in self .click_events :
1469
+ if not click_item .is_destroyed ():
1470
+ GLib .source_remove (click_item .get_id ())
1471
+ self .click_events .clear ()
1472
+
1473
+ # translate to drag_widget coords
1474
+ drag_widget = self .get_widget ()
1475
+ scale_coef = self .canvas .get_scale ()
1476
+ bounds = self .canvas .get_root_item ().get_bounds ()
1477
+ height_canvas = bounds .y2 - bounds .y1
1478
+ x = self ._last_x * scale_coef - self .hadjustment .get_value ()
1479
+ y = ((height_canvas + self ._last_y ) * scale_coef -
1480
+ self .vadjustment .get_value ())
1481
+
1482
+ drag_widget .drag_begin_with_coordinates (
1483
+ drag_widget .drag_source_get_target_list (),
1484
+ Gdk .DragAction .COPY ,
1485
+ Gdk .ModifierType .BUTTON1_MASK ,
1486
+ event ,
1487
+ x , y )
1488
+ return True
1438
1489
return False
1439
1490
1440
1491
def set_zoom (self , value ):
@@ -1478,6 +1529,7 @@ def select_node(self, item, target, event):
1478
1529
1479
1530
if button == 1 and node_class == 'node' : # left mouse
1480
1531
self .uistate .set_busy_cursor (True )
1532
+ self .drag_person = self .dbstate .db .get_person_from_handle (handle )
1481
1533
if handle == self .active_person_handle :
1482
1534
# Find a parent of the active person so that they can become
1483
1535
# the active person, if no parents then leave as the current
@@ -1488,7 +1540,6 @@ def select_node(self, item, target, event):
1488
1540
else :
1489
1541
# unset busy cursor as we don't change active person
1490
1542
self .uistate .set_busy_cursor (False )
1491
- return True
1492
1543
1493
1544
# redraw the graph based on the selected person
1494
1545
# schedule after because double click can occur
@@ -1498,6 +1549,11 @@ def select_node(self, item, target, event):
1498
1549
context = GLib .main_context_default ()
1499
1550
self .click_events .append (context .find_source_by_id (click_event_id ))
1500
1551
1552
+ # go to drag mode, applyed on motion event
1553
+ self ._in_drag = True
1554
+ self ._last_x = event .x
1555
+ self ._last_y = event .y
1556
+
1501
1557
elif button == 3 and node_class : # right mouse
1502
1558
if node_class == 'node' :
1503
1559
self .menu = PopupMenu (self , 'person' , handle )
@@ -1513,6 +1569,30 @@ def select_node(self, item, target, event):
1513
1569
1514
1570
return True
1515
1571
1572
+ def cb_drag_begin (self , widget , data ):
1573
+ """Set up some inital conditions for drag. Set up icon."""
1574
+ self ._in_drag = True
1575
+ widget .drag_source_set_icon_name ('gramps-person' )
1576
+
1577
+ def cb_drag_end (self , widget , data ):
1578
+ """Set up some inital conditions for drag. Set up icon."""
1579
+ self ._in_drag = False
1580
+
1581
+ def cb_drag_data_get (self , widget , context , sel_data , info , time ):
1582
+ """
1583
+ Returned parameters after drag.
1584
+ Specified for 'person-link', for others return text info about person.
1585
+ """
1586
+ tgs = [x .name () for x in context .list_targets ()]
1587
+ if info == DdTargets .PERSON_LINK .app_id :
1588
+ data = (DdTargets .PERSON_LINK .drag_type ,
1589
+ id (self ), self .drag_person .handle , 0 )
1590
+ sel_data .set (sel_data .get_target (), 8 , pickle .dumps (data ))
1591
+ elif ('TEXT' in tgs or 'text/plain' in tgs ) and info == 0 :
1592
+ format_helper = FormattingHelper (self .dbstate )
1593
+ sel_data .set_text (
1594
+ format_helper .format_person (self .drag_person , 11 ),- 1 )
1595
+
1516
1596
def find_a_parent (self , handle ):
1517
1597
"""
1518
1598
Locate a parent from the first family that the selected person is a
0 commit comments