Feature Fanchart: allow box color based on age. This enables coloring based on other scales also
svn: r20336
This commit is contained in:
parent
d8ccf0de9b
commit
6ff34a87ee
@ -89,6 +89,45 @@ def get_death_or_fallback(db, person, format=None):
|
|||||||
return event
|
return event
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def get_age(db, person, fallback=True, calendar="gregorian"):
|
||||||
|
"""
|
||||||
|
Compute the age of person. Allow fallback events if fallback=True
|
||||||
|
person : person handle or person object
|
||||||
|
Return: tuple of year, month day if valid, None otherwise
|
||||||
|
"""
|
||||||
|
birth = None
|
||||||
|
death = None
|
||||||
|
if isinstance(person, str):
|
||||||
|
# a handle is passed
|
||||||
|
person = db.get_person_from_handle(person)
|
||||||
|
if fallback:
|
||||||
|
birth = get_birth_or_fallback(db, person)
|
||||||
|
death = get_death_or_fallback(db, person)
|
||||||
|
else:
|
||||||
|
birth_ref = person.get_birth_ref()
|
||||||
|
if birth_ref: # regular birth found
|
||||||
|
event = db.get_event_from_handle(birth_ref.ref)
|
||||||
|
if event:
|
||||||
|
birth = event
|
||||||
|
death_ref = person.get_death_ref()
|
||||||
|
if death_ref: # regular death found
|
||||||
|
event = db.get_event_from_handle(death_ref.ref)
|
||||||
|
if event:
|
||||||
|
death = event
|
||||||
|
age = None
|
||||||
|
if birth is not None:
|
||||||
|
birth_date = birth.get_date_object().to_calendar("gregorian")
|
||||||
|
if (birth_date and birth_date.get_valid()):
|
||||||
|
if death is not None:
|
||||||
|
death_date = death.get_date_object().to_calendar("gregorian")
|
||||||
|
if (death_date and death_date.get_valid()):
|
||||||
|
age = death_date - birth_date
|
||||||
|
if not age.is_valid():
|
||||||
|
age = None
|
||||||
|
else:
|
||||||
|
age = age.tuple()
|
||||||
|
return age
|
||||||
|
|
||||||
def get_event_ref(db, family, event_type):
|
def get_event_ref(db, family, event_type):
|
||||||
"""
|
"""
|
||||||
Return a reference to a primary family event of the given event type.
|
Return a reference to a primary family event of the given event type.
|
||||||
|
@ -362,7 +362,7 @@ class ConfigureDialog(ManagedWindow):
|
|||||||
return entry
|
return entry
|
||||||
|
|
||||||
def add_combo(self, table, label, index, constant, opts, callback=None,
|
def add_combo(self, table, label, index, constant, opts, callback=None,
|
||||||
config=None, valueactive=False):
|
config=None, valueactive=False, setactive=None):
|
||||||
"""
|
"""
|
||||||
A drop-down list allowing selection from a number of fixed options.
|
A drop-down list allowing selection from a number of fixed options.
|
||||||
:param opts: A list of options. Each option is a tuple containing an
|
:param opts: A list of options. Each option is a tuple containing an
|
||||||
@ -391,7 +391,10 @@ class ConfigureDialog(ManagedWindow):
|
|||||||
break
|
break
|
||||||
combo.set_active(pos)
|
combo.set_active(pos)
|
||||||
else:
|
else:
|
||||||
combo.set_active(config.get(constant))
|
if setactive is None:
|
||||||
|
combo.set_active(config.get(constant))
|
||||||
|
else:
|
||||||
|
combo.set_active(setactive)
|
||||||
combo.connect('changed', callback, constant)
|
combo.connect('changed', callback, constant)
|
||||||
table.attach(lwidget, 1, 2, index, index+1, yoptions=0,
|
table.attach(lwidget, 1, 2, index, index+1, yoptions=0,
|
||||||
xoptions=Gtk.AttachOptions.FILL)
|
xoptions=Gtk.AttachOptions.FILL)
|
||||||
|
@ -26,7 +26,6 @@
|
|||||||
from basicentry import *
|
from basicentry import *
|
||||||
from buttons import *
|
from buttons import *
|
||||||
from expandcollapsearrow import *
|
from expandcollapsearrow import *
|
||||||
from fanchart import *
|
|
||||||
from labels import *
|
from labels import *
|
||||||
from linkbox import *
|
from linkbox import *
|
||||||
from photo import *
|
from photo import *
|
||||||
|
@ -42,6 +42,7 @@ from gi.repository import GObject
|
|||||||
from gi.repository import Gdk
|
from gi.repository import Gdk
|
||||||
from gi.repository import Gtk
|
from gi.repository import Gtk
|
||||||
from gi.repository import PangoCairo
|
from gi.repository import PangoCairo
|
||||||
|
import cairo
|
||||||
import math
|
import math
|
||||||
import colorsys
|
import colorsys
|
||||||
import cPickle as pickle
|
import cPickle as pickle
|
||||||
@ -53,12 +54,15 @@ from cgi import escape
|
|||||||
#
|
#
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
from gen.display.name import displayer as name_displayer
|
from gen.display.name import displayer as name_displayer
|
||||||
|
from gen.errors import WindowActiveError
|
||||||
|
from gui.editors import EditPerson
|
||||||
import gen.lib
|
import gen.lib
|
||||||
import gui.utils
|
import gui.utils
|
||||||
from gui.ddtargets import DdTargets
|
from gui.ddtargets import DdTargets
|
||||||
from gen.utils.alive import probably_alive
|
from gen.utils.alive import probably_alive
|
||||||
from gen.utils.libformatting import FormattingHelper
|
from gen.utils.libformatting import FormattingHelper
|
||||||
from gen.utils.db import (find_children, find_parents, find_witnessed_people)
|
from gen.utils.db import (find_children, find_parents, find_witnessed_people,
|
||||||
|
get_age)
|
||||||
|
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
@ -115,6 +119,8 @@ class FanChartWidget(Gtk.DrawingArea):
|
|||||||
BACKGROUND_WHITE: ((255,255,255),
|
BACKGROUND_WHITE: ((255,255,255),
|
||||||
(255,255,255),),
|
(255,255,255),),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MAX_AGE = 100
|
||||||
|
|
||||||
COLLAPSED = 0
|
COLLAPSED = 0
|
||||||
NORMAL = 1
|
NORMAL = 1
|
||||||
@ -172,14 +178,21 @@ class FanChartWidget(Gtk.DrawingArea):
|
|||||||
self.center_xy = [0, 0] # distance from center (x, y)
|
self.center_xy = [0, 0] # distance from center (x, y)
|
||||||
self.center = 50 # pixel radius of center
|
self.center = 50 # pixel radius of center
|
||||||
#default values
|
#default values
|
||||||
self.reset(9, self.BACKGROUND_GRAD_GEN, True, True, 'Sans', '#0000FF',
|
self.reset(None, 9, self.BACKGROUND_GRAD_GEN, True, True, 'Sans', '#0000FF',
|
||||||
'#FF0000')
|
'#FF0000')
|
||||||
self.set_size_request(120, 120)
|
self.set_size_request(120, 120)
|
||||||
|
|
||||||
def reset(self, maxgen, background, childring, radialtext, fontdescr,
|
def reset(self, root_person_handle, maxgen, background, childring,
|
||||||
grad_start, grad_end):
|
radialtext, fontdescr, grad_start, grad_end):
|
||||||
"""
|
"""
|
||||||
Reset all of the data on where/how slices appear, and if they are expanded.
|
Reset all of the data:
|
||||||
|
root_person_handle = person to show
|
||||||
|
maxgen = maximum generations to show
|
||||||
|
background = config setting of which background procedure to use (int)
|
||||||
|
childring = to show the center ring with children or not
|
||||||
|
radialtext = try to use radial text or not
|
||||||
|
fontdescr = string describing the font to use
|
||||||
|
grad_start, grad_end: colors to use for background procedure
|
||||||
"""
|
"""
|
||||||
self.cache_fontcolor = {}
|
self.cache_fontcolor = {}
|
||||||
|
|
||||||
@ -191,9 +204,110 @@ class FanChartWidget(Gtk.DrawingArea):
|
|||||||
self.grad_end = grad_end
|
self.grad_end = grad_end
|
||||||
|
|
||||||
self.set_generations(maxgen)
|
self.set_generations(maxgen)
|
||||||
|
|
||||||
|
# fill the data structure: self.data, self.childrenroot, self.angle
|
||||||
|
self._fill_data_structures(root_person_handle)
|
||||||
|
|
||||||
# prepare the colors for the boxes
|
# prepare the colors for the boxes
|
||||||
self.prepare_background_box()
|
self.prepare_background_box()
|
||||||
|
|
||||||
|
def _fill_data_structures(self, root_person_handle):
|
||||||
|
person = self.dbstate.db.get_person_from_handle(root_person_handle)
|
||||||
|
if not person:
|
||||||
|
name = None
|
||||||
|
else:
|
||||||
|
name = name_displayer.display(person)
|
||||||
|
parents = self._have_parents(person)
|
||||||
|
child = self._have_children(person)
|
||||||
|
# our data structure is the text, the person object, parents, child and
|
||||||
|
# list for userdata which we might fill in later.
|
||||||
|
self.data[0][0] = (name, person, parents, child, [])
|
||||||
|
self.childrenroot = []
|
||||||
|
if child:
|
||||||
|
childlist = find_children(self.dbstate.db, person)
|
||||||
|
for child_handle in childlist:
|
||||||
|
child = self.dbstate.db.get_person_from_handle(child_handle)
|
||||||
|
if not child:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
self.childrenroot.append((child_handle, child.get_gender(),
|
||||||
|
self._have_children(child), []))
|
||||||
|
for current in range(1, self.generations):
|
||||||
|
parent = 0
|
||||||
|
# name, person, parents, children
|
||||||
|
for (n, p, q, c, d) in self.data[current - 1]:
|
||||||
|
# Get father's details:
|
||||||
|
person = self._get_parent(p, True)
|
||||||
|
if person:
|
||||||
|
name = name_displayer.display(person)
|
||||||
|
else:
|
||||||
|
name = None
|
||||||
|
if current == self.generations - 1:
|
||||||
|
parents = self._have_parents(person)
|
||||||
|
else:
|
||||||
|
parents = None
|
||||||
|
self.data[current][parent] = (name, person, parents, None, [])
|
||||||
|
if person is None:
|
||||||
|
# start,stop,male/right,state
|
||||||
|
self.angle[current][parent][3] = self.COLLAPSED
|
||||||
|
parent += 1
|
||||||
|
# Get mother's details:
|
||||||
|
person = self._get_parent(p, False)
|
||||||
|
if person:
|
||||||
|
name = name_displayer.display(person)
|
||||||
|
else:
|
||||||
|
name = None
|
||||||
|
if current == self.generations - 1:
|
||||||
|
parents = self._have_parents(person)
|
||||||
|
else:
|
||||||
|
parents = None
|
||||||
|
self.data[current][parent] = (name, person, parents, None, [])
|
||||||
|
if person is None:
|
||||||
|
# start,stop,male/right,state
|
||||||
|
self.angle[current][parent][3] = self.COLLAPSED
|
||||||
|
parent += 1
|
||||||
|
|
||||||
|
def _have_parents(self, person):
|
||||||
|
"""
|
||||||
|
Returns True if a person has parents.
|
||||||
|
TODO: is there no util function for this
|
||||||
|
"""
|
||||||
|
if person:
|
||||||
|
m = self._get_parent(person, False)
|
||||||
|
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):
|
||||||
|
"""
|
||||||
|
Get the father of the family if father == True, otherwise mother
|
||||||
|
"""
|
||||||
|
if person:
|
||||||
|
parent_handle_list = person.get_parent_family_handle_list()
|
||||||
|
if parent_handle_list:
|
||||||
|
family_id = parent_handle_list[0]
|
||||||
|
family = self.dbstate.db.get_family_from_handle(family_id)
|
||||||
|
if family:
|
||||||
|
if father:
|
||||||
|
person_handle = gen.lib.Family.get_father_handle(family)
|
||||||
|
else:
|
||||||
|
person_handle = gen.lib.Family.get_mother_handle(family)
|
||||||
|
if person_handle:
|
||||||
|
return self.dbstate.db.get_person_from_handle(person_handle)
|
||||||
|
return None
|
||||||
|
|
||||||
def set_generations(self, generations):
|
def set_generations(self, generations):
|
||||||
"""
|
"""
|
||||||
Set the generations to max, and fill data structures with initial data.
|
Set the generations to max, and fill data structures with initial data.
|
||||||
@ -204,7 +318,7 @@ class FanChartWidget(Gtk.DrawingArea):
|
|||||||
self.childrenroot = []
|
self.childrenroot = []
|
||||||
for i in range(self.generations):
|
for i in range(self.generations):
|
||||||
# name, person, parents?, children?
|
# name, person, parents?, children?
|
||||||
self.data[i] = [(None,) * 4] * 2 ** i
|
self.data[i] = [(None,) * 5] * 2 ** i
|
||||||
self.angle[i] = []
|
self.angle[i] = []
|
||||||
angle = 0
|
angle = 0
|
||||||
slice = 360.0 / (2 ** i)
|
slice = 360.0 / (2 ** i)
|
||||||
@ -243,7 +357,7 @@ class FanChartWidget(Gtk.DrawingArea):
|
|||||||
nrgen = None
|
nrgen = None
|
||||||
for generation in range(self.generations - 1, 0, -1):
|
for generation in range(self.generations - 1, 0, -1):
|
||||||
for p in range(len(self.data[generation])):
|
for p in range(len(self.data[generation])):
|
||||||
(text, person, parents, child) = self.data[generation][p]
|
(text, person, parents, child, userdata) = self.data[generation][p]
|
||||||
if person:
|
if person:
|
||||||
nrgen = generation
|
nrgen = generation
|
||||||
break
|
break
|
||||||
@ -284,14 +398,14 @@ class FanChartWidget(Gtk.DrawingArea):
|
|||||||
cr.rotate(self.rotate_value * math.pi/180)
|
cr.rotate(self.rotate_value * math.pi/180)
|
||||||
for generation in range(self.generations - 1, 0, -1):
|
for generation in range(self.generations - 1, 0, -1):
|
||||||
for p in range(len(self.data[generation])):
|
for p in range(len(self.data[generation])):
|
||||||
(text, person, parents, child) = self.data[generation][p]
|
(text, person, parents, child, userdata) = self.data[generation][p]
|
||||||
if person:
|
if person:
|
||||||
start, stop, male, state = self.angle[generation][p]
|
start, stop, male, state = self.angle[generation][p]
|
||||||
if state in [self.NORMAL, self.EXPANDED]:
|
if state in [self.NORMAL, self.EXPANDED]:
|
||||||
self.draw_person(cr, gender_code(male),
|
self.draw_person(cr, gender_code(male),
|
||||||
text, start, stop,
|
text, start, stop,
|
||||||
generation, state, parents, child,
|
generation, state, parents, child,
|
||||||
person)
|
person, userdata)
|
||||||
cr.set_source_rgb(1, 1, 1) # white
|
cr.set_source_rgb(1, 1, 1) # white
|
||||||
cr.move_to(0,0)
|
cr.move_to(0,0)
|
||||||
cr.arc(0, 0, self.center, 0, 2 * math.pi)
|
cr.arc(0, 0, self.center, 0, 2 * math.pi)
|
||||||
@ -302,9 +416,9 @@ class FanChartWidget(Gtk.DrawingArea):
|
|||||||
cr.stroke()
|
cr.stroke()
|
||||||
cr.restore()
|
cr.restore()
|
||||||
# Draw center person:
|
# Draw center person:
|
||||||
(text, person, parents, child) = self.data[0][0]
|
(text, person, parents, child, userdata) = self.data[0][0]
|
||||||
if person:
|
if person:
|
||||||
r, g, b = self.background_box(person, person.gender, 0)
|
r, g, b = self.background_box(person, person.gender, 0, userdata)
|
||||||
cr.arc(0, 0, self.center, 0, 2 * math.pi)
|
cr.arc(0, 0, self.center, 0, 2 * math.pi)
|
||||||
cr.set_source_rgb(r/255, g/255, b/255)
|
cr.set_source_rgb(r/255, g/255, b/255)
|
||||||
cr.fill()
|
cr.fill()
|
||||||
@ -323,79 +437,11 @@ class FanChartWidget(Gtk.DrawingArea):
|
|||||||
cr.stroke()
|
cr.stroke()
|
||||||
if child and self.childring:
|
if child and self.childring:
|
||||||
self.drawchildring(cr)
|
self.drawchildring(cr)
|
||||||
|
if self.background in [self.BACKGROUND_GRAD_AGE]:
|
||||||
def prepare_background_box(self):
|
self.draw_gradient(cr, widget, halfdist)
|
||||||
"""
|
|
||||||
Method that is called every reset of the chart, to precomputed values
|
|
||||||
needed for the background of the boxes
|
|
||||||
"""
|
|
||||||
maxgen = self.generations
|
|
||||||
if self.background == self.BACKGROUND_GENDER:
|
|
||||||
# nothing to precompute
|
|
||||||
self.colors = None
|
|
||||||
elif self.background == self.BACKGROUND_GRAD_GEN:
|
|
||||||
#compute the colors, -1, 0, ..., maxgen
|
|
||||||
cstart = gui.utils.hex_to_rgb(self.grad_start)
|
|
||||||
cend = gui.utils.hex_to_rgb(self.grad_end)
|
|
||||||
cstart_hsv = colorsys.rgb_to_hsv(cstart[0]/255, cstart[1]/255,
|
|
||||||
cstart[2]/255)
|
|
||||||
cend_hsv = colorsys.rgb_to_hsv(cend[0]/255, cend[1]/255,
|
|
||||||
cend[2]/255)
|
|
||||||
divs = [x/(maxgen-1) for x in range(maxgen)]
|
|
||||||
rgb_colors = [colorsys.hsv_to_rgb(
|
|
||||||
(1-x) * cstart_hsv[0] + x * cend_hsv[0],
|
|
||||||
(1-x) * cstart_hsv[1] + x * cend_hsv[1],
|
|
||||||
(1-x) * cstart_hsv[2] + x * cend_hsv[2],
|
|
||||||
) for x in divs]
|
|
||||||
self.colors = [(255*r, 255*g, 255*b) for r, g, b in rgb_colors]
|
|
||||||
else:
|
|
||||||
# known colors per generation, set or compute them
|
|
||||||
self.colors = self.GENCOLOR[self.background]
|
|
||||||
|
|
||||||
def background_box(self, person, gender, generation):
|
|
||||||
"""
|
|
||||||
determine red, green, blue value of background of the box of person,
|
|
||||||
which has gender gender, and is in ring generation
|
|
||||||
"""
|
|
||||||
if generation == 0 and self.background in [self.BACKGROUND_GENDER,
|
|
||||||
self.BACKGROUND_GRAD_GEN, self.BACKGROUND_SCHEME1,
|
|
||||||
self.BACKGROUND_SCHEME2]:
|
|
||||||
# white for center person:
|
|
||||||
return (255, 255, 255)
|
|
||||||
if self.background == self.BACKGROUND_GENDER:
|
|
||||||
try:
|
|
||||||
alive = probably_alive(person, self.dbstate.db)
|
|
||||||
except RuntimeError:
|
|
||||||
alive = False
|
|
||||||
backgr, border = gui.utils.color_graph_box(alive, person.gender)
|
|
||||||
r, g, b = gui.utils.hex_to_rgb(backgr)
|
|
||||||
else:
|
|
||||||
if self.background == self.BACKGROUND_GRAD_GEN and generation < 0:
|
|
||||||
generation = 0
|
|
||||||
r, g, b = self.colors[generation % len(self.colors)]
|
|
||||||
if gender == gen.lib.Person.MALE:
|
|
||||||
r *= .9
|
|
||||||
g *= .9
|
|
||||||
b *= .9
|
|
||||||
return r, g, b
|
|
||||||
|
|
||||||
def fontcolor(self, r, g, b):
|
|
||||||
"""
|
|
||||||
return the font color based on the r, g, b of the background
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return self.cache_fontcolor[(r, g, b)]
|
|
||||||
except KeyError:
|
|
||||||
hls = colorsys.rgb_to_hls(r/255, g/255, b/255)
|
|
||||||
# we use the lightness value to determine white or black font
|
|
||||||
if hls[1] > 0.4:
|
|
||||||
self.cache_fontcolor[(r, g, b)] = (0, 0, 0)
|
|
||||||
else:
|
|
||||||
self.cache_fontcolor[(r, g, b)] = (255, 255, 255)
|
|
||||||
return self.cache_fontcolor[(r, g, b)]
|
|
||||||
|
|
||||||
def draw_person(self, cr, gender, name, start, stop, generation,
|
def draw_person(self, cr, gender, name, start, stop, generation,
|
||||||
state, parents, child, person):
|
state, parents, child, person, userdata):
|
||||||
"""
|
"""
|
||||||
Display the piece of pie for a given person. start and stop
|
Display the piece of pie for a given person. start and stop
|
||||||
are in degrees. Gender is indication of father position or mother
|
are in degrees. Gender is indication of father position or mother
|
||||||
@ -404,7 +450,7 @@ class FanChartWidget(Gtk.DrawingArea):
|
|||||||
cr.save()
|
cr.save()
|
||||||
start_rad = start * math.pi/180
|
start_rad = start * math.pi/180
|
||||||
stop_rad = stop * math.pi/180
|
stop_rad = stop * math.pi/180
|
||||||
r, g, b = self.background_box(person, gender, generation)
|
r, g, b = self.background_box(person, gender, generation, userdata)
|
||||||
radius = generation * self.PIXELS_PER_GENERATION + self.center
|
radius = generation * self.PIXELS_PER_GENERATION + self.center
|
||||||
# If max generation, and they have parents:
|
# If max generation, and they have parents:
|
||||||
if generation == self.generations - 1 and parents:
|
if generation == self.generations - 1 and parents:
|
||||||
@ -467,7 +513,7 @@ class FanChartWidget(Gtk.DrawingArea):
|
|||||||
startangle += angleinc
|
startangle += angleinc
|
||||||
|
|
||||||
def drawchild(self, cr, childdata, start, inc):
|
def drawchild(self, cr, childdata, start, inc):
|
||||||
child_handle, child_gender, has_child = childdata
|
child_handle, child_gender, has_child, userdata = childdata
|
||||||
# in polar coordinates what is to draw
|
# in polar coordinates what is to draw
|
||||||
rmin = self.TRANSLATE_PX
|
rmin = self.TRANSLATE_PX
|
||||||
rmax = self.TRANSLATE_PX + self.CHILDRING_WIDTH
|
rmax = self.TRANSLATE_PX + self.CHILDRING_WIDTH
|
||||||
@ -487,7 +533,7 @@ class FanChartWidget(Gtk.DrawingArea):
|
|||||||
cr.stroke()
|
cr.stroke()
|
||||||
#now fill
|
#now fill
|
||||||
person = self.dbstate.db.get_person_from_handle(child_handle)
|
person = self.dbstate.db.get_person_from_handle(child_handle)
|
||||||
r, g, b = self.background_box(person, person.gender, -1)
|
r, g, b = self.background_box(person, person.gender, -1, userdata)
|
||||||
_childpath(cr)
|
_childpath(cr)
|
||||||
cr.set_source_rgb(r/255., g/255., b/255.)
|
cr.set_source_rgb(r/255., g/255., b/255.)
|
||||||
cr.fill()
|
cr.fill()
|
||||||
@ -615,6 +661,153 @@ class FanChartWidget(Gtk.DrawingArea):
|
|||||||
cr.restore()
|
cr.restore()
|
||||||
cr.restore()
|
cr.restore()
|
||||||
|
|
||||||
|
def draw_gradient(self, cr, widget, halfdist):
|
||||||
|
gradwidth = 10
|
||||||
|
gradheight = 10
|
||||||
|
starth = 25
|
||||||
|
startw = 5
|
||||||
|
alloc = self.get_allocation()
|
||||||
|
x, y, w, h = alloc.x, alloc.y, alloc.width, alloc.height
|
||||||
|
cr.save()
|
||||||
|
if widget:
|
||||||
|
cr.translate(-w/2. + self.center_xy[0], -h/2. + self.center_xy[1])
|
||||||
|
else:
|
||||||
|
cr.translate(-halfdist + self.center_xy[0], -halfdist + self.center_xy[1])
|
||||||
|
font = Pango.FontDescription(self.fontdescr)
|
||||||
|
fontsize = self.fontsize
|
||||||
|
font.set_size(fontsize * Pango.SCALE)
|
||||||
|
for color, text in zip(self.gradcol, self.gradval):
|
||||||
|
cr.move_to(startw, starth)
|
||||||
|
cr.rectangle(startw, starth, gradwidth, gradheight)
|
||||||
|
cr.set_source_rgb(color[0], color[1], color[2])
|
||||||
|
cr.fill()
|
||||||
|
layout = self.create_pango_layout(text)
|
||||||
|
layout.set_font_description(font)
|
||||||
|
cr.move_to(startw+gradwidth+4, starth)
|
||||||
|
cr.set_source_rgb(0, 0, 0) #black
|
||||||
|
PangoCairo.show_layout(cr, layout)
|
||||||
|
starth = starth+gradheight
|
||||||
|
cr.restore()
|
||||||
|
|
||||||
|
def prepare_background_box(self):
|
||||||
|
"""
|
||||||
|
Method that is called every reset of the chart, to precomputed values
|
||||||
|
needed for the background of the boxes
|
||||||
|
"""
|
||||||
|
maxgen = self.generations
|
||||||
|
cstart = gui.utils.hex_to_rgb(self.grad_start)
|
||||||
|
cend = gui.utils.hex_to_rgb(self.grad_end)
|
||||||
|
cstart_hsv = colorsys.rgb_to_hsv(cstart[0]/255, cstart[1]/255,
|
||||||
|
cstart[2]/255)
|
||||||
|
cend_hsv = colorsys.rgb_to_hsv(cend[0]/255, cend[1]/255,
|
||||||
|
cend[2]/255)
|
||||||
|
if self.background == self.BACKGROUND_GENDER:
|
||||||
|
# nothing to precompute
|
||||||
|
self.colors = None
|
||||||
|
elif self.background == self.BACKGROUND_GRAD_GEN:
|
||||||
|
#compute the colors, -1, 0, ..., maxgen
|
||||||
|
divs = [x/(maxgen-1) for x in range(maxgen)]
|
||||||
|
rgb_colors = [colorsys.hsv_to_rgb(
|
||||||
|
(1-x) * cstart_hsv[0] + x * cend_hsv[0],
|
||||||
|
(1-x) * cstart_hsv[1] + x * cend_hsv[1],
|
||||||
|
(1-x) * cstart_hsv[2] + x * cend_hsv[2],
|
||||||
|
) for x in divs]
|
||||||
|
self.colors = [(255*r, 255*g, 255*b) for r, g, b in rgb_colors]
|
||||||
|
elif self.background == self.BACKGROUND_GRAD_AGE:
|
||||||
|
# we fill in in the data structure what the age is, None if no age
|
||||||
|
for generation in range(self.generations):
|
||||||
|
for p in range(len(self.data[generation])):
|
||||||
|
agecol = (255, 255, 255) # white
|
||||||
|
(text, person, parents, child, userdata) = self.data[generation][p]
|
||||||
|
if person:
|
||||||
|
age = get_age(self.dbstate.db, person)
|
||||||
|
if age is not None:
|
||||||
|
age = age[0]
|
||||||
|
if age < 0:
|
||||||
|
age = 0
|
||||||
|
#now determine fraction for gradient
|
||||||
|
agefrac = age / self.MAX_AGE
|
||||||
|
agecol = colorsys.hsv_to_rgb(
|
||||||
|
(1-agefrac) * cstart_hsv[0] + agefrac * cend_hsv[0],
|
||||||
|
(1-agefrac) * cstart_hsv[1] + agefrac * cend_hsv[1],
|
||||||
|
(1-agefrac) * cstart_hsv[2] + agefrac * cend_hsv[2],
|
||||||
|
)
|
||||||
|
userdata.append((agecol[0]*255, agecol[1]*255, agecol[2]*255))
|
||||||
|
# same for child
|
||||||
|
for childdata in self.childrenroot:
|
||||||
|
agecol = (255, 255, 255) # white
|
||||||
|
child_handle, child_gender, has_child, userdata = childdata
|
||||||
|
child = self.dbstate.db.get_person_from_handle(child_handle)
|
||||||
|
age = get_age(self.dbstate.db, child)
|
||||||
|
if age is not None:
|
||||||
|
age = age.tuple()[0]
|
||||||
|
if age < 0:
|
||||||
|
age = 0
|
||||||
|
#now determine fraction for gradient
|
||||||
|
agefrac = age / self.MAX_AGE
|
||||||
|
agecol = colorsys.hsv_to_rgb(
|
||||||
|
(1-agefrac) * cstart_hsv[0] + agefrac * cend_hsv[0],
|
||||||
|
(1-agefrac) * cstart_hsv[1] + agefrac * cend_hsv[1],
|
||||||
|
(1-agefrac) * cstart_hsv[2] + agefrac * cend_hsv[2],
|
||||||
|
)
|
||||||
|
userdata.append(agecol)
|
||||||
|
#now create gradient data, 5 values from 0 to max
|
||||||
|
steps = 5
|
||||||
|
divs = [x/steps for x in range(steps+1)]
|
||||||
|
self.gradval = ['%d' % int(x*self.MAX_AGE) for x in divs]
|
||||||
|
self.gradcol = [colorsys.hsv_to_rgb(
|
||||||
|
(1-div) * cstart_hsv[0] + div * cend_hsv[0],
|
||||||
|
(1-div) * cstart_hsv[1] + div * cend_hsv[1],
|
||||||
|
(1-div) * cstart_hsv[2] + div * cend_hsv[2],
|
||||||
|
) for div in divs]
|
||||||
|
else:
|
||||||
|
# known colors per generation, set or compute them
|
||||||
|
self.colors = self.GENCOLOR[self.background]
|
||||||
|
|
||||||
|
def background_box(self, person, gender, generation, userdata):
|
||||||
|
"""
|
||||||
|
determine red, green, blue value of background of the box of person,
|
||||||
|
which has gender gender, and is in ring generation
|
||||||
|
"""
|
||||||
|
if generation == 0 and self.background in [self.BACKGROUND_GENDER,
|
||||||
|
self.BACKGROUND_GRAD_GEN, self.BACKGROUND_SCHEME1,
|
||||||
|
self.BACKGROUND_SCHEME2]:
|
||||||
|
# white for center person:
|
||||||
|
return (255, 255, 255)
|
||||||
|
if self.background == self.BACKGROUND_GENDER:
|
||||||
|
try:
|
||||||
|
alive = probably_alive(person, self.dbstate.db)
|
||||||
|
except RuntimeError:
|
||||||
|
alive = False
|
||||||
|
backgr, border = gui.utils.color_graph_box(alive, person.gender)
|
||||||
|
r, g, b = gui.utils.hex_to_rgb(backgr)
|
||||||
|
elif self.background == self.BACKGROUND_GRAD_AGE:
|
||||||
|
r, g, b = userdata[0]
|
||||||
|
else:
|
||||||
|
if self.background == self.BACKGROUND_GRAD_GEN and generation < 0:
|
||||||
|
generation = 0
|
||||||
|
r, g, b = self.colors[generation % len(self.colors)]
|
||||||
|
if gender == gen.lib.Person.MALE:
|
||||||
|
r *= .9
|
||||||
|
g *= .9
|
||||||
|
b *= .9
|
||||||
|
return r, g, b
|
||||||
|
|
||||||
|
def fontcolor(self, r, g, b):
|
||||||
|
"""
|
||||||
|
return the font color based on the r, g, b of the background
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return self.cache_fontcolor[(r, g, b)]
|
||||||
|
except KeyError:
|
||||||
|
hls = colorsys.rgb_to_hls(r/255, g/255, b/255)
|
||||||
|
# we use the lightness value to determine white or black font
|
||||||
|
if hls[1] > 0.4:
|
||||||
|
self.cache_fontcolor[(r, g, b)] = (0, 0, 0)
|
||||||
|
else:
|
||||||
|
self.cache_fontcolor[(r, g, b)] = (255, 255, 255)
|
||||||
|
return self.cache_fontcolor[(r, g, b)]
|
||||||
|
|
||||||
def expand_parents(self, generation, selected, current):
|
def expand_parents(self, generation, selected, current):
|
||||||
if generation >= self.generations: return
|
if generation >= self.generations: return
|
||||||
selected = 2 * selected
|
selected = 2 * selected
|
||||||
@ -730,9 +923,11 @@ class FanChartWidget(Gtk.DrawingArea):
|
|||||||
tooltip = ""
|
tooltip = ""
|
||||||
person = None
|
person = None
|
||||||
if selected is not None and generation >= 0:
|
if selected is not None and generation >= 0:
|
||||||
text, person, parents, child = self.data[generation][selected]
|
text, person, parents, child, userdata = \
|
||||||
|
self.data[generation][selected]
|
||||||
elif selected is not None and generation == -2:
|
elif selected is not None and generation == -2:
|
||||||
child_handle, child_gender, has_child = self.childrenroot[selected]
|
child_handle, child_gender, has_child, userdata = \
|
||||||
|
self.childrenroot[selected]
|
||||||
person = self.dbstate.db.get_person_from_handle(child_handle)
|
person = self.dbstate.db.get_person_from_handle(child_handle)
|
||||||
if person:
|
if person:
|
||||||
tooltip = self.format_helper.format_person(person, 11)
|
tooltip = self.format_helper.format_person(person, 11)
|
||||||
@ -845,10 +1040,12 @@ class FanChartWidget(Gtk.DrawingArea):
|
|||||||
# Do things based on state, event.get_state(), or button, event.button
|
# Do things based on state, event.get_state(), or button, event.button
|
||||||
if gui.utils.is_right_click(event):
|
if gui.utils.is_right_click(event):
|
||||||
if generation == -2:
|
if generation == -2:
|
||||||
child_handle, child_gender, has_child = self.childrenroot[selected]
|
child_handle, child_gender, has_child, userdata = \
|
||||||
|
self.childrenroot[selected]
|
||||||
person = self.dbstate.db.get_person_from_handle(child_handle)
|
person = self.dbstate.db.get_person_from_handle(child_handle)
|
||||||
else:
|
else:
|
||||||
text, person, parents, child = self.data[generation][selected]
|
text, person, parents, child, userdata = \
|
||||||
|
self.data[generation][selected]
|
||||||
if person and self.on_popup:
|
if person and self.on_popup:
|
||||||
self.on_popup(widget, event, person.handle)
|
self.on_popup(widget, event, person.handle)
|
||||||
return True
|
return True
|
||||||
@ -891,11 +1088,11 @@ class FanChartWidget(Gtk.DrawingArea):
|
|||||||
tgs = [x.name() for x in context.list_targets()]
|
tgs = [x.name() for x in context.list_targets()]
|
||||||
if self._mouse_click_gen == -2:
|
if self._mouse_click_gen == -2:
|
||||||
#children
|
#children
|
||||||
child_handle, child_gender, has_child \
|
child_handle, child_gender, has_child, userdata = \
|
||||||
= self.childrenroot[self._mouse_click_sel]
|
self.childrenroot[self._mouse_click_sel]
|
||||||
person = self.dbstate.db.get_person_from_handle(child_handle)
|
person = self.dbstate.db.get_person_from_handle(child_handle)
|
||||||
else:
|
else:
|
||||||
text, person, parents, child \
|
text, person, parents, child, userdata \
|
||||||
= self.data[self._mouse_click_gen][self._mouse_click_sel]
|
= self.data[self._mouse_click_gen][self._mouse_click_sel]
|
||||||
if info == DdTargets.PERSON_LINK.app_id:
|
if info == DdTargets.PERSON_LINK.app_id:
|
||||||
data = (DdTargets.PERSON_LINK.drag_type,
|
data = (DdTargets.PERSON_LINK.drag_type,
|
||||||
@ -938,45 +1135,6 @@ class FanChartGrampsGUI(object):
|
|||||||
self.fonttype = font
|
self.fonttype = font
|
||||||
self.grad_start = '#0000FF'
|
self.grad_start = '#0000FF'
|
||||||
self.grad_end = '#FF0000'
|
self.grad_end = '#FF0000'
|
||||||
|
|
||||||
def have_parents(self, person):
|
|
||||||
"""on_childmenu_changed
|
|
||||||
Returns True if a person has parents.
|
|
||||||
"""
|
|
||||||
if person:
|
|
||||||
m = self.get_parent(person, False)
|
|
||||||
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.
|
|
||||||
"""
|
|
||||||
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):
|
|
||||||
"""
|
|
||||||
Get the father of the family if father == True, otherwise mother
|
|
||||||
"""
|
|
||||||
if person:
|
|
||||||
parent_handle_list = person.get_parent_family_handle_list()
|
|
||||||
if parent_handle_list:
|
|
||||||
family_id = parent_handle_list[0]
|
|
||||||
family = self.dbstate.db.get_family_from_handle(family_id)
|
|
||||||
if family:
|
|
||||||
if father:
|
|
||||||
person_handle = gen.lib.Family.get_father_handle(family)
|
|
||||||
else:
|
|
||||||
person_handle = gen.lib.Family.get_mother_handle(family)
|
|
||||||
if person_handle:
|
|
||||||
return self.dbstate.db.get_person_from_handle(person_handle)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def set_fan(self, fan):
|
def set_fan(self, fan):
|
||||||
"""
|
"""
|
||||||
@ -991,62 +1149,10 @@ class FanChartGrampsGUI(object):
|
|||||||
Fill the data structures with the active data. This initializes all
|
Fill the data structures with the active data. This initializes all
|
||||||
data.
|
data.
|
||||||
"""
|
"""
|
||||||
self.fan.reset(self.maxgen, self.background, self.childring,
|
root_person_handle = self.get_active('Person')
|
||||||
|
self.fan.reset(root_person_handle, self.maxgen, self.background, self.childring,
|
||||||
self.radialtext, self.fonttype,
|
self.radialtext, self.fonttype,
|
||||||
self.grad_start, self.grad_end)
|
self.grad_start, self.grad_end)
|
||||||
person = self.dbstate.db.get_person_from_handle(self.get_active('Person'))
|
|
||||||
if not person:
|
|
||||||
name = None
|
|
||||||
else:
|
|
||||||
name = name_displayer.display(person)
|
|
||||||
parents = self.have_parents(person)
|
|
||||||
child = self.have_children(person)
|
|
||||||
self.fan.data[0][0] = (name, person, parents, child)
|
|
||||||
self.fan.childrenroot = []
|
|
||||||
if child:
|
|
||||||
childlist = find_children(self.dbstate.db, person)
|
|
||||||
for child_handle in childlist:
|
|
||||||
child = self.dbstate.db.get_person_from_handle(child_handle)
|
|
||||||
if not child:
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
self.fan.childrenroot.append((child_handle,
|
|
||||||
child.get_gender(),
|
|
||||||
self.have_children(child)))
|
|
||||||
for current in range(1, self.maxgen):
|
|
||||||
parent = 0
|
|
||||||
# name, person, parents, children
|
|
||||||
for (n,p,q,c) in self.fan.data[current - 1]:
|
|
||||||
# Get father's details:
|
|
||||||
person = self.get_parent(p, True)
|
|
||||||
if person:
|
|
||||||
name = name_displayer.display(person)
|
|
||||||
else:
|
|
||||||
name = None
|
|
||||||
if current == self.maxgen - 1:
|
|
||||||
parents = self.have_parents(person)
|
|
||||||
else:
|
|
||||||
parents = None
|
|
||||||
self.fan.data[current][parent] = (name, person, parents, None)
|
|
||||||
if person is None:
|
|
||||||
# start,stop,male/right,state
|
|
||||||
self.fan.angle[current][parent][3] = self.fan.COLLAPSED
|
|
||||||
parent += 1
|
|
||||||
# Get mother's details:
|
|
||||||
person = self.get_parent(p, False)
|
|
||||||
if person:
|
|
||||||
name = name_displayer.display(person)
|
|
||||||
else:
|
|
||||||
name = None
|
|
||||||
if current == self.maxgen - 1:
|
|
||||||
parents = self.have_parents(person)
|
|
||||||
else:
|
|
||||||
parents = None
|
|
||||||
self.fan.data[current][parent] = (name, person, parents, None)
|
|
||||||
if person is None:
|
|
||||||
# start,stop,male/right,state
|
|
||||||
self.fan.angle[current][parent][3] = self.fan.COLLAPSED
|
|
||||||
parent += 1
|
|
||||||
self.fan.queue_draw()
|
self.fan.queue_draw()
|
||||||
|
|
||||||
def on_popup(self, obj, event, person_handle):
|
def on_popup(self, obj, event, person_handle):
|
||||||
|
@ -45,9 +45,7 @@ from gen.ggettext import gettext as _
|
|||||||
import gen.lib
|
import gen.lib
|
||||||
from gui.widgets.fanchart import FanChartWidget, FanChartGrampsGUI
|
from gui.widgets.fanchart import FanChartWidget, FanChartGrampsGUI
|
||||||
from gui.views.navigationview import NavigationView
|
from gui.views.navigationview import NavigationView
|
||||||
from gen.errors import WindowActiveError
|
|
||||||
from gui.views.bookmarks import PersonBookmarks
|
from gui.views.bookmarks import PersonBookmarks
|
||||||
from gui.editors import EditPerson
|
|
||||||
from gui.utils import SystemFonts
|
from gui.utils import SystemFonts
|
||||||
|
|
||||||
# the print settings to remember between print sessions
|
# the print settings to remember between print sessions
|
||||||
@ -263,17 +261,27 @@ class FanChartView(FanChartGrampsGUI, NavigationView):
|
|||||||
_('Text Font'),
|
_('Text Font'),
|
||||||
1, 'interface.fanview-font',
|
1, 'interface.fanview-font',
|
||||||
self.allfonts, callback=self.cb_update_font, valueactive=True)
|
self.allfonts, callback=self.cb_update_font, valueactive=True)
|
||||||
configdialog.add_combo(table,
|
backgrvals = (
|
||||||
_('Background'),
|
|
||||||
2, 'interface.fanview-background',
|
|
||||||
(
|
|
||||||
(self.fan.BACKGROUND_GENDER, _('Gender colors')),
|
(self.fan.BACKGROUND_GENDER, _('Gender colors')),
|
||||||
(self.fan.BACKGROUND_GRAD_GEN, _('Generation based gradient')),
|
(self.fan.BACKGROUND_GRAD_GEN, _('Generation based gradient')),
|
||||||
|
(self.fan.BACKGROUND_GRAD_AGE, _('Age (0-100) based gradient')),
|
||||||
(self.fan.BACKGROUND_WHITE, _('White')),
|
(self.fan.BACKGROUND_WHITE, _('White')),
|
||||||
(self.fan.BACKGROUND_SCHEME1, _('Color scheme classic report')),
|
(self.fan.BACKGROUND_SCHEME1, _('Color scheme classic report')),
|
||||||
(self.fan.BACKGROUND_SCHEME2, _('Color scheme classic view')),
|
(self.fan.BACKGROUND_SCHEME2, _('Color scheme classic view')),
|
||||||
),
|
)
|
||||||
callback=self.cb_update_background)
|
curval = self._config.get('interface.fanview-background')
|
||||||
|
nrval = 0
|
||||||
|
for nr, val in backgrvals:
|
||||||
|
if curval == nr:
|
||||||
|
break
|
||||||
|
nrval += 1
|
||||||
|
print nrval
|
||||||
|
configdialog.add_combo(table,
|
||||||
|
_('Background'),
|
||||||
|
2, 'interface.fanview-background',
|
||||||
|
backgrvals,
|
||||||
|
callback=self.cb_update_background, valueactive=False, setactive=nrval
|
||||||
|
)
|
||||||
#colors, stored as hex values
|
#colors, stored as hex values
|
||||||
configdialog.add_color(table, _('Start gradient/Main color'), 3,
|
configdialog.add_color(table, _('Start gradient/Main color'), 3,
|
||||||
'interface.color-start-grad', col=1)
|
'interface.color-start-grad', col=1)
|
||||||
|
Loading…
Reference in New Issue
Block a user