Added uistate to gramplet class; Finished Interactive Fan Chart: right-click gives menu options (same from PedigreeView)
svn: r11719
This commit is contained in:
parent
b2816502f4
commit
04b9ec770f
@ -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())
|
||||
|
||||
|
@ -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
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
|
Loading…
Reference in New Issue
Block a user