Feature: a fanchart for descendants. Futher to test with duplicates
svn: r20402
This commit is contained 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
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,7 @@ pkgpython_PYTHON = \
 | 
			
		||||
	buttons.py \
 | 
			
		||||
	expandcollapsearrow.py \
 | 
			
		||||
	fanchart.py \
 | 
			
		||||
	fanchartdesc.py \
 | 
			
		||||
	grampletpane.py \
 | 
			
		||||
	labels.py \
 | 
			
		||||
	linkbox.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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										704
									
								
								src/gui/widgets/fanchartdesc.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										704
									
								
								src/gui/widgets/fanchartdesc.py
									
									
									
									
									
										Normal file
									
								
							@@ -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()
 | 
			
		||||
@@ -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 \
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										521
									
								
								src/plugins/view/fanchartdescview.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										521
									
								
								src/plugins/view/fanchartdescview.py
									
									
									
									
									
										Normal file
									
								
							@@ -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 '''<ui>
 | 
			
		||||
          <menubar name="MenuBar">
 | 
			
		||||
            <menu action="GoMenu">
 | 
			
		||||
              <placeholder name="CommonGo">
 | 
			
		||||
                <menuitem action="Back"/>
 | 
			
		||||
                <menuitem action="Forward"/>
 | 
			
		||||
                <separator/>
 | 
			
		||||
                <menuitem action="HomePerson"/>
 | 
			
		||||
                <separator/>
 | 
			
		||||
              </placeholder>
 | 
			
		||||
            </menu>
 | 
			
		||||
            <menu action="EditMenu">
 | 
			
		||||
              <placeholder name="CommonEdit">
 | 
			
		||||
                <menuitem action="PrintView"/>
 | 
			
		||||
              </placeholder>
 | 
			
		||||
            </menu>
 | 
			
		||||
            <menu action="BookMenu">
 | 
			
		||||
              <placeholder name="AddEditBook">
 | 
			
		||||
                <menuitem action="AddBook"/>
 | 
			
		||||
                <menuitem action="EditBook"/>
 | 
			
		||||
              </placeholder>
 | 
			
		||||
            </menu>
 | 
			
		||||
          </menubar>
 | 
			
		||||
          <toolbar name="ToolBar">
 | 
			
		||||
            <placeholder name="CommonNavigation">
 | 
			
		||||
              <toolitem action="Back"/>  
 | 
			
		||||
              <toolitem action="Forward"/>  
 | 
			
		||||
              <toolitem action="HomePerson"/>
 | 
			
		||||
            </placeholder>
 | 
			
		||||
            <placeholder name="CommonEdit">
 | 
			
		||||
              <toolitem action="PrintView"/>
 | 
			
		||||
            </placeholder>
 | 
			
		||||
          </toolbar>
 | 
			
		||||
        </ui>
 | 
			
		||||
        '''
 | 
			
		||||
 | 
			
		||||
    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="<PRIMARY>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()
 | 
			
		||||
@@ -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"),
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user