diff --git a/po/POTFILES.in b/po/POTFILES.in
index 6fc879582..be90837ee 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -489,6 +489,7 @@ src/gui/views/treemodels/treebasemodel.py
src/gui/widgets/buttons.py
src/gui/widgets/expandcollapsearrow.py
src/gui/widgets/fanchart.py
+src/gui/widgets/fanchartdesc.py
src/gui/widgets/grampletpane.py
src/gui/widgets/labels.py
src/gui/widgets/monitoredwidgets.py
@@ -693,6 +694,7 @@ src/plugins/view/citationlistview.py
src/plugins/view/eventview.py
src/plugins/view/familyview.py
src/plugins/view/fanchartview.py
+src/plugins/view/fanchartdescview.py
src/plugins/view/geography.gpr.py
src/plugins/view/geoclose.py
src/plugins/view/geoevents.py
diff --git a/src/gui/widgets/Makefile.am b/src/gui/widgets/Makefile.am
index ca03d4b07..f5e93256a 100644
--- a/src/gui/widgets/Makefile.am
+++ b/src/gui/widgets/Makefile.am
@@ -12,6 +12,7 @@ pkgpython_PYTHON = \
buttons.py \
expandcollapsearrow.py \
fanchart.py \
+ fanchartdesc.py \
grampletpane.py \
labels.py \
linkbox.py \
diff --git a/src/gui/widgets/fanchart.py b/src/gui/widgets/fanchart.py
index 9499e99cc..3450882e9 100644
--- a/src/gui/widgets/fanchart.py
+++ b/src/gui/widgets/fanchart.py
@@ -86,7 +86,6 @@ def gender_code(is_male):
PIXELS_PER_GENERATION = 50 # size of radius for generation
BORDER_EDGE_WIDTH = 10 # empty white box size at edge to indicate parents
-CENTER = 50 # pixel radius of center
CHILDRING_WIDTH = 12 # width of the children ring inside the person
TRANSLATE_PX = 10 # size of the central circle, used to move the chart
PAD_PX = 4 # padding with edges
@@ -142,9 +141,11 @@ TYPE_BOX_FAMILY = 1
class FanChartBaseWidget(Gtk.DrawingArea):
""" a base widget for fancharts"""
+ CENTER = 50 # pixel radius of center, changes per fanchart
def __init__(self, dbstate, callback_popup=None):
GObject.GObject.__init__(self)
+ self.radialtext = True
st_cont = self.get_style_context()
col = st_cont.lookup_color('text_color')
if col[0]:
@@ -227,9 +228,9 @@ class FanChartBaseWidget(Gtk.DrawingArea):
requisition.height = requisition.width
elif self.form == FORM_HALFCIRCLE:
requisition.width = 2 * self.halfdist()
- requisition.height = requisition.width / 2 + CENTER + PAD_PX
+ requisition.height = requisition.width / 2 + self.CENTER + PAD_PX
elif self.form == FORM_QUADRANT:
- requisition.width = self.halfdist() + CENTER + PAD_PX
+ requisition.width = self.halfdist() + self.CENTER + PAD_PX
requisition.height = requisition.width
def do_get_preferred_width(self):
@@ -293,7 +294,7 @@ class FanChartBaseWidget(Gtk.DrawingArea):
userdata.append(period)
def set_userdata_age(self, person, userdata):
- agecol = (255, 255, 255) # white
+ agecol = (1, 1, 1) # white
if person:
age = get_age(self.dbstate.db, person)
if age is not None:
@@ -472,6 +473,31 @@ class FanChartBaseWidget(Gtk.DrawingArea):
return True
return False
+ def draw_radbox(self, cr, radiusin, radiusout, start_rad, stop_rad, color,
+ thick=False):
+ cr.move_to(radiusout * math.cos(start_rad), radiusout * math.sin(start_rad))
+ cr.arc(0, 0, radiusout, start_rad, stop_rad)
+ cr.line_to(radiusin * math.cos(stop_rad), radiusin * math.sin(stop_rad))
+ cr.arc_negative(0, 0, radiusin, stop_rad, start_rad)
+ cr.close_path()
+ ##path = cr.copy_path() # not working correct
+ cr.set_source_rgba(color[0], color[1], color[2], color[3])
+ cr.fill()
+ #and again for the border
+ cr.move_to(radiusout * math.cos(start_rad), radiusout * math.sin(start_rad))
+ cr.arc(0, 0, radiusout, start_rad, stop_rad)
+ cr.line_to(radiusin * math.cos(stop_rad), radiusin * math.sin(stop_rad))
+ cr.arc_negative(0, 0, radiusin, stop_rad, start_rad)
+ cr.close_path()
+ ##cr.append_path(path) # not working correct
+ cr.set_source_rgb(0, 0, 0) # black
+ if thick:
+ cr.set_line_width(3)
+ else:
+ cr.set_line_width(1)
+ cr.stroke()
+ cr.set_line_width(1)
+
def draw_innerring(self, cr, person, userdata, start, inc):
"""
Procedure to draw a person in the inner ring position
@@ -682,10 +708,10 @@ class FanChartBaseWidget(Gtk.DrawingArea):
elif (self.angle[-2] and
radius < TRANSLATE_PX + CHILDRING_WIDTH):
generation = -2 # indication of one of the children
- elif radius < CENTER:
+ elif radius < self.CENTER:
generation = 0
else:
- generation = int((radius - CENTER)/self.gen_pixels()) + 1
+ generation = int((radius - self.CENTER)/self.gen_pixels()) + 1
btype = self.boxtype(radius)
rads = math.atan2( (cury - cy), (curx - cx) )
@@ -730,6 +756,18 @@ class FanChartBaseWidget(Gtk.DrawingArea):
"""
raise NotImplementedError
+ def _have_children(self, person):
+ """
+ Returns True if a person has children.
+ TODO: is there no util function for this
+ """
+ if person:
+ for family_handle in person.get_family_handle_list():
+ family = self.dbstate.db.get_family_from_handle(family_handle)
+ if family and len(family.get_child_ref_list()) > 0:
+ return True
+ return False
+
def on_mouse_down(self, widget, event):
self.translating = False # keep track of up/down/left/right movement
generation, selected, btype = self.person_under_cursor(event.x, event.y)
@@ -785,9 +823,9 @@ class FanChartBaseWidget(Gtk.DrawingArea):
if self.form == FORM_CIRCLE:
self.center_xy = w/2 - event.x, h/2 - event.y
elif self.form == FORM_HALFCIRCLE:
- self.center_xy = w/2 - event.x, h - CENTER - PAD_PX - event.y
+ self.center_xy = w/2 - event.x, h - self.CENTER - PAD_PX - event.y
elif self.form == FORM_QUADRANT:
- self.center_xy = CENTER + PAD_PX - event.x, h - CENTER - PAD_PX - event.y
+ self.center_xy = self.CENTER + PAD_PX - event.x, h - self.CENTER - PAD_PX - event.y
else:
cx = w/2 - self.center_xy[0]
cy = h/2 - self.center_xy[1]
@@ -826,9 +864,9 @@ class FanChartBaseWidget(Gtk.DrawingArea):
self.center_xy = w/2 - event.x, h/2 - event.y
self.center_xy = w/2 - event.x, h/2 - event.y
elif self.form == FORM_HALFCIRCLE:
- self.center_xy = w/2 - event.x, h - CENTER - PAD_PX - event.y
+ self.center_xy = w/2 - event.x, h - self.CENTER - PAD_PX - event.y
elif self.form == FORM_QUADRANT:
- self.center_xy = CENTER + PAD_PX - event.x, h - CENTER - PAD_PX - event.y
+ self.center_xy = self.CENTER + PAD_PX - event.x, h - self.CENTER - PAD_PX - event.y
self.last_x, self.last_y = None, None
self.queue_draw()
@@ -1012,18 +1050,6 @@ class FanChartWidget(FanChartBaseWidget):
f = self._get_parent(person, True)
return not m is f is None
return False
-
- def _have_children(self, person):
- """
- Returns True if a person has children.
- TODO: is there no util function for this
- """
- if person:
- for family_handle in person.get_family_handle_list():
- family = self.dbstate.db.get_family_from_handle(family_handle)
- if family and len(family.get_child_ref_list()) > 0:
- return True
- return False
def _get_parent(self, person, father):
"""
@@ -1069,7 +1095,7 @@ class FanChartWidget(FanChartBaseWidget):
Compute the half radius of the circle
"""
nrgen = self.nrgen()
- return PIXELS_PER_GENERATION * nrgen + CENTER + BORDER_EDGE_WIDTH
+ return PIXELS_PER_GENERATION * nrgen + self.CENTER + BORDER_EDGE_WIDTH
def people_generator(self):
"""
@@ -1101,9 +1127,9 @@ class FanChartWidget(FanChartBaseWidget):
if self.form == FORM_CIRCLE:
self.set_size_request(2 * halfdist, 2 * halfdist)
elif self.form == FORM_HALFCIRCLE:
- self.set_size_request(2 * halfdist, halfdist + CENTER + PAD_PX)
+ self.set_size_request(2 * halfdist, halfdist + self.CENTER + PAD_PX)
elif self.form == FORM_QUADRANT:
- self.set_size_request(halfdist + CENTER + PAD_PX, halfdist + CENTER + PAD_PX)
+ self.set_size_request(halfdist + self.CENTER + PAD_PX, halfdist + self.CENTER + PAD_PX)
#obtain the allocation
alloc = self.get_allocation()
@@ -1117,10 +1143,10 @@ class FanChartWidget(FanChartBaseWidget):
self.center_y = h/2 - self.center_xy[1]
elif self.form == FORM_HALFCIRCLE:
self.center_x = w/2. - self.center_xy[0]
- self.center_y = h - CENTER - PAD_PX- self.center_xy[1]
+ self.center_y = h - self.CENTER - PAD_PX- self.center_xy[1]
elif self.form == FORM_QUADRANT:
- self.center_x = CENTER + PAD_PX - self.center_xy[0]
- self.center_y = h - CENTER - PAD_PX - self.center_xy[1]
+ self.center_x = self.CENTER + PAD_PX - self.center_xy[0]
+ self.center_y = h - self.CENTER - PAD_PX - self.center_xy[1]
cr.translate(self.center_x, self.center_y)
cr.save()
@@ -1137,17 +1163,17 @@ class FanChartWidget(FanChartBaseWidget):
person, userdata)
cr.set_source_rgb(1, 1, 1) # white
cr.move_to(0,0)
- cr.arc(0, 0, CENTER, 0, 2 * math.pi)
+ cr.arc(0, 0, self.CENTER, 0, 2 * math.pi)
cr.fill()
cr.set_source_rgb(0, 0, 0) # black
- cr.arc(0, 0, CENTER, 0, 2 * math.pi)
+ cr.arc(0, 0, self.CENTER, 0, 2 * math.pi)
cr.stroke()
cr.restore()
# Draw center person:
(text, person, parents, child, userdata) = self.data[0][0]
if person:
r, g, b, a = self.background_box(person, 0, userdata)
- cr.arc(0, 0, CENTER, 0, 2 * math.pi)
+ cr.arc(0, 0, self.CENTER, 0, 2 * math.pi)
if self.childring and child:
cr.arc_negative(0, 0, TRANSLATE_PX + CHILDRING_WIDTH, 2 * math.pi, 0)
cr.close_path()
@@ -1155,8 +1181,8 @@ class FanChartWidget(FanChartBaseWidget):
cr.fill()
cr.save()
name = name_displayer.display(person)
- self.draw_text(cr, name, CENTER -
- (CENTER - (CHILDRING_WIDTH + TRANSLATE_PX))/2, 95, 455,
+ self.draw_text(cr, name, self.CENTER -
+ (self.CENTER - (CHILDRING_WIDTH + TRANSLATE_PX))/2, 95, 455,
10, False,
self.fontcolor(r, g, b, a), self.fontbold(a))
cr.restore()
@@ -1184,7 +1210,7 @@ class FanChartWidget(FanChartBaseWidget):
start_rad = start * math.pi/180
stop_rad = stop * math.pi/180
r, g, b, a = self.background_box(person, generation, userdata)
- radius = generation * PIXELS_PER_GENERATION + CENTER
+ radius = generation * PIXELS_PER_GENERATION + self.CENTER
# If max generation, and they have parents:
if generation == self.generations - 1 and parents:
# draw an indicator
diff --git a/src/gui/widgets/fanchartdesc.py b/src/gui/widgets/fanchartdesc.py
new file mode 100644
index 000000000..fea0c1d1e
--- /dev/null
+++ b/src/gui/widgets/fanchartdesc.py
@@ -0,0 +1,704 @@
+#
+# Gramps - a GTK+/GNOME based genealogy program
+#
+# Copyright (C) 2001-2007 Donald N. Allingham, Martin Hawlisch
+# Copyright (C) 2009 Douglas S. Blank
+# Copyright (C) 2012 Benny Malengier
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+
+# $Id$
+
+## Based on the paper:
+## http://www.cs.utah.edu/~draperg/research/fanchart/draperg_FHT08.pdf
+## and the applet:
+## http://www.cs.utah.edu/~draperg/research/fanchart/demo/
+
+## Found by redwood:
+## http://www.gramps-project.org/bugs/view.php?id=2611
+
+from __future__ import division
+
+#-------------------------------------------------------------------------
+#
+# Python modules
+#
+#-------------------------------------------------------------------------
+from gi.repository import Pango
+from gi.repository import GObject
+from gi.repository import Gdk
+from gi.repository import Gtk
+from gi.repository import PangoCairo
+import cairo
+import math
+import colorsys
+import cPickle as pickle
+from cgi import escape
+
+#-------------------------------------------------------------------------
+#
+# GRAMPS modules
+#
+#-------------------------------------------------------------------------
+from gen.display.name import displayer as name_displayer
+from gen.errors import WindowActiveError
+from gui.editors import EditPerson, EditFamily
+import gen.lib
+import gui.utils
+from gui.ddtargets import DdTargets
+from gen.utils.alive import probably_alive
+from gen.utils.libformatting import FormattingHelper
+from gen.utils.db import (find_children, find_parents, find_witnessed_people,
+ get_age, get_timeperiod)
+from gen.plug.report.utils import find_spouse
+from gui.widgets.fanchart import *
+
+#-------------------------------------------------------------------------
+#
+# Constants
+#
+#-------------------------------------------------------------------------
+pi = math.pi
+
+PIXELS_PER_GENPERSON = 30 # size of radius for generation of children
+PIXELS_PER_GENFAMILY = 20 # size of radius for family
+PIXELS_PER_RECLAIM = 4 # size of the radius of pixels taken from family to reclaim space
+PARENTRING_WIDTH = 12 # width of the parent ring inside the person
+
+ANGLE_CHEQUI = 0 #Algorithm with homogeneous children distribution
+ANGLE_WEIGHT = 1 #Algorithm for angle computation based on nr of descendants
+
+
+#-------------------------------------------------------------------------
+#
+# FanChartDescWidget
+#
+#-------------------------------------------------------------------------
+
+class FanChartDescWidget(FanChartBaseWidget):
+ """
+ Interactive Fan Chart Widget.
+ """
+ CENTER = 60 # we require a larger center
+
+ def __init__(self, dbstate, callback_popup=None):
+ """
+ Fan Chart Widget. Handles visualization of data in self.data.
+ See main() of FanChartGramplet for example of model format.
+ """
+ self.set_values(None, 9, BACKGROUND_GRAD_GEN, 'Sans', '#0000FF',
+ '#FF0000', None, 0.5, FORM_CIRCLE, ANGLE_WEIGHT)
+ FanChartBaseWidget.__init__(self, dbstate, callback_popup)
+
+ def set_values(self, root_person_handle, maxgen, background,
+ fontdescr, grad_start, grad_end,
+ filter, alpha_filter, form, angle_algo):
+ """
+ Reset the values to be used:
+ root_person_handle = person to show
+ maxgen = maximum generations to show
+ background = config setting of which background procedure to use (int)
+ fontdescr = string describing the font to use
+ grad_start, grad_end: colors to use for background procedure
+ filter = the person filter to apply to the people in the chart
+ alpha = the alpha transparency value (0-1) to apply to filtered out data
+ form = the FORM_ constant for the fanchart
+ """
+ self.rootpersonh = root_person_handle
+ self.generations = maxgen
+ self.background = background
+ self.fontdescr = fontdescr
+ self.grad_start = grad_start
+ self.grad_end = grad_end
+ self.filter = filter
+ self.alpha_filter = alpha_filter
+ self.form = form
+ self.anglealgo = angle_algo
+
+ def gen_pixels(self):
+ """
+ how many pixels a generation takes up in the fanchart
+ """
+ return PIXELS_PER_GENPERSON + PIXELS_PER_GENFAMILY
+
+ def set_generations(self):
+ """
+ Set the generations to max, and fill data structures with initial data.
+ """
+ self.handle2desc = {}
+ self.famhandle2desc = {}
+ self.handle2fam = {}
+ self.gen2people = {}
+ self.gen2fam = {}
+ self.parentsroot = []
+ self.gen2people[0] = [(None, False, 0, 2*pi, '', 0, 0, [], NORMAL)] #no center person
+ self.gen2fam[0] = [] #no families
+ self.angle = {}
+ self.angle[-2] = []
+ for i in range(1, self.generations-1):
+ self.gen2fam[i] = []
+ self.gen2people[i] = []
+ self.gen2people[self.generations-1] = [] #indication of more children
+ self.rotfactor = 1
+ self.rotstartangle = 0
+ if self.form == FORM_HALFCIRCLE:
+ self.rotfactor = 1/2
+ self.rotangle = 90
+ elif self.form == FORM_QUADRANT:
+ self.rotangle = 180
+ self.rotfactor = 1/4
+
+ def _fill_data_structures(self):
+ self.set_generations()
+ person = self.dbstate.db.get_person_from_handle(self.rootpersonh)
+ if not person:
+ #nothing to do, just return
+ return
+ else:
+ name = name_displayer.display(person)
+
+ # person, duplicate or not, start angle, slice size,
+ # text, parent pos in fam, nrfam, userdata, status
+ self.gen2people[0] = [[person, False, 0, 2*pi, name, 0, 0, [], NORMAL]]
+ self.handle2desc[self.rootpersonh] = 0
+ # fill in data for the parents
+ self.parentsroot = []
+ handleparents = []
+ family_handle_list = person.get_parent_family_handle_list()
+ if family_handle_list:
+ for family_handle in family_handle_list:
+ family = self.dbstate.db.get_family_from_handle(family_handle)
+ if not family:
+ continue
+ hfather = family.get_father_handle()
+ if hfather and hfather not in handleparents:
+ father = self.dbstate.db.get_person_from_handle(hfather)
+ if father:
+ self.parentsroot.append((father, []))
+ handleparents.append(hfather)
+ hmother = family.get_mother_handle()
+ if hmother and hmother not in handleparents:
+ mother = self.dbstate.db.get_person_from_handle(hmother)
+ if mother:
+ self.parentsroot.append((mother, []))
+ handleparents.append(hmother)
+
+ #recursively fill in the datastructures:
+ nrdesc = self.__rec_fill_data(0, person, 0)
+ self.handle2desc[person.handle] += nrdesc
+ self.__compute_angles()
+
+ def __rec_fill_data(self, gen, person, pos):
+ """
+ Recursively fill in the data
+ """
+ totdesc = 0
+ nrfam = len(person.get_family_handle_list())
+ self.gen2people[gen][pos][6] = nrfam
+ for family_handle in person.get_family_handle_list():
+ totdescfam = 0
+ family = self.dbstate.db.get_family_from_handle(family_handle)
+
+ spouse_handle = find_spouse(person, family)
+ if spouse_handle:
+ spouse = self.dbstate.db.get_person_from_handle(spouse_handle)
+ spname = name_displayer.display(spouse)
+ else:
+ spname = ''
+ if family_handle in self.famhandle2desc:
+ #family occurs via father and via mother in the chart, only
+ #first to show and count.
+ famdup = True
+ else:
+ famdup = False
+ # family, duplicate or not, start angle, slice size,
+ # text, spouse pos in gen, nrchildren, userdata, parnter, status
+ self.gen2fam[gen].append([family, famdup, 0, 0, spname, pos, 0, [],
+ spouse, NORMAL])
+ posfam = len(self.gen2fam[gen]) - 1
+
+ if not famdup:
+ nrchild = len(family.get_child_ref_list())
+ self.gen2fam[gen][-1][6] = nrchild
+ for child_ref in family.get_child_ref_list():
+ child = self.dbstate.db.get_person_from_handle(child_ref.ref)
+ chname = name_displayer.display(child)
+ if child_ref.ref in self.handle2desc:
+ dup = True
+ else:
+ dup = False
+ self.handle2desc[child_ref.ref] = 0
+ # person, duplicate or not, start angle, slice size,
+ # text, parent pos in fam, nrfam, userdata, status
+ self.gen2people[gen+1].append([child, dup, 0, 0, chname,
+ posfam, 0, [], NORMAL])
+ totdescfam += 1 #add this person as descendant
+ pospers = len(self.gen2people[gen+1]) - 1
+ if not dup and not(self.generations == gen+2):
+ nrdesc = self.__rec_fill_data(gen+1, child, pospers)
+ self.handle2desc[child_ref.ref] += nrdesc
+ totdescfam += nrdesc # add children of him as descendants
+ self.famhandle2desc[family_handle] = totdescfam
+ totdesc += totdescfam
+ return totdesc
+
+ def __compute_angles(self):
+ """
+ Compute the angles of the boxes
+ """
+ #first we compute the size of the slice.
+ nrgen = self.nrgen()
+ #set angles root person
+ if self.form == FORM_CIRCLE:
+ slice = 2*pi
+ start = 0.
+ elif self.form == FORM_HALFCIRCLE:
+ slice = pi
+ start = pi/2
+ elif self.form == FORM_QUADRANT:
+ slice = pi/2
+ start = pi
+ gen = 0
+ data = self.gen2people[gen][0]
+ data[2] = start
+ data[3] = slice
+ for gen in range(1, nrgen):
+ nrpeople = len(self.gen2people[gen])
+ prevpartnerdatahandle = None
+ offset = 0
+ for data in self.gen2fam[gen-1]:
+ #obtain start and stop of partner
+ partnerdata = self.gen2people[gen-1][data[5]]
+ nrdescfam = self.famhandle2desc[data[0].handle]
+ nrdescpartner = self.handle2desc[partnerdata[0].handle]
+ nrfam = partnerdata[6]
+ partstart = partnerdata[2]
+ partslice = partnerdata[3]
+ if prevpartnerdatahandle != partnerdata[0].handle:
+ #reset the offset
+ offset = 0
+ prevpartnerdatahandle = partnerdata[0].handle
+ slice = partslice/(nrdescpartner+nrfam)*(nrdescfam+1)
+ if data[9] == COLLAPSED:
+ slice = 0
+ elif data[9] == EXPANDED:
+ slice = partslice
+
+ data[2] = partstart + offset
+ data[3] = slice
+ offset += slice
+
+## if nrdescpartner == 0:
+## #no offspring, draw as large as fraction of
+## #nr families
+## nrfam = partnerdata[6]
+## slice = partslice/nrfam
+## data[2] = partstart + offset
+## data[3] = slice
+## offset += slice
+## elif nrdescfam == 0:
+## #no offspring this family, but there is another
+## #family. We draw this as a weight of 1
+## nrfam = partnerdata[6]
+## slice = partslice/(nrdescpartner + nrfam - 1)*(nrdescfam+1)
+## data[2] = partstart + offset
+## data[3] = slice
+## offset += slice
+## else:
+## #this family has offspring. We give it space for it's
+## #weight in offspring
+## nrfam = partnerdata[6]
+## slice = partslice/(nrdescpartner + nrfam - 1)*(nrdescfam+1)
+## data[2] = partstart + offset
+## data[3] = slice
+## offset += slice
+
+ prevfamdatahandle = None
+ offset = 0
+ for data in self.gen2people[gen]:
+ #obtain start and stop of family this is child of
+ parentfamdata = self.gen2fam[gen-1][data[5]]
+ nrdescfam = self.famhandle2desc[parentfamdata[0].handle]
+ nrdesc = self.handle2desc[data[0].handle]
+ famstart = parentfamdata[2]
+ famslice = parentfamdata[3]
+ nrchild = parentfamdata[6]
+ #now we divide this slice to the weight of children,
+ #adding one for every child
+ if self.anglealgo == ANGLE_CHEQUI:
+ slice = famslice / nrchild
+ elif self.anglealgo == ANGLE_WEIGHT:
+ slice = famslice/(nrdescfam) * (nrdesc + 1)
+ else:
+ print self.anglealgo == ANGLE_WEIGHT,self.anglealgo, ANGLE_WEIGHT
+ raise NotImplementedError, 'Unknown angle algorithm %d' % self.anglealgo
+ if prevfamdatahandle != parentfamdata[0].handle:
+ #reset the offset
+ offset = 0
+ prevfamdatahandle = parentfamdata[0].handle
+ if data[8] == COLLAPSED:
+ slice = 0
+ elif data[8] == EXPANDED:
+ slice = famslice
+ data[2] = famstart + offset
+ data[3] = slice
+ offset += slice
+
+ def nrgen(self):
+ #compute the number of generations present
+ nrgen = None
+ for gen in range(self.generations - 1, 0, -1):
+ if len(self.gen2people[gen]) > 0:
+ nrgen = gen + 1
+ break
+ if nrgen is None:
+ nrgen = 1
+ return nrgen
+
+ def halfdist(self):
+ """
+ Compute the half radius of the circle
+ """
+ nrgen = self.nrgen()
+ ringpxs = (PIXELS_PER_GENPERSON + PIXELS_PER_GENFAMILY) * (nrgen - 1)
+ return ringpxs + self.CENTER + BORDER_EDGE_WIDTH
+
+ def people_generator(self):
+ """
+ a generator over all people outside of the core person
+ """
+ for generation in range(self.generations):
+ for data in self.gen2people[generation]:
+ yield (data[0], data[7])
+ for generation in range(self.generations-1):
+ for data in self.gen2fam[generation]:
+ yield (data[8], data[7])
+
+ def innerpeople_generator(self):
+ """
+ a generator over all people inside of the core person
+ """
+ for parentdata in self.parentsroot:
+ parent, userdata = parentdata
+ yield (parent, userdata)
+
+ def on_draw(self, widget, cr, scale=1.):
+ """
+ The main method to do the drawing.
+ If widget is given, we assume we draw in GTK3 and use the allocation.
+ To draw raw on the cairo context cr, set widget=None.
+ """
+ # first do size request of what we will need
+ halfdist = self.halfdist()
+ if widget:
+ if self.form == FORM_CIRCLE:
+ self.set_size_request(2 * halfdist, 2 * halfdist)
+ elif self.form == FORM_HALFCIRCLE:
+ self.set_size_request(2 * halfdist, halfdist + self.CENTER
+ + PAD_PX)
+ elif self.form == FORM_QUADRANT:
+ self.set_size_request(halfdist + self.CENTER + PAD_PX,
+ halfdist + self.CENTER + PAD_PX)
+
+ #obtain the allocation
+ alloc = self.get_allocation()
+ x, y, w, h = alloc.x, alloc.y, alloc.width, alloc.height
+
+ cr.scale(scale, scale)
+ # when printing, we need not recalculate
+ if widget:
+ if self.form == FORM_CIRCLE:
+ self.center_x = w/2 - self.center_xy[0]
+ self.center_y = h/2 - self.center_xy[1]
+ elif self.form == FORM_HALFCIRCLE:
+ self.center_x = w/2. - self.center_xy[0]
+ self.center_y = h - self.CENTER - PAD_PX- self.center_xy[1]
+ elif self.form == FORM_QUADRANT:
+ self.center_x = self.CENTER + PAD_PX - self.center_xy[0]
+ self.center_y = h - self.CENTER - PAD_PX - self.center_xy[1]
+ cr.translate(self.center_x, self.center_y)
+
+ cr.save()
+ #draw center
+ cr.set_source_rgb(1, 1, 1) # white
+ cr.move_to(0,0)
+ cr.arc(0, 0, self.CENTER-PIXELS_PER_GENFAMILY, 0, 2 * math.pi)
+ cr.fill()
+ cr.set_source_rgb(0, 0, 0) # black
+ cr.arc(0, 0, self.CENTER-PIXELS_PER_GENFAMILY, 0, 2 * math.pi)
+ cr.stroke()
+ cr.restore()
+ # Draw center person:
+ (person, dup, start, slice, text, parentfampos, nrfam, userdata, status) \
+ = self.gen2people[0][0]
+ if person:
+ r, g, b, a = self.background_box(person, 0, userdata)
+ cr.arc(0, 0, self.CENTER-PIXELS_PER_GENFAMILY, 0, 2 * math.pi)
+ if self.parentsroot:
+ cr.arc_negative(0, 0, TRANSLATE_PX + CHILDRING_WIDTH,
+ 2 * math.pi, 0)
+ cr.close_path()
+ cr.set_source_rgba(r/255, g/255, b/255, a)
+ cr.fill()
+ cr.save()
+ name = name_displayer.display(person)
+ self.draw_text(cr, name, self.CENTER - PIXELS_PER_GENFAMILY
+ - (self.CENTER - PIXELS_PER_GENFAMILY
+ - (CHILDRING_WIDTH + TRANSLATE_PX))/2,
+ 95, 455, 10, False,
+ self.fontcolor(r, g, b, a), self.fontbold(a))
+ cr.restore()
+ #draw center to move chart
+ cr.set_source_rgb(0, 0, 0) # black
+ cr.move_to(TRANSLATE_PX, 0)
+ cr.arc(0, 0, TRANSLATE_PX, 0, 2 * math.pi)
+ if self.parentsroot: # has at least one parent
+ cr.fill()
+ self.draw_parentring(cr)
+ else:
+ cr.stroke()
+ #now write all the families and children
+ cr.save()
+ cr.rotate(self.rotate_value * math.pi/180)
+ radstart = self.CENTER - PIXELS_PER_GENFAMILY - PIXELS_PER_GENPERSON
+ for gen in range(self.generations-1):
+ radstart += PIXELS_PER_GENPERSON
+ for famdata in self.gen2fam[gen]:
+ # family, duplicate or not, start angle, slice size,
+ # text, spouse pos in gen, nrchildren, userdata, status
+ fam, dup, start, slice, text, posfam, nrchild, userdata,\
+ partner, status = famdata
+ if status != COLLAPSED:
+ self.draw_person(cr, text, start, slice, radstart,
+ radstart + PIXELS_PER_GENFAMILY, gen, dup,
+ partner, userdata, family=True, thick=status != NORMAL)
+ radstart += PIXELS_PER_GENFAMILY
+ for pdata in self.gen2people[gen+1]:
+ # person, duplicate or not, start angle, slice size,
+ # text, parent pos in fam, nrfam, userdata, status
+ pers, dup, start, slice, text, pospar, nrfam, userdata, status = \
+ pdata
+ if status != COLLAPSED:
+ self.draw_person(cr, text, start, slice, radstart,
+ radstart + PIXELS_PER_GENPERSON, gen+1, dup,
+ pers, userdata, thick=status != NORMAL)
+ cr.restore()
+
+ if self.background in [BACKGROUND_GRAD_AGE, BACKGROUND_GRAD_PERIOD]:
+ self.draw_gradient(cr, widget, halfdist)
+
+ def draw_person(self, cr, name, start_rad, slice, radius, radiusend,
+ generation, dup, person, userdata, family=False, thick=False):
+ """
+ Display the piece of pie for a given person. start_rad and slice
+ are in radial.
+ """
+ if slice == 0:
+ return
+ cr.save()
+ full = False
+ if abs(slice - 2*pi) < 1e-6:
+ full = True
+ stop_rad = start_rad + slice
+ if not dup:
+ r, g, b, a = self.background_box(person, generation, userdata)
+ else:
+ #duplicate color
+ a = 1
+ r, g, b = (0.2, 0.2, 0.2)
+ # If max generation, and they have children:
+ if (not family and generation == self.generations - 1
+ and self._have_children(person)):
+ # draw an indicator
+ radmax = radiusend + BORDER_EDGE_WIDTH
+ cr.move_to(radmax*math.cos(start_rad), radmax*math.sin(start_rad))
+ cr.arc(0, 0, radmax, start_rad, stop_rad)
+ cr.line_to(radiusend*math.cos(stop_rad), radiusend*math.sin(stop_rad))
+ cr.arc_negative(0, 0, radiusend, stop_rad, start_rad)
+ cr.close_path()
+ ##path = cr.copy_path() # not working correct
+ cr.set_source_rgb(1, 1, 1) # white
+ cr.fill()
+ #and again for the border
+ cr.move_to(radmax*math.cos(start_rad), radmax*math.sin(start_rad))
+ cr.arc(0, 0, radmax, start_rad, stop_rad)
+ cr.line_to(radiusend*math.cos(stop_rad), radiusend*math.sin(stop_rad))
+ cr.arc_negative(0, 0, radiusend, stop_rad, start_rad)
+ cr.close_path()
+ ##cr.append_path(path) # not working correct
+ cr.set_source_rgb(0, 0, 0) # black
+ cr.stroke()
+ # now draw the person
+ self.draw_radbox(cr, radius, radiusend, start_rad, stop_rad,
+ (r/255, g/255, b/255, a), thick)
+ if self.last_x is None or self.last_y is None:
+ #we are not in a move, so draw text
+ radial = False
+ width = radiusend-radius
+ radstart = radius + width/2
+ spacepolartext = radstart * (stop_rad-start_rad)
+ if spacepolartext < width * 1.1:
+ # more space to print it radial
+ radial = True
+ radstart = radius + 4
+ self.draw_text(cr, name, radstart, start_rad/ math.pi*180,
+ stop_rad/ math.pi*180, width, radial,
+ self.fontcolor(r, g, b, a), self.fontbold(a))
+ cr.restore()
+
+ def boxtype(self, radius):
+ """
+ default is only one type of box type
+ """
+ if radius <= self.CENTER:
+ if radius >= self.CENTER - PIXELS_PER_GENFAMILY:
+ return TYPE_BOX_FAMILY
+ else:
+ return TYPE_BOX_NORMAL
+ else:
+ gen = int((radius - self.CENTER)/self.gen_pixels()) + 1
+ radius = (radius - self.CENTER) % PIXELS_PER_GENERATION
+ if radius >= PIXELS_PER_GENPERSON:
+ if gen < self.generations - 1:
+ return TYPE_BOX_FAMILY
+ else:
+ # the last generation has no family boxes
+ None
+ else:
+ return TYPE_BOX_NORMAL
+
+ def draw_parentring(self, cr):
+ cr.move_to(TRANSLATE_PX + CHILDRING_WIDTH, 0)
+ cr.set_source_rgb(0, 0, 0) # black
+ cr.set_line_width(1)
+ cr.arc(0, 0, TRANSLATE_PX + CHILDRING_WIDTH, 0, 2 * math.pi)
+ cr.stroke()
+ nrparent = len(self.parentsroot)
+ #Y axis is downward. positve angles are hence clockwise
+ startangle = math.pi
+ if nrparent <= 2:
+ angleinc = math.pi
+ elif nrparent <= 4:
+ angleinc = math.pi/2
+ else:
+ angleinc = 2 * math.pi / nrchild
+ for data in self.parentsroot:
+ self.draw_innerring(cr, data[0], data[1], startangle, angleinc)
+ startangle += angleinc
+
+ def personpos_at_angle(self, generation, angledeg, btype):
+ """
+ returns the person in generation generation at angle.
+ """
+ angle = angledeg / 360 * 2 * pi
+ selected = None
+ if btype == TYPE_BOX_NORMAL:
+ for p, pdata in enumerate(self.gen2people[generation]):
+ # person, duplicate or not, start angle, slice size,
+ # text, parent pos in fam, nrfam, userdata, status
+ start = pdata[2]
+ stop = start + pdata[3]
+ if start <= angle <= stop:
+ selected = p
+ break
+ elif btype == TYPE_BOX_FAMILY:
+ for p, pdata in enumerate(self.gen2fam[generation]):
+ # person, duplicate or not, start angle, slice size,
+ # text, parent pos in fam, nrfam, userdata, status
+ start = pdata[2]
+ stop = start + pdata[3]
+ if start <= angle <= stop:
+ selected = p
+ break
+ return selected
+
+ def person_at(self, generation, pos, btype):
+ """
+ returns the person at generation, pos, btype
+ """
+ if pos is None:
+ return None
+ if generation == -2:
+ person, userdata = self.parentsroot[pos]
+ elif btype == TYPE_BOX_NORMAL:
+ # person, duplicate or not, start angle, slice size,
+ # text, parent pos in fam, nrfam, userdata, status
+ person = self.gen2people[generation][pos][0]
+ elif btype == TYPE_BOX_FAMILY:
+ # family, duplicate or not, start angle, slice size,
+ # text, spouse pos in gen, nrchildren, userdata, person, status
+ person = self.gen2fam[generation][pos][8]
+ return person
+
+ def do_mouse_click(self):
+ # no drag occured, expand or collapse the section
+ self.change_slice(self._mouse_click_gen, self._mouse_click_sel,
+ self._mouse_click_btype)
+ self._mouse_click = False
+ self.queue_draw()
+
+ def change_slice(self, generation, selected, btype):
+ if generation < 1:
+ return
+ if btype == TYPE_BOX_NORMAL:
+ data = self.gen2people[generation][selected]
+ parpos = data[5]
+ status = data[8]
+ if status == NORMAL:
+ #should be expanded, rest collapsed
+ for entry in self.gen2people[generation]:
+ if entry[5] == parpos:
+ entry[8] = COLLAPSED
+ data[8] = EXPANDED
+ else:
+ #is expanded, set back to normal
+ for entry in self.gen2people[generation]:
+ if entry[5] == parpos:
+ entry[8] = NORMAL
+ if btype == TYPE_BOX_FAMILY:
+ data = self.gen2fam[generation][selected]
+ parpos = data[5]
+ status = data[9]
+ if status == NORMAL:
+ #should be expanded, rest collapsed
+ for entry in self.gen2fam[generation]:
+ if entry[5] == parpos:
+ entry[9] = COLLAPSED
+ data[9] = EXPANDED
+ else:
+ #is expanded, set back to normal
+ for entry in self.gen2fam[generation]:
+ if entry[5] == parpos:
+ entry[9] = NORMAL
+
+ self.__compute_angles()
+
+class FanChartDescGrampsGUI(FanChartGrampsGUI):
+ """ class for functions fanchart GUI elements will need in Gramps
+ """
+
+ def main(self):
+ """
+ Fill the data structures with the active data. This initializes all
+ data.
+ """
+ root_person_handle = self.get_active('Person')
+ self.fan.set_values(root_person_handle, self.maxgen, self.background,
+ self.fonttype, self.grad_start, self.grad_end,
+ self.generic_filter, self.alpha_filter, self.form,
+ self.angle_algo)
+ self.fan.reset()
+ self.fan.queue_draw()
diff --git a/src/plugins/view/Makefile.am b/src/plugins/view/Makefile.am
index 1d583469b..23b58a66e 100644
--- a/src/plugins/view/Makefile.am
+++ b/src/plugins/view/Makefile.am
@@ -12,13 +12,14 @@ pkgpython_PYTHON = \
eventview.py \
familyview.py \
fanchartview.py \
+ fanchartdescview.py \
geoclose.py \
geoevents.py \
geoplaces.py \
geoperson.py \
geofamily.py \
- geofamclose.py \
- geomoves.py \
+ geofamclose.py \
+ geomoves.py \
geography.gpr.py \
htmlrenderer.gpr.py \
grampletview.py \
diff --git a/src/plugins/view/fanchartdescview.py b/src/plugins/view/fanchartdescview.py
new file mode 100644
index 000000000..ea27e7447
--- /dev/null
+++ b/src/plugins/view/fanchartdescview.py
@@ -0,0 +1,521 @@
+# Gramps - a GTK+/GNOME based genealogy program
+#
+# Copyright (C) 2001-2007 Donald N. Allingham, Martin Hawlisch
+# Copyright (C) 2009 Douglas S. Blank
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+# $Id$
+
+## Based on the paper:
+## http://www.cs.utah.edu/~draperg/research/fanchart/draperg_FHT08.pdf
+## and the applet:
+## http://www.cs.utah.edu/~draperg/research/fanchart/demo/
+
+## Found by redwood:
+## http://www.gramps-project.org/bugs/view.php?id=2611
+
+#-------------------------------------------------------------------------
+#
+# Python modules
+#
+#-------------------------------------------------------------------------
+from gi.repository import Gdk
+from gi.repository import Gtk
+import cairo
+from gen.ggettext import gettext as _
+
+#-------------------------------------------------------------------------
+#
+# GRAMPS modules
+#
+#-------------------------------------------------------------------------
+import gen.lib
+import gui.widgets.fanchart as fanchart
+import gui.widgets.fanchartdesc as fanchartdesc
+from gui.views.navigationview import NavigationView
+from gui.views.bookmarks import PersonBookmarks
+from gui.utils import SystemFonts
+
+# the print settings to remember between print sessions
+PRINT_SETTINGS = None
+
+class FanChartDescView(fanchartdesc.FanChartDescGrampsGUI, NavigationView):
+ """
+ The Gramplet code that realizes the FanChartWidget.
+ """
+ #settings in the config file
+ CONFIGSETTINGS = (
+ ('interface.fanview-maxgen', 9),
+ ('interface.fanview-background', fanchart.BACKGROUND_GRAD_GEN),
+ ('interface.fanview-font', 'Sans'),
+ ('interface.fanview-form', fanchart.FORM_CIRCLE),
+ ('interface.color-start-grad', '#ef2929'),
+ ('interface.color-end-grad', '#3d37e9'),
+ ('interface.angle-algorithm', fanchartdesc.ANGLE_WEIGHT),
+ )
+ def __init__(self, pdata, dbstate, uistate, nav_group=0):
+ self.dbstate = dbstate
+ self.uistate = uistate
+
+ NavigationView.__init__(self, _('Descendant Fan Chart'),
+ pdata, dbstate, uistate,
+ dbstate.db.get_bookmarks(),
+ PersonBookmarks,
+ nav_group)
+ fanchartdesc.FanChartDescGrampsGUI.__init__(self, self.on_childmenu_changed)
+ #set needed values
+ self.maxgen = self._config.get('interface.fanview-maxgen')
+ self.background = self._config.get('interface.fanview-background')
+ self.fonttype = self._config.get('interface.fanview-font')
+
+ self.grad_start = self._config.get('interface.color-start-grad')
+ self.grad_end = self._config.get('interface.color-end-grad')
+ self.form = self._config.get('interface.fanview-form')
+ self.angle_algo = self._config.get('interface.angle-algorithm')
+ self.generic_filter = None
+ self.alpha_filter = 0.2
+
+ dbstate.connect('active-changed', self.active_changed)
+ dbstate.connect('database-changed', self.change_db)
+
+ self.additional_uis.append(self.additional_ui())
+ self.allfonts = [x for x in enumerate(SystemFonts().get_system_fonts())]
+
+ def navigation_type(self):
+ return 'Person'
+
+ def build_widget(self):
+ self.set_fan(fanchartdesc.FanChartDescWidget(self.dbstate, self.on_popup))
+ self.scrolledwindow = Gtk.ScrolledWindow(None, None)
+ self.scrolledwindow.set_policy(Gtk.PolicyType.AUTOMATIC,
+ Gtk.PolicyType.AUTOMATIC)
+ self.fan.show_all()
+ self.scrolledwindow.add_with_viewport(self.fan)
+
+ return self.scrolledwindow
+
+ def get_stock(self):
+ """
+ The category stock icon
+ """
+ return 'gramps-pedigree'
+
+ def get_viewtype_stock(self):
+ """Type of view in category
+ """
+ return 'gramps-fanchart'
+
+ def additional_ui(self):
+ return '''
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ '''
+
+ def define_actions(self):
+ """
+ Required define_actions function for PageView. Builds the action
+ group information required.
+ """
+ NavigationView.define_actions(self)
+
+ self._add_action('PrintView', Gtk.STOCK_PRINT, _("_Print/Save View..."),
+ accel="P",
+ tip=_("Print or save the Fan Chart View"),
+ callback=self.printview)
+ def build_tree(self):
+ """
+ Generic method called by PageView to construct the view.
+ Here the tree builds when active person changes or db changes or on
+ callbacks like person_rebuild, so build will be double sometimes.
+ However, change in generic filter also triggers build_tree ! So we
+ need to reset.
+ """
+ self.update()
+
+ def active_changed(self, handle):
+ """
+ Method called when active person changes.
+ """
+ # Reset everything but rotation angle (leave it as is)
+ self.update()
+
+ def _connect_db_signals(self):
+ """
+ Connect database signals.
+ """
+ self._add_db_signal('person-add', self.person_rebuild)
+ self._add_db_signal('person-update', self.person_rebuild)
+ self._add_db_signal('person-delete', self.person_rebuild)
+ self._add_db_signal('person-rebuild', self.person_rebuild_bm)
+ self._add_db_signal('family-update', self.person_rebuild)
+ self._add_db_signal('family-add', self.person_rebuild)
+ self._add_db_signal('family-delete', self.person_rebuild)
+ self._add_db_signal('family-rebuild', self.person_rebuild)
+
+ def change_db(self, db):
+ self._change_db(db)
+ self.bookmarks.update_bookmarks(self.dbstate.db.get_bookmarks())
+ if self.active:
+ self.bookmarks.redraw()
+ self.update()
+
+ def update(self):
+ self.main()
+
+ def goto_handle(self, handle):
+ self.change_active(handle)
+ self.main()
+
+ def get_active(self, object):
+ """overrule get_active, to support call as in Gramplets
+ """
+ return NavigationView.get_active(self)
+
+ def person_rebuild(self, *args):
+ self.update()
+
+ def person_rebuild_bm(self, *args):
+ """Large change to person database"""
+ self.person_rebuild()
+ if self.active:
+ self.bookmarks.redraw()
+
+ def printview(self, obj):
+ """
+ Print or save the view that is currently shown
+ """
+ widthpx = 2 * self.fan.halfdist()
+ heightpx = widthpx
+ if self.form == fanchart.FORM_HALFCIRCLE:
+ heightpx = heightpx / 2 + fanchart.CENTER + fanchart.PAD_PX
+ elif self.form == fanchart.FORM_QUADRANT:
+ heightpx = heightpx / 2 + fanchart.CENTER + fanchart.PAD_PX
+ widthpx = heightpx
+
+ prt = CairoPrintSave(widthpx, heightpx, self.fan.on_draw, self.uistate.window)
+ prt.run()
+
+ def on_childmenu_changed(self, obj, person_handle):
+ """Callback for the pulldown menu selection, changing to the person
+ attached with menu item."""
+ self.change_active(person_handle)
+ return True
+
+ def can_configure(self):
+ """
+ See :class:`~gui.views.pageview.PageView
+ :return: bool
+ """
+ return True
+
+ def _get_configure_page_funcs(self):
+ """
+ Return a list of functions that create gtk elements to use in the
+ notebook pages of the Configure dialog
+
+ :return: list of functions
+ """
+ return [self.config_panel]
+
+ def config_panel(self, configdialog):
+ """
+ Function that builds the widget in the configuration dialog
+ """
+ nrentry = 7
+ table = Gtk.Table(6, 3)
+ table.set_border_width(12)
+ table.set_col_spacings(6)
+ table.set_row_spacings(6)
+
+ configdialog.add_spinner(table, _("Max generations"), 0,
+ 'interface.fanview-maxgen', (1, 11),
+ callback=self.cb_update_maxgen)
+ configdialog.add_combo(table,
+ _('Text Font'),
+ 1, 'interface.fanview-font',
+ self.allfonts, callback=self.cb_update_font, valueactive=True)
+ backgrvals = (
+ (fanchart.BACKGROUND_GENDER, _('Gender colors')),
+ (fanchart.BACKGROUND_GRAD_GEN, _('Generation based gradient')),
+ (fanchart.BACKGROUND_GRAD_AGE, _('Age (0-100) based gradient')),
+ (fanchart.BACKGROUND_SINGLE_COLOR,
+ _('Single main (filter) color')),
+ (fanchart.BACKGROUND_GRAD_PERIOD, _('Time period based gradient')),
+ (fanchart.BACKGROUND_WHITE, _('White')),
+ (fanchart.BACKGROUND_SCHEME1, _('Color scheme classic report')),
+ (fanchart.BACKGROUND_SCHEME2, _('Color scheme classic view')),
+ )
+ curval = self._config.get('interface.fanview-background')
+ nrval = 0
+ for nr, val in backgrvals:
+ if curval == nr:
+ break
+ nrval += 1
+ configdialog.add_combo(table,
+ _('Background'),
+ 2, 'interface.fanview-background',
+ backgrvals,
+ callback=self.cb_update_background, valueactive=False,
+ setactive=nrval
+ )
+ #colors, stored as hex values
+ configdialog.add_color(table, _('Start gradient/Main color'), 3,
+ 'interface.color-start-grad', col=1)
+ configdialog.add_color(table, _('End gradient/2nd color'), 4,
+ 'interface.color-end-grad', col=1)
+ # form of the fan
+ configdialog.add_combo(table, _('Fan chart type'), 5,
+ 'interface.fanview-form',
+ ((fanchart.FORM_CIRCLE, _('Full Circle')),
+ (fanchart.FORM_HALFCIRCLE, _('Half Circle')),
+ (fanchart.FORM_QUADRANT, _('Quadrant'))),
+ callback=self.cb_update_form)
+ # algo for the fan angle distribution
+ configdialog.add_combo(table, _('Fan chart distribution'), 6,
+ 'interface.angle-algorithm',
+ ((fanchartdesc.ANGLE_CHEQUI,
+ _('Homogeneous children distribution')),
+ (fanchartdesc.ANGLE_WEIGHT,
+ _('Size proportional to number of descendants')),
+ ),
+ callback=self.cb_update_anglealgo)
+
+ return _('Layout'), table
+
+ def config_connect(self):
+ """
+ Overwriten from :class:`~gui.views.pageview.PageView method
+ This method will be called after the ini file is initialized,
+ use it to monitor changes in the ini file
+ """
+ self._config.connect('interface.color-start-grad',
+ self.cb_update_color)
+ self._config.connect('interface.color-end-grad',
+ self.cb_update_color)
+
+ def cb_update_maxgen(self, spinbtn, constant):
+ self.maxgen = spinbtn.get_value_as_int()
+ self._config.set(constant, self.maxgen)
+ self.update()
+
+ def cb_update_background(self, obj, constant):
+ entry = obj.get_active()
+ Gtk.TreePath.new_from_string('%d' % entry)
+ val = int(obj.get_model().get_value(
+ obj.get_model().get_iter_from_string('%d' % entry), 0))
+ self._config.set(constant, val)
+ self.background = val
+ self.update()
+
+ def cb_update_form(self, obj, constant):
+ entry = obj.get_active()
+ self._config.set(constant, entry)
+ self.form = entry
+ self.update()
+
+ def cb_update_anglealgo(self, obj, constant):
+ entry = obj.get_active()
+ self._config.set(constant, entry)
+ self.angle_algo = entry
+ self.update()
+
+ def cb_update_color(self, client, cnxn_id, entry, data):
+ """
+ Called when the configuration menu changes the childrenring setting.
+ """
+ self.grad_start = self._config.get('interface.color-start-grad')
+ self.grad_end = self._config.get('interface.color-end-grad')
+ self.update()
+
+ def cb_update_font(self, obj, constant):
+ entry = obj.get_active()
+ self._config.set(constant, self.allfonts[entry][1])
+ self.fonttype = self.allfonts[entry][1]
+ self.update()
+
+ def get_default_gramplets(self):
+ """
+ Define the default gramplets for the sidebar and bottombar.
+ """
+ return (("Person Filter",),
+ ())
+
+#------------------------------------------------------------------------
+#
+# CairoPrintSave class
+#
+#------------------------------------------------------------------------
+class CairoPrintSave():
+ """Act as an abstract document that can render onto a cairo context.
+
+ It can render the model onto cairo context pages, according to the received
+ page style.
+
+ """
+
+ def __init__(self, widthpx, heightpx, drawfunc, parent):
+ """
+ This class provides the things needed so as to dump a cairo drawing on
+ a context to output
+ """
+ self.widthpx = widthpx
+ self.heightpx = heightpx
+ self.drawfunc = drawfunc
+ self.parent = parent
+
+ def run(self):
+ """Create the physical output from the meta document.
+
+ """
+ global PRINT_SETTINGS
+
+ # set up a print operation
+ operation = Gtk.PrintOperation()
+ operation.connect("draw_page", self.on_draw_page)
+ operation.connect("preview", self.on_preview)
+ operation.connect("paginate", self.on_paginate)
+ operation.set_n_pages(1)
+ #paper_size = Gtk.PaperSize.new(name="iso_a4")
+ ## WHY no Gtk.Unit.PIXEL ?? Is there a better way to convert
+ ## Pixels to MM ??
+ paper_size = Gtk.PaperSize.new_custom("custom",
+ "Custom Size",
+ round(self.widthpx * 0.2646),
+ round(self.heightpx * 0.2646),
+ Gtk.Unit.MM)
+ page_setup = Gtk.PageSetup()
+ page_setup.set_paper_size(paper_size)
+ #page_setup.set_orientation(Gtk.PageOrientation.PORTRAIT)
+ operation.set_default_page_setup(page_setup)
+ #operation.set_use_full_page(True)
+
+ if PRINT_SETTINGS is not None:
+ operation.set_print_settings(PRINT_SETTINGS)
+
+ # run print dialog
+ while True:
+ self.preview = None
+ res = operation.run(Gtk.PrintOperationAction.PRINT_DIALOG, self.parent)
+ if self.preview is None: # cancel or print
+ break
+ # set up printing again; can't reuse PrintOperation?
+ operation = Gtk.PrintOperation()
+ operation.set_default_page_setup(page_setup)
+ operation.connect("draw_page", self.on_draw_page)
+ operation.connect("preview", self.on_preview)
+ operation.connect("paginate", self.on_paginate)
+ # set print settings if it was stored previously
+ if PRINT_SETTINGS is not None:
+ operation.set_print_settings(PRINT_SETTINGS)
+
+ # store print settings if printing was successful
+ if res == Gtk.PrintOperationResult.APPLY:
+ PRINT_SETTINGS = operation.get_print_settings()
+
+ def on_draw_page(self, operation, context, page_nr):
+ """Draw a page on a Cairo context.
+ """
+ cr = context.get_cairo_context()
+ pxwidth = round(context.get_width())
+ pxheight = round(context.get_height())
+ scale = min(pxwidth/self.widthpx, pxheight/self.heightpx)
+ if scale > 1:
+ scale = 1
+ self.drawfunc(None, cr, scale=scale)
+
+ def on_paginate(self, operation, context):
+ """Paginate the whole document in chunks.
+ We don't need this as there is only one page, however,
+ we provide a dummy holder here, because on_preview crashes if no
+ default application is set with gir 3.3.2 (typically evince not installed)!
+ It will provide the start of the preview dialog, which cannot be
+ started in on_preview
+ """
+ finished = True
+ # update page number
+ operation.set_n_pages(1)
+
+ # start preview if needed
+ if self.preview:
+ self.preview.run()
+
+ return finished
+
+ def on_preview(self, operation, preview, context, parent):
+ """Implement custom print preview functionality.
+ We provide a dummy holder here, because on_preview crashes if no
+ default application is set with gir 3.3.2 (typically evince not installed)!
+ """
+ dlg = Gtk.MessageDialog(parent,
+ flags=Gtk.DialogFlags.MODAL,
+ type=Gtk.MessageType.WARNING,
+ buttons=Gtk.ButtonsType.CLOSE,
+ message_format=_('No preview available'))
+ self.preview = dlg
+ self.previewopr = operation
+ #dlg.format_secondary_markup(msg2)
+ dlg.set_title("Fan Chart Preview - Gramps")
+ dlg.connect('response', self.previewdestroy)
+
+ # give a dummy cairo context to Gtk.PrintContext,
+ try:
+ width = int(round(context.get_width()))
+ except ValueError:
+ width = 0
+ try:
+ height = int(round(context.get_height()))
+ except ValueError:
+ height = 0
+ surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
+ cr = cairo.Context(surface)
+ context.set_cairo_context(cr, 72.0, 72.0)
+
+ return True
+
+ def previewdestroy(self, dlg, res):
+ self.preview.destroy()
+ self.previewopr.end_preview()
diff --git a/src/plugins/view/view.gpr.py b/src/plugins/view/view.gpr.py
index 06784cc80..5dcbd6a0a 100644
--- a/src/plugins/view/view.gpr.py
+++ b/src/plugins/view/view.gpr.py
@@ -140,17 +140,32 @@ register(VIEW,
id = 'fanchartview',
name = _("Fan Chart View"),
category = ("Ancestry", _("Ancestry")),
-description = _("The view showing relations through a fanchart"),
+description = _("A view showing parents through a fanchart"),
version = '1.0',
gramps_target_version = '4.0',
status = STABLE,
fname = 'fanchartview.py',
-authors = [u"Douglas S. Blank"],
-authors_email = ["doug.blank@gmail.com"],
+authors = [u"Douglas S. Blank", u"B. Malengier"],
+authors_email = ["doug.blank@gmail.com", "benny.malengier@gmail.com"],
viewclass = 'FanChartView',
stock_icon = 'gramps-fanchart',
)
+register(VIEW,
+id = 'fanchartdescview',
+name = _("Descendants Fan Chart View"),
+category = ("Ancestry", _("Ancestry")),
+description = _("Showing descendants through a fanchart"),
+version = '1.0',
+gramps_target_version = '4.0',
+status = STABLE,
+fname = 'fanchartdescview.py',
+authors = [u"B. Malengier"],
+authors_email = ["benny.malengier@gmail.com"],
+viewclass = 'FanChartDescView',
+stock_icon = 'gramps-fanchart',
+ )
+
register(VIEW,
id = 'personview',
name = _("Person Tree View"),