Skip to content

Commit 602effa

Browse files
committed
Add new Timeline tab
1 parent 0f0b0e1 commit 602effa

File tree

3 files changed

+266
-1
lines changed

3 files changed

+266
-1
lines changed

CombinedView/combinedview.gpr.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
id = 'combinedview',
2323
name = _("Combined"),
2424
description = _("A view showing relationships and events for a person"),
25-
version = '1.0.12',
25+
version = '1.0.13',
2626
gramps_target_version = '5.1',
2727
status = STABLE,
2828
fname = 'combinedview.py',

CombinedView/combinedview.py

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
from gramps.gen.db import DbTxn
5757
from navigationview import NavigationView
5858
from taglist import TagList
59+
from timeline import Timeline
5960
from gramps.gui.uimanager import ActionGroup
6061
from gramps.gui.editors import EditPerson, EditFamily, EditEvent
6162
from gramps.gui.editors import FilterEditor
@@ -559,6 +560,7 @@ def _change_person(self, handle):
559560
self.write_families(person)
560561
self.write_events(person)
561562
self.write_album(person)
563+
self.write_timeline(person)
562564

563565
#self.stack.set_visible_child_name(self.person_tab)
564566

@@ -1010,6 +1012,130 @@ def write_event_ref(self, vbox, ename, event):
10101012
def write_data(self, box, title):
10111013
box.add(widgets.BasicLabel(title))
10121014

1015+
##############################################################################
1016+
#
1017+
# Timeline
1018+
#
1019+
##############################################################################
1020+
1021+
def write_timeline(self, person):
1022+
1023+
grid = Gtk.Grid(orientation=Gtk.Orientation.VERTICAL)
1024+
1025+
scroll = Gtk.ScrolledWindow()
1026+
scroll.add(grid)
1027+
scroll.show_all()
1028+
self.stack.add_titled(scroll, 'timeline', _('Timeline'))
1029+
1030+
events = []
1031+
start_date = None
1032+
# Personal events
1033+
for index, event_ref in enumerate(person.get_event_ref_list()):
1034+
event = self.dbstate.db.get_event_from_handle(event_ref.ref)
1035+
date = event.get_date_object()
1036+
if (start_date is None and event_ref.role.is_primary() and
1037+
(event.type.is_birth_fallback() or
1038+
event.type == EventType.BIRTH)):
1039+
start_date = date
1040+
sortval = date.get_sort_value()
1041+
events.append(((sortval, index), event_ref, None))
1042+
1043+
# Family events
1044+
for family_handle in person.get_family_handle_list():
1045+
family = self.dbstate.db.get_family_from_handle(family_handle)
1046+
father_handle = family.get_father_handle()
1047+
mother_handle = family.get_mother_handle()
1048+
spouse = None
1049+
if father_handle == person.handle:
1050+
if mother_handle:
1051+
spouse = self.dbstate.db.get_person_from_handle(mother_handle)
1052+
else:
1053+
if father_handle:
1054+
spouse = self.dbstate.db.get_person_from_handle(father_handle)
1055+
for event_ref in family.get_event_ref_list():
1056+
event = self.dbstate.db.get_event_from_handle(event_ref.ref)
1057+
sortval = event.get_date_object().get_sort_value()
1058+
events.append(((sortval, 0), event_ref, spouse))
1059+
1060+
# Write all events sorted by date
1061+
for index, event in enumerate(sorted(events, key=itemgetter(0))):
1062+
self.write_node(grid, event[1], event[2], index+1, start_date)
1063+
1064+
grid.show_all()
1065+
1066+
def write_node(self, grid, event_ref, spouse, index, start_date):
1067+
handle = event_ref.ref
1068+
event = self.dbstate.db.get_event_from_handle(handle)
1069+
etype = str(event.get_type())
1070+
desc = event.get_description()
1071+
who = get_participant_from_event(self.dbstate.db, handle)
1072+
1073+
title = etype
1074+
if desc:
1075+
title = '%s (%s)' % (title, desc)
1076+
if spouse:
1077+
spouse_name = name_displayer.display(spouse)
1078+
title = '%s - %s' % (title, spouse_name)
1079+
1080+
role = event_ref.get_role()
1081+
if role in (EventRoleType.PRIMARY, EventRoleType.FAMILY):
1082+
emph = True
1083+
else:
1084+
emph = False
1085+
title = '%s of %s' % (title, who)
1086+
1087+
vbox1 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
1088+
1089+
link_func = self._event_link
1090+
name = (title, None)
1091+
handle = event_ref.ref
1092+
link_label = widgets.LinkLabel(name, link_func, handle, emph,
1093+
theme=self.theme)
1094+
link_label.set_padding(3, 0)
1095+
link_label.set_tooltip_text(_('Click to make this event active'))
1096+
if self._config.get('preferences.releditbtn'):
1097+
button = widgets.IconButton(self.edit_event, handle)
1098+
button.set_tooltip_text(_('Edit %s') % name[0])
1099+
else:
1100+
button = None
1101+
1102+
hbox = widgets.LinkBox(link_label, button)
1103+
if self.show_tags:
1104+
tag_list = TagList(self.get_tag_list(event))
1105+
hbox.pack_start(tag_list, False, False, 0)
1106+
vbox1.pack_start(hbox, False, False, 0)
1107+
1108+
pname = place_displayer.display_event(self.dbstate.db, event)
1109+
vbox1.pack_start(widgets.BasicLabel(pname), False, False, 0)
1110+
vbox1.set_vexpand(False)
1111+
vbox1.set_valign(Gtk.Align.CENTER)
1112+
vbox1.show_all()
1113+
1114+
eventbox = self.make_dragbox(vbox1, 'Event', handle)
1115+
eventbox.set_hexpand(True)
1116+
eventbox.set_vexpand(False)
1117+
eventbox.set_valign(Gtk.Align.CENTER)
1118+
eventbox.set_margin_top(1)
1119+
eventbox.set_margin_bottom(1)
1120+
eventbox.show_all()
1121+
1122+
vbox2 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
1123+
dobj = event.get_date_object()
1124+
date = widgets.BasicLabel(displayer.display(dobj))
1125+
vbox2.pack_start(date, False, False, 0)
1126+
if start_date is not None:
1127+
age_precision = config.get('preferences.age-display-precision')
1128+
diff = (dobj - start_date).format(precision=age_precision)
1129+
age = widgets.BasicLabel(diff)
1130+
vbox2.pack_start(age, False, False, 0)
1131+
vbox2.set_valign(Gtk.Align.CENTER)
1132+
grid.add(vbox2)
1133+
1134+
tl = Timeline()
1135+
grid.attach_next_to(tl, vbox2, Gtk.PositionType.RIGHT, 1, 1)
1136+
1137+
grid.attach_next_to(eventbox, tl, Gtk.PositionType.RIGHT, 1, 1)
1138+
10131139
##############################################################################
10141140
#
10151141
# Events list

CombinedView/timeline.py

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
#
2+
# Gramps - a GTK+/GNOME based genealogy program
3+
#
4+
# Copyright (C) 2020 Nick Hall
5+
#
6+
# This program is free software; you can redistribute it and/or modify
7+
# it under the terms of the GNU General Public License as published by
8+
# the Free Software Foundation; either version 2 of the License, or
9+
# (at your option) any later version.
10+
#
11+
# This program is distributed in the hope that it will be useful,
12+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
# GNU General Public License for more details.
15+
#
16+
# You should have received a copy of the GNU General Public License
17+
# along with this program; if not, write to the Free Software
18+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19+
#
20+
21+
"""
22+
Timeline widget
23+
24+
This widget displays a segment of a timeline.
25+
"""
26+
27+
#-------------------------------------------------------------------------
28+
#
29+
# Python modules
30+
#
31+
#-------------------------------------------------------------------------
32+
import math
33+
34+
#-------------------------------------------------------------------------
35+
#
36+
# Gtk modules
37+
#
38+
#-------------------------------------------------------------------------
39+
from gi.repository import Gtk
40+
from gi.repository import Gdk
41+
from gi.repository import GObject
42+
from gi.repository import PangoCairo
43+
44+
NODE_SIZE = 10
45+
46+
47+
#-------------------------------------------------------------------------
48+
#
49+
# SegmentMap class
50+
#
51+
#-------------------------------------------------------------------------
52+
53+
class Timeline(Gtk.DrawingArea):
54+
"""
55+
A segment of a timeline.
56+
"""
57+
58+
__gsignals__ = {'clicked': (GObject.SignalFlags.RUN_FIRST, None, ())}
59+
60+
def __init__(self, position='middle'):
61+
Gtk.DrawingArea.__init__(self)
62+
63+
self.add_events(Gdk.EventMask.POINTER_MOTION_MASK |
64+
Gdk.EventMask.BUTTON_PRESS_MASK |
65+
Gdk.EventMask.BUTTON_RELEASE_MASK)
66+
self.connect('motion-notify-event', self.on_pointer_motion)
67+
self.connect('button-press-event', self.on_button_press)
68+
69+
self.position = position
70+
self.__active = False
71+
72+
def do_draw(self, cr):
73+
"""
74+
A custom draw method for this widget.
75+
@param cr: A cairo context.
76+
@type cr: cairo.Context
77+
"""
78+
allocation = self.get_allocation()
79+
context = self.get_style_context()
80+
fg_color = context.get_color(context.get_state())
81+
cr.set_source_rgba(*fg_color)
82+
cr.set_line_width(5)
83+
84+
if self.position != 'start':
85+
cr.move_to(allocation.width / 2, 0)
86+
cr.line_to(allocation.width / 2, allocation.height / 2)
87+
cr.stroke()
88+
89+
if self.position != 'end':
90+
cr.move_to(allocation.width / 2, allocation.height / 2)
91+
cr.line_to(allocation.width / 2, allocation.height)
92+
cr.stroke()
93+
94+
cr.translate(allocation.width / 2, allocation.height / 2)
95+
cr.arc(0, 0, NODE_SIZE, 0, 2 * math.pi)
96+
cr.stroke_preserve()
97+
if self.__active:
98+
cr.set_source_rgba(0.7, 0.7, 1, 1)
99+
else:
100+
cr.set_source_rgba(0.5, 0.5, 1, 1)
101+
cr.fill()
102+
103+
self.set_size_request(100, 50)
104+
105+
def on_pointer_motion(self, _dummy, event):
106+
"""
107+
Called when the pointer is moved.
108+
@param _dummy: This widget. Unused.
109+
@type _dummy: Gtk.Widget
110+
@param event: An event.
111+
@type event: Gdk.Event
112+
"""
113+
allocation = self.get_allocation()
114+
x = allocation.width / 2
115+
y = allocation.height / 2
116+
if (event.x > (x - NODE_SIZE) and event.x < (x + NODE_SIZE) and
117+
event.y > (y - NODE_SIZE) and event.y < (y + NODE_SIZE)):
118+
active = True
119+
else:
120+
active = False
121+
122+
if self.__active != active:
123+
self.__active = active
124+
self.queue_draw()
125+
126+
return False
127+
128+
def on_button_press(self, _dummy, event):
129+
"""
130+
Called when a mouse button is clicked.
131+
@param _dummy: This widget. Unused.
132+
@type _dummy: Gtk.Widget
133+
@param event: An event.
134+
@type event: Gdk.Event
135+
"""
136+
if (event.button == 1 and
137+
event.type == Gdk.EventType.DOUBLE_BUTTON_PRESS and
138+
self.__active):
139+
self.emit('clicked')

0 commit comments

Comments
 (0)