Added uistate to gramplet class; Finished Interactive Fan Chart: right-click gives menu options (same from PedigreeView)

svn: r11719
This commit is contained in:
Doug Blank 2009-01-25 17:29:59 +00:00
parent b2816502f4
commit 04b9ec770f
2 changed files with 319 additions and 34 deletions

View File

@ -219,6 +219,7 @@ class Gramplet(object):
self.gui = gui # plugin gramplet has link to gui
gui.pui = self # gui has link to plugin ui
self.dbstate = gui.dbstate
self.uistate = gui.uistate
self.init()
self.on_load()
self.build_options()
@ -400,7 +401,7 @@ class Gramplet(object):
from PluginUtils import make_gui_option
#tooltips, dbstate, uistate, track
widget, label = make_gui_option(option, None, self.dbstate,
self.gui.uistate,None)
self.uistate,None)
self.option_dict.update({option.get_label(): (widget, option)})
self.option_order.append(option.get_label())

View File

@ -1,5 +1,6 @@
# 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
@ -26,13 +27,6 @@
## Found by redwood:
## http://www.gramps-project.org/bugs/view.php?id=2611
## TODO:
## 1) add arrows to show rotation ability (click on background)
## 2) add center popup to pick center's children
## 3) perhaps right-click shows choice to edit, or make active, quick views,
## etc
## 4) add animations
#-------------------------------------------------------------------------
#
# Python modules
@ -44,6 +38,7 @@ import pango
import gtk
import math
from gtk import gdk
from cgi import escape
try:
import cairo
except ImportError:
@ -60,11 +55,25 @@ if gtk.pygtk_version < (2,3,93):
from BasicUtils import name_displayer
from gettext import gettext as _
from DataViews import Gramplet, register
from DataViews.PedigreeView import (find_children, find_parents,
find_witnessed_people, FormattingHelper)
import gen.lib
import Errors
from Editors import EditPerson, EditFamily
#-------------------------------------------------------------------------
#
# Functions
#
#-------------------------------------------------------------------------
def gender_code(is_male):
if is_male: return 1
return 0
"""
Given boolean is_male (means position in FanChart) return code.
"""
if is_male:
return gen.lib.Person.MALE
else:
return gen.lib.Person.FEMALE
#-------------------------------------------------------------------------
#
@ -91,16 +100,17 @@ class FanChartWidget(gtk.Widget):
NORMAL = 1
EXPANDED = 2
def __init__(self, generations, right_click_callback=None):
def __init__(self, generations, context_popup_callback=None):
"""
Highly experimental... documents forthcoming...
Fan Chart Widget. Handles visualization of data in self.data.
See main() of FanChartGramplet for example of model format.
"""
gtk.Widget.__init__(self)
self.last_x, self.last_y = None, None
self.connect("button_release_event", self.on_mouse_up)
self.connect("motion_notify_event", self.on_mouse_move)
self.connect("button-press-event", self.on_mouse_down)
self.right_click_callback = right_click_callback
self.context_popup_callback = context_popup_callback
self.add_events(gdk.BUTTON_PRESS_MASK |
gdk.BUTTON_RELEASE_MASK |
gdk.POINTER_MOTION_MASK)
@ -130,12 +140,13 @@ class FanChartWidget(gtk.Widget):
self.angle = {}
self.data = {}
for i in range(self.generations):
self.data[i] = [(None, None, None) for j in range(2 ** i)]
# name, person, parents?, children?
self.data[i] = [(None, None, None, None) for j in range(2 ** i)]
self.angle[i] = []
angle = 0
slice = 360.0 / (2 ** i)
gender = True
for a in range(len(self.data[i])):
for count in range(len(self.data[i])):
# start, stop, male, state
self.angle[i].append([angle, angle + slice,gender,self.NORMAL])
angle += slice
@ -208,13 +219,13 @@ class FanChartWidget(gtk.Widget):
cr.rotate(self.rotate_value * math.pi/180)
for generation in range(self.generations - 1, 0, -1):
for p in range(len(self.data[generation])):
(text, person, parents) = self.data[generation][p]
(text, person, parents, child) = self.data[generation][p]
if person:
start, stop, male, state = self.angle[generation][p]
if state in [self.NORMAL, self.EXPANDED]:
self.draw_person(cr, gender_code(male),
text, start, stop,
generation, state, parents)
generation, state, parents, child)
cr.set_source_rgb(1, 1, 1) # white
cr.move_to(0,0)
cr.arc(0, 0, self.center, 0, 2 * math.pi)
@ -224,20 +235,26 @@ class FanChartWidget(gtk.Widget):
cr.arc(0, 0, self.center, 0, 2 * math.pi)
cr.stroke()
# Draw center person:
(text, person, parents) = self.data[0][0]
(text, person, parents, child) = self.data[0][0]
cr.restore()
if person:
cr.save()
name = name_displayer.display(person)
self.draw_text(cr, name, self.center - 10, 95, 455)
cr.restore()
if child: # has at least one child
cr.set_source_rgb(0, 0, 0) # black
cr.move_to(0,0)
cr.arc(0, 0, 10, 0, 2 * math.pi)
cr.move_to(0,0)
cr.fill()
fontw, fonth = self.layout.get_pixel_size()
cr.move_to((w - fontw - 4), (h - fonth ))
cr.update_layout(self.layout)
cr.show_layout(self.layout)
def draw_person(self, cr, gender, name, start, stop, generation,
state, parents):
state, parents, child):
"""
Display the piece of pie for a given person. start and stop
are in degrees.
@ -485,11 +502,16 @@ class FanChartWidget(gtk.Widget):
selected = p
break
# Handle the click:
if selected == None: # clicked in open area
if selected == None: # clicked in open area, or center
if radius < self.center:
print "TODO: select child, spouse"
self.queue_draw()
return True
# right mouse
if event.button == 3 and self.context_popup_callback:
self.context_popup_callback(widget, event,
self.data[0][0][1].handle)
return True
else:
return False
# else, what to do on left click?
else:
# save the mouse location for movements
self.last_x, self.last_y = event.x, event.y
@ -498,9 +520,9 @@ class FanChartWidget(gtk.Widget):
if event.button == 1: # left mouse
self.change_slice(generation, selected)
elif event.button == 3: # right mouse
text, person, parents = self.data[generation][selected]
if person and self.right_click_callback:
self.right_click_callback(person)
text, person, parents, child = self.data[generation][selected]
if person and self.context_popup_callback:
self.context_popup_callback(widget, event, person.handle)
self.queue_draw()
return True
@ -509,10 +531,11 @@ class FanChartGramplet(Gramplet):
The Gramplet code that realizes the FanChartWidget.
"""
def init(self):
self.set_tooltip("Click to expand/contract person\nRight-click to make person active")
self.set_tooltip("Click to expand/contract person\nRight-click for options\nClick and drag in open area to rotate")
self.generations = 6
self.format_helper = FormattingHelper(self.dbstate)
self.gui.fan = FanChartWidget(self.generations,
right_click_callback=self.dbstate.change_active_person)
context_popup_callback=self.on_popup)
# Replace the standard textview with the fan chart widget:
self.gui.get_container_widget().remove(self.gui.textview)
self.gui.get_container_widget().add_with_viewport(self.gui.fan)
@ -536,6 +559,17 @@ class FanChartGramplet(Gramplet):
return m != None or f != 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, gender):
"""
Get the father if gender == "male", or get mother otherwise.
@ -566,11 +600,12 @@ class FanChartGramplet(Gramplet):
else:
name = name_displayer.display(person)
parents = self.have_parents(person)
self.gui.fan.data[0][0] = (name, person, parents)
child = self.have_children(person)
self.gui.fan.data[0][0] = (name, person, parents, child)
for current in range(1, self.generations):
parent = 0
# name, person, parents
for (n,p,q) in self.gui.fan.data[current - 1]:
# name, person, parents, children
for (n,p,q,c) in self.gui.fan.data[current - 1]:
# Get father's details:
person = self.get_parent(p, "male")
if person:
@ -581,7 +616,7 @@ class FanChartGramplet(Gramplet):
parents = self.have_parents(person)
else:
parents = None
self.gui.fan.data[current][parent] = (name, person, parents)
self.gui.fan.data[current][parent] = (name, person, parents, None)
if person is None:
# start,stop,male/right,state
self.gui.fan.angle[current][parent][3] = self.gui.fan.COLLAPSED
@ -592,14 +627,263 @@ class FanChartGramplet(Gramplet):
name = name_displayer.display(person)
else:
name = None
parents = self.have_parents(person)
self.gui.fan.data[current][parent] = (name, person, parents)
if current == self.generations - 1:
parents = self.have_parents(person)
else:
parents = None
self.gui.fan.data[current][parent] = (name, person, parents, None)
if person is None:
# start,stop,male/right,state
self.gui.fan.angle[current][parent][3] = self.gui.fan.COLLAPSED
parent += 1
self.gui.fan.queue_draw()
def on_childmenu_changed(self, obj,person_handle):
"""Callback for the pulldown menu selection, changing to the person
attached with menu item."""
self.dbstate.change_active_handle(person_handle)
return True
def edit_person_cb(self, obj,person_handle):
person = self.dbstate.db.get_person_from_handle(person_handle)
if person:
try:
EditPerson(self.dbstate, self.uistate, [], person)
except Errors.WindowActiveError:
pass
return True
return False
def copy_person_to_clipboard_cb(self, obj,person_handle):
"""Renders the person data into some lines of text and puts that into the clipboard"""
person = self.dbstate.db.get_person_from_handle(person_handle)
if person:
cb = gtk.clipboard_get(gtk.gdk.SELECTION_CLIPBOARD)
cb.set_text( self.format_helper.format_person(person,11))
return True
return False
def on_popup(self, obj, event, person_handle):
"""
Builds the full menu (including Siblings, Spouses, Children,
and Parents) with navigation. Copied from PedigreeView.
"""
menu = gtk.Menu()
menu.set_title(_('People Menu'))
person = self.dbstate.db.get_person_from_handle(person_handle)
if not person:
return 0
go_image = gtk.image_new_from_stock(gtk.STOCK_JUMP_TO,gtk.ICON_SIZE_MENU)
go_image.show()
go_item = gtk.ImageMenuItem(name_displayer.display(person))
go_item.set_image(go_image)
go_item.connect("activate",self.on_childmenu_changed,person_handle)
go_item.show()
menu.append(go_item)
edit_item = gtk.ImageMenuItem(gtk.STOCK_EDIT)
edit_item.connect("activate",self.edit_person_cb,person_handle)
edit_item.show()
menu.append(edit_item)
clipboard_item = gtk.ImageMenuItem(gtk.STOCK_COPY)
clipboard_item.connect("activate",self.copy_person_to_clipboard_cb,person_handle)
clipboard_item.show()
menu.append(clipboard_item)
# collect all spouses, parents and children
linked_persons = []
# Go over spouses and build their menu
item = gtk.MenuItem(_("Spouses"))
fam_list = person.get_family_handle_list()
no_spouses = 1
for fam_id in fam_list:
family = self.dbstate.db.get_family_from_handle(fam_id)
if family.get_father_handle() == person.get_handle():
sp_id = family.get_mother_handle()
else:
sp_id = family.get_father_handle()
spouse = self.dbstate.db.get_person_from_handle(sp_id)
if not spouse:
continue
if no_spouses:
no_spouses = 0
item.set_submenu(gtk.Menu())
sp_menu = item.get_submenu()
go_image = gtk.image_new_from_stock(gtk.STOCK_JUMP_TO,gtk.ICON_SIZE_MENU)
go_image.show()
sp_item = gtk.ImageMenuItem(name_displayer.display(spouse))
sp_item.set_image(go_image)
linked_persons.append(sp_id)
sp_item.connect("activate",self.on_childmenu_changed,sp_id)
sp_item.show()
sp_menu.append(sp_item)
if no_spouses:
item.set_sensitive(0)
item.show()
menu.append(item)
# Go over siblings and build their menu
item = gtk.MenuItem(_("Siblings"))
pfam_list = person.get_parent_family_handle_list()
no_siblings = 1
for f in pfam_list:
fam = self.dbstate.db.get_family_from_handle(f)
sib_list = fam.get_child_ref_list()
for sib_ref in sib_list:
sib_id = sib_ref.ref
if sib_id == person.get_handle():
continue
sib = self.dbstate.db.get_person_from_handle(sib_id)
if not sib:
continue
if no_siblings:
no_siblings = 0
item.set_submenu(gtk.Menu())
sib_menu = item.get_submenu()
if find_children(self.dbstate.db,sib):
label = gtk.Label('<b><i>%s</i></b>' % escape(name_displayer.display(sib)))
else:
label = gtk.Label(escape(name_displayer.display(sib)))
go_image = gtk.image_new_from_stock(gtk.STOCK_JUMP_TO,gtk.ICON_SIZE_MENU)
go_image.show()
sib_item = gtk.ImageMenuItem(None)
sib_item.set_image(go_image)
label.set_use_markup(True)
label.show()
label.set_alignment(0,0)
sib_item.add(label)
linked_persons.append(sib_id)
sib_item.connect("activate",self.on_childmenu_changed,sib_id)
sib_item.show()
sib_menu.append(sib_item)
if no_siblings:
item.set_sensitive(0)
item.show()
menu.append(item)
# Go over children and build their menu
item = gtk.MenuItem(_("Children"))
no_children = 1
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
if no_children:
no_children = 0
item.set_submenu(gtk.Menu())
child_menu = item.get_submenu()
if find_children(self.dbstate.db,child):
label = gtk.Label('<b><i>%s</i></b>' % escape(name_displayer.display(child)))
else:
label = gtk.Label(escape(name_displayer.display(child)))
go_image = gtk.image_new_from_stock(gtk.STOCK_JUMP_TO,gtk.ICON_SIZE_MENU)
go_image.show()
child_item = gtk.ImageMenuItem(None)
child_item.set_image(go_image)
label.set_use_markup(True)
label.show()
label.set_alignment(0,0)
child_item.add(label)
linked_persons.append(child_handle)
child_item.connect("activate",self.on_childmenu_changed,child_handle)
child_item.show()
child_menu.append(child_item)
if no_children:
item.set_sensitive(0)
item.show()
menu.append(item)
# Go over parents and build their menu
item = gtk.MenuItem(_("Parents"))
no_parents = 1
par_list = find_parents(self.dbstate.db,person)
for par_id in par_list:
par = self.dbstate.db.get_person_from_handle(par_id)
if not par:
continue
if no_parents:
no_parents = 0
item.set_submenu(gtk.Menu())
par_menu = item.get_submenu()
if find_parents(self.dbstate.db,par):
label = gtk.Label('<b><i>%s</i></b>' % escape(name_displayer.display(par)))
else:
label = gtk.Label(escape(name_displayer.display(par)))
go_image = gtk.image_new_from_stock(gtk.STOCK_JUMP_TO,gtk.ICON_SIZE_MENU)
go_image.show()
par_item = gtk.ImageMenuItem(None)
par_item.set_image(go_image)
label.set_use_markup(True)
label.show()
label.set_alignment(0,0)
par_item.add(label)
linked_persons.append(par_id)
par_item.connect("activate",self.on_childmenu_changed,par_id)
par_item.show()
par_menu.append(par_item)
if no_parents:
item.set_sensitive(0)
item.show()
menu.append(item)
# Go over parents and build their menu
item = gtk.MenuItem(_("Related"))
no_related = 1
for p_id in find_witnessed_people(self.dbstate.db,person):
#if p_id in linked_persons:
# continue # skip already listed family members
per = self.dbstate.db.get_person_from_handle(p_id)
if not per:
continue
if no_related:
no_related = 0
item.set_submenu(gtk.Menu())
per_menu = item.get_submenu()
label = gtk.Label(escape(name_displayer.display(per)))
go_image = gtk.image_new_from_stock(gtk.STOCK_JUMP_TO,gtk.ICON_SIZE_MENU)
go_image.show()
per_item = gtk.ImageMenuItem(None)
per_item.set_image(go_image)
label.set_use_markup(True)
label.show()
label.set_alignment(0,0)
per_item.add(label)
per_item.connect("activate",self.on_childmenu_changed,p_id)
per_item.show()
per_menu.append(per_item)
if no_related:
item.set_sensitive(0)
item.show()
menu.append(item)
menu.popup(None,None,None,event.button,event.time)
return 1
#-------------------------------------------------------------------------
#