gramps/src/PedView.py

378 lines
14 KiB
Python
Raw Normal View History

#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2001 Donald N. Allingham
#
# 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
#
_PAD = 3
_CANVASPAD = 20
_PERSON = "p"
import GTK
import GDK
import gtk
import Config
from intl import gettext
_ = gettext
#-------------------------------------------------------------------------
#
#
#
#-------------------------------------------------------------------------
class PedigreeView:
def __init__(self,canvas,update,status_bar,change_active,lp):
self.canvas = canvas
self.canvas_items = []
self.root = self.canvas.root()
self.active_person = None
self.x1 = 0
self.x2 = 0
self.y1 = 0
self.y2 = 0
self.update = update
self.sb = status_bar
self.change_active_person = change_active
self.load_person = lp
def load_canvas(self,person):
"""Redraws the pedigree view window, using the passed person
as the root person of the tree."""
for i in self.canvas_items:
i.destroy()
self.active_person = person
if person == None:
return
h = 0
w = 0
x1,y1,x2,y2 = self.canvas.get_allocation()
self.canvas.set_scroll_region(x1,y1,x2,y2)
style = self.canvas['style']
font = style.font
list = [None]*31
self.find_tree(self.active_person,0,1,list)
# determine the largest string width and height for calcuation
# of box sizes.
for t in list:
if t:
n = t[0].getPrimaryName().getName()
h = max(h,font.height(n)+2*_PAD)
w = max(w,font.width(n)+2*_PAD)
w = max(w,font.width("d. %s" % t[0].getDeath().getDate())+2*_PAD)
w = max(w,font.width("b. %s" % t[0].getBirth().getDate())+2*_PAD)
cpad = max(h+4,_CANVASPAD)
cw = (x2-x1-(2*cpad))
ch = (y2-y1-(2*cpad))
if 5*w < cw and 24*h < ch:
gen = 31
xdiv = 5.0
elif 4*w < cw and 12*h < ch:
gen = 15
xdiv = 4.0
else:
gen = 7
xdiv = 3.0
xpts = self.build_x_coords(cw/xdiv,cpad)
ypts = self.build_y_coords(ch/32.0)
for family in self.active_person.getFamilyList():
if len(family.getChildList()) > 0:
button,arrow = self.make_arrow_button(GTK.ARROW_LEFT,self.on_show_child_menu)
item = self.root.add("widget", widget=button,
x=x1, y=ypts[0]+(h/2.0),
height=h, width=h,
size_pixels=1, anchor=GTK.ANCHOR_WEST)
self.canvas_items = [item, button, arrow]
break
else:
self.canvas_items = []
if list[1]:
p = list[1]
self.add_parent_button(p[0],x2-_PAD,ypts[1],h)
if list[2]:
p = list[2]
self.add_parent_button(p[0],x2-_PAD,ypts[2],h)
for i in range(gen):
if list[i]:
if i < int(gen/2):
findex = (2*i)+1
mindex = findex+1
if list[findex]:
p = list[findex]
self.draw_canvas_line(xpts[i], ypts[i], xpts[findex],
ypts[findex], h, w, p[0], style, p[1])
if list[mindex]:
p = list[mindex]
self.draw_canvas_line(xpts[i],ypts[i], xpts[mindex],
ypts[mindex], h, w, p[0], style, p[1])
p = list[i]
self.add_box(xpts[i],ypts[i],w,h,p[0],style)
def make_arrow_button(self,direction,function):
"""Make a button containing an arrow with the attached callback"""
arrow = gtk.GtkArrow(at=direction)
button = gtk.GtkButton()
button.add(arrow)
button.connect("clicked",function)
arrow.show()
button.show()
return (button, arrow)
def on_show_child_menu(self,obj):
"""Build and display the menu attached to the left pointing arrow
button. The menu consists of the children of the current root
person of the tree. Attach a child to each menu item."""
if self.active_person:
myMenu = gtk.GtkMenu()
for family in self.active_person.getFamilyList():
for child in family.getChildList():
menuitem = gtk.GtkMenuItem(Config.nameof(child))
myMenu.append(menuitem)
menuitem.set_data(_PERSON,child)
menuitem.connect("activate",self.on_childmenu_changed)
menuitem.show()
myMenu.popup(None,None,None,0,0)
return 1
def on_childmenu_changed(self,obj):
"""Callback for the pulldown menu selection, changing to the person
attached with menu item."""
person = obj.get_data(_PERSON)
if person:
self.load_canvas(person)
return 1
def add_parent_button(self,parent,x,y,h):
"""Add a button with a right pointing button on the main group at
the specified location. Attach the passed parent and the callback
to the button."""
button,arrow = self.make_arrow_button(GTK.ARROW_RIGHT,self.change_to_parent)
button.set_data(_PERSON,parent)
item = self.root.add("widget", widget=button, x=x, y=y+(h/2),
height=h, width=h, size_pixels=1,
anchor=GTK.ANCHOR_EAST)
self.canvas_items.append(arrow)
self.canvas_items.append(item)
self.canvas_items.append(button)
def change_to_parent(self,obj):
"""Callback to right pointing arrow button. Gets the person
attached to the button and change the root person to that
person, redrawing the view."""
self.load_canvas(obj.get_data(_PERSON))
def draw_canvas_line(self,x1,y1,x2,y2,h,w,data,style,ls):
"""Draw an two segment line between the x,y point pairs. Attach
a event callback and data to the line."""
startx = x1+(w/2.0)
pts = [startx,y1, startx,y2+(h/2.0), x2,y2+(h/2.0)]
item = self.root.add("line", width_pixels=2,
points=pts, line_style=ls,
fill_color_gdk=style.black)
item.set_data(_PERSON,data)
item.connect("event",self.line_event)
self.canvas_items.append(item)
def build_x_coords(self,x,cpad):
"""Build the array of x coordinates for the possible positions
on the pedegree view."""
return [cpad] + [x+cpad]*2 + [x*2+cpad]*4 + \
[x*3+cpad]*8 + [x*4+cpad]*16
def build_y_coords(self,y):
"""Build the array of y coordinates for the possible positions
on the pedegree view."""
return [ y*16, y*8, y*24, y*4, y*12, y*20, y*28, y*2,
y*6, y*10, y*14, y*18, y*22, y*26, y*30, y,
y*3, y*5, y*7, y*9, y*11, y*13, y*15, y*17,
y*19, y*21, y*23, y*25, y*27, y*29, y*31]
def add_box(self,x,y,bwidth,bheight,person,style):
"""Draw a box of the specified size at the specified location.
The box consists of a shadow box for effect, the real box
that contains the information, and the basic text
information. For convience, the all the subelements are
grouped into a GNOME canvas group."""
shadow = _PAD
xpad = _PAD
name = person.getPrimaryName().getName()
group = self.root.add("group",x=x,y=y)
self.canvas_items.append(group)
# draw the shadow box
item = group.add("rect", x1=shadow, y1=shadow,
x2=bwidth+shadow, y2=bheight+shadow,
outline_color_gdk=style.dark[GTK.STATE_NORMAL],
fill_color_gdk=style.dark[GTK.STATE_NORMAL])
self.canvas_items.append(item)
# draw the real box
item = group.add("rect", x1=0, y1=0, x2=bwidth, y2=bheight,
outline_color_gdk=style.bg[GTK.STATE_NORMAL],
fill_color_gdk=style.white)
self.canvas_items.append(item)
# Write the text
item = group.add("text", x=xpad, y=bheight/2.0, text=name,
fill_color_gdk=style.text[GTK.STATE_NORMAL],
font_gdk=style.font, anchor=GTK.ANCHOR_WEST)
self.canvas_items.append(item)
group.connect('event',self.box_event)
group.set_data('p',person)
def box_event(self,obj,event):
"""Handle events over a drawn box. Doubleclick would edit,
shift doubleclick would change the active person, entering
the box expands it to display more information, leaving a
box returns it to the original size and information"""
if event.type == GDK._2BUTTON_PRESS:
if event.button == 1:
person = obj.get_data(_PERSON)
if (event.state & GDK.SHIFT_MASK) or (event.state & GDK.CONTROL_MASK):
self.change_active_person(person)
self.load_canvas(person)
else:
self.load_person(person)
return 1
elif event.type == GDK.ENTER_NOTIFY:
self.expand_box(obj)
elif event.type == GDK.LEAVE_NOTIFY:
self.shrink_box(obj)
def shrink_box(self,obj):
"""Shrink an exanded box back down to normal size"""
ch = obj.children()
length = len(ch)
if length <= 3:
return 1
box = obj.children()[1]
x,y,w,h = box.get_bounds()
box.set(x1=x,y1=y,x2=w,y2=h/3)
box2 = obj.children()[0]
x,y,w,h1 = box2.get_bounds()
box2.set(x1=x,y1=y,x2=w,y2=(h/3)+_PAD)
if length > 4:
ch[4].destroy()
if length > 3:
ch[3].destroy()
self.update()
self.canvas.update_now()
def expand_box(self,obj):
"""Expand a box to include additional information"""
obj.raise_to_top()
box = obj.children()[1]
x,y,w,h = box.get_bounds()
box.set(x1=x,y1=y,x2=w,y2=h*3)
box2 = obj.children()[0]
x,y,w,h1 = box2.get_bounds()
box2.set(x1=x,y1=y,x2=w,y2=(3*h)+_PAD)
person = obj.get_data('p')
font = self.canvas['style'].font
color = self.canvas['style'].text[GTK.STATE_NORMAL]
obj.add("text", font_gdk=font, fill_color_gdk=color,
text="b. %s" % person.getBirth().getDate(),
anchor=GTK.ANCHOR_WEST, x=_PAD, y=h+(h/2))
obj.add("text", font_gdk=font, fill_color_gdk=color,
text="d. %s" % person.getDeath().getDate(),
anchor=GTK.ANCHOR_WEST, x=_PAD, y=2*h+(h/2))
msg = _("Doubleclick to edit, Shift-Doubleclick to make the active person")
self.sb.set_status(msg)
def line_event(self,obj,event):
"""Catch X events over a line and respond to the ones we care about"""
person = obj.get_data(_PERSON)
style = self.canvas['style']
if event.type == GDK._2BUTTON_PRESS:
if event.button == 1 and event.type == GDK._2BUTTON_PRESS:
self.load_canvas(person)
elif event.type == GDK.ENTER_NOTIFY:
obj.set(fill_color_gdk=style.bg[GTK.STATE_SELECTED],
width_pixels=4)
name = Config.nameof(person)
msg = _("Double clicking will make %s the active person") % name
self.sb.set_status(msg)
elif event.type == GDK.LEAVE_NOTIFY:
obj.set(fill_color_gdk=style.black, width_pixels=2)
self.update()
def on_canvas1_event(self,obj,event):
"""Handle resize events over the canvas, redrawing if the size changes"""
if event.type == GDK.EXPOSE:
x1,y1,x2,y2 = self.canvas.get_allocation()
if self.x1 != x1 or self.x2 != x2 or \
self.y1 != y1 or self.y2 != y2:
self.x1 = x1; self.x2 = x2
self.y1 = y1; self.y2 = y2
self.load_canvas(self.active_person)
return 0
def find_tree(self,person,index,depth,list,val=0):
"""Recursively build a list of ancestors"""
if depth > 5 or person == None:
return
family = person.getMainFamily()
frel = 0
mrel = 0
if family == None:
l = person.getAltFamilyList()
if len(l) > 0:
(family,m,f) = l[0]
mrel = (m != "Birth")
frel = (f != "Birth")
list[index] = (person,val)
if family != None:
father = family.getFather()
if father != None:
self.find_tree(father,(2*index)+1,depth+1,list,frel)
mother = family.getMother()
if mother != None:
self.find_tree(mother,(2*index)+2,depth+1,list,mrel)