Rearrange methods to get all of the fanchart business int the widget; fixed a bug in show_parents; general refining

svn: r11680
This commit is contained in:
Doug Blank 2009-01-22 00:36:26 +00:00
parent 48da3f90e3
commit 6815af9303

View File

@ -61,6 +61,7 @@ from BasicUtils import name_displayer
from Simple import SimpleAccess from Simple import SimpleAccess
from gettext import gettext as _ from gettext import gettext as _
from DataViews import Gramplet, register from DataViews import Gramplet, register
from gen.lib import Person
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
# #
@ -69,8 +70,7 @@ from DataViews import Gramplet, register
#------------------------------------------------------------------------- #-------------------------------------------------------------------------
class FanChartWidget(gtk.Widget): class FanChartWidget(gtk.Widget):
""" """
Widget to do most of the FanChart work. Some of this is currently in Interactive Fan Chart Widget.
the Gramplet (below) but should probably be moved here.
""" """
BORDER_WIDTH = 10 BORDER_WIDTH = 10
__gsignals__ = { 'realize': 'override', __gsignals__ = { 'realize': 'override',
@ -84,25 +84,31 @@ class FanChartWidget(gtk.Widget):
(183,219,197), (183,219,197),
(206,246,209)) (206,246,209))
def __init__(self, generations): COLLAPSED = 0
NORMAL = 1
EXPANDED = 2
def __init__(self, generations, right_click_callback=None):
""" """
Highly experimental... documents forthcoming... Highly experimental... documents forthcoming...
""" """
gtk.Widget.__init__(self) gtk.Widget.__init__(self)
self.connect("button_release_event", self.button_release) self.connect("button_release_event", self.button_release)
self.connect("motion_notify_event", self.motion_notify) self.connect("motion_notify_event", self.motion_notify)
self.connect("button-press-event", self.on_mouse_down)
self.right_click_callback = right_click_callback
self.add_events(gdk.BUTTON_PRESS_MASK | self.add_events(gdk.BUTTON_PRESS_MASK |
gdk.BUTTON_RELEASE_MASK | gdk.BUTTON_RELEASE_MASK |
gdk.POINTER_MOTION_MASK) gdk.POINTER_MOTION_MASK)
self.pixels_per_generation = 50 self.pixels_per_generation = 50 # size of radius for generation
## gotten from experiments with "sans serif 8": ## gotten from experiments with "sans serif 8":
self.degrees_per_radius = .80 self.degrees_per_radius = .80
## Other fonts will have different settings. Can you compute that ## Other fonts will have different settings. Can you compute that
## from the font size? I have no idea. ## from the font size? I have no idea.
self.generations = generations self.generations = generations
self.rotate_value = 90 # initially, put 1st gen male on right half self.rotate_value = 90 # degrees, initially, 1st gen male on right half
self.set_generations(self.generations) self.set_generations(self.generations)
self.center = 50 self.center = 50 # pixel radius of center
self.layout = self.create_pango_layout('cairo') self.layout = self.create_pango_layout('cairo')
self.layout.set_font_description(pango.FontDescription("sans serif 8")) self.layout.set_font_description(pango.FontDescription("sans serif 8"))
@ -135,7 +141,7 @@ class FanChartWidget(gtk.Widget):
gender = True gender = True
for a in range(len(self.data[i])): for a in range(len(self.data[i])):
# start, stop, male, state # start, stop, male, state
self.angle[i].append([angle, angle + slice, gender, 1]) self.angle[i].append([angle, angle + slice,gender,self.NORMAL])
angle += slice angle += slice
gender = not gender gender = not gender
@ -211,7 +217,7 @@ class FanChartWidget(gtk.Widget):
start, stop, male, state = self.angle[generation][p] start, stop, male, state = self.angle[generation][p]
name = name_displayer.display(person) name = name_displayer.display(person)
gender = person.get_gender() gender = person.get_gender()
if state > 0: if state in [self.NORMAL, self.EXPANDED]:
self.draw_person(cr, gender, name, start, stop, self.draw_person(cr, gender, name, start, stop,
generation, state) generation, state)
cr.set_source_rgb(1, 1, 1) # white cr.set_source_rgb(1, 1, 1) # white
@ -222,8 +228,8 @@ class FanChartWidget(gtk.Widget):
cr.set_source_rgb(0, 0, 0) # black cr.set_source_rgb(0, 0, 0) # black
cr.arc(0, 0, self.center, 0, 2 * math.pi) cr.arc(0, 0, self.center, 0, 2 * math.pi)
cr.stroke() cr.stroke()
person = self.data[0][0]
# Draw center person: # Draw center person:
person = self.data[0][0]
cr.restore() cr.restore()
if person: if person:
cr.save() cr.save()
@ -244,7 +250,7 @@ class FanChartWidget(gtk.Widget):
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.GENCOLOR[generation % len(self.GENCOLOR)] r,g,b = self.GENCOLOR[generation % len(self.GENCOLOR)]
if gender == 1: if gender == Person.MALE:
r -= r * .10 r -= r * .10
g -= g * .10 g -= g * .10
b -= b * .10 b -= b * .10
@ -259,9 +265,9 @@ class FanChartWidget(gtk.Widget):
cr.line_to(0, 0) cr.line_to(0, 0)
cr.arc(0, 0, radius, start_rad, stop_rad) cr.arc(0, 0, radius, start_rad, stop_rad)
cr.line_to(0, 0) cr.line_to(0, 0)
if state == 1: # normal if state == self.NORMAL: # normal
cr.set_line_width(1) cr.set_line_width(1)
else: # expanded else: # EXPANDED
cr.set_line_width(3) cr.set_line_width(3)
cr.stroke() cr.stroke()
self.draw_text(cr, name, radius - self.pixels_per_generation/2, self.draw_text(cr, name, radius - self.pixels_per_generation/2,
@ -291,7 +297,7 @@ class FanChartWidget(gtk.Widget):
text = self.text_limit(text, stop - start, radius - 15) text = self.text_limit(text, stop - start, radius - 15)
# center text: # center text:
# offset for cairo-font system is 90: # offset for cairo-font system is 90:
pos = start + ((stop - start) - self.text_degrees(text, radius))/2.0 + 90 pos = start + ((stop - start) - self.text_degrees(text,radius))/2.0 + 90
x, y, w, h = self.allocation x, y, w, h = self.allocation
cr.save() cr.save()
# Create a PangoLayout, set the font and text # Create a PangoLayout, set the font and text
@ -311,179 +317,195 @@ class FanChartWidget(gtk.Widget):
cr.restore() cr.restore()
cr.restore() cr.restore()
class FanChartGramplet(Gramplet):
"""
The Gramplet code that realizes the FanChartWidget. Some of this should be moved to
the widget itself.
"""
def init(self):
self.set_tooltip("Click to expand/contract person\nRight-click to make person active")
self.generations = 6
self.gui.fan = FanChartWidget(self.generations)
self.gui.get_container_widget().remove(self.gui.textview)
self.gui.get_container_widget().add_with_viewport(self.gui.fan)
#container is a gtk.ScrolledWindow
container = self.gui.get_container_widget()
container.connect("button-press-event", self.on_mouse_down)
self.gui.fan.show()
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
start,stop,male,state = self.gui.fan.angle[generation][selected] start,stop,male,state = self.angle[generation][selected]
if state > 0: if state in [self.NORMAL, self.EXPANDED]:
slice = (stop - start) * 2.0 slice = (stop - start) * 2.0
self.gui.fan.angle[generation][selected] = [current,current+slice,male,state] self.angle[generation][selected] = [current,current+slice,
male,state]
self.expand_parents(generation + 1, selected, current) self.expand_parents(generation + 1, selected, current)
current += slice current += slice
start,stop,male,state = self.gui.fan.angle[generation][selected+1] start,stop,male,state = self.angle[generation][selected+1]
if state > 0: if state in [self.NORMAL, self.EXPANDED]:
slice = (stop - start) * 2.0 slice = (stop - start) * 2.0
self.gui.fan.angle[generation][selected+1] = [current,current+slice,male,state] self.angle[generation][selected+1] = [current,current+slice,
male,state]
self.expand_parents(generation + 1, selected+1, current) self.expand_parents(generation + 1, selected+1, current)
def show_parents(self, generation, selected, angle): def show_parents(self, generation, selected, angle, slice):
if generation >= self.generations: return if generation >= self.generations: return
slice = 360.0 / (2 ** generation)
selected = selected * 2 selected = selected * 2
self.gui.fan.angle[generation][selected][0] = angle self.angle[generation][selected][0] = angle
self.gui.fan.angle[generation][selected][1] = angle + slice self.angle[generation][selected][1] = angle + slice
self.gui.fan.angle[generation][selected][3] = 1 self.angle[generation][selected][3] = self.NORMAL
self.show_parents(generation+1, selected, angle) self.show_parents(generation+1, selected, angle, slice/2.0)
self.gui.fan.angle[generation][selected+1][0] = angle + slice self.angle[generation][selected+1][0] = angle + slice
self.gui.fan.angle[generation][selected+1][1] = angle + slice + slice self.angle[generation][selected+1][1] = angle + slice + slice
self.gui.fan.angle[generation][selected+1][3] = 1 self.angle[generation][selected+1][3] = self.NORMAL
self.show_parents(generation+1, selected + 1, angle + slice) self.show_parents(generation+1, selected + 1, angle + slice, slice/2.0)
def hide_parents(self, generation, selected, angle): def hide_parents(self, generation, selected, angle):
if generation >= self.generations: return if generation >= self.generations: return
selected = 2 * selected selected = 2 * selected
self.gui.fan.angle[generation][selected][0] = angle self.angle[generation][selected][0] = angle
self.gui.fan.angle[generation][selected][1] = angle self.angle[generation][selected][1] = angle
self.gui.fan.angle[generation][selected][3] = 0 self.angle[generation][selected][3] = self.COLLAPSED
self.hide_parents(generation + 1, selected, angle) self.hide_parents(generation + 1, selected, angle)
self.gui.fan.angle[generation][selected+1][0] = angle self.angle[generation][selected+1][0] = angle
self.gui.fan.angle[generation][selected+1][1] = angle self.angle[generation][selected+1][1] = angle
self.gui.fan.angle[generation][selected+1][3] = 0 self.angle[generation][selected+1][3] = self.COLLAPSED
self.hide_parents(generation + 1, selected+1, angle) self.hide_parents(generation + 1, selected+1, angle)
def shrink_parents(self, generation, selected, current): def shrink_parents(self, generation, selected, current):
if generation >= self.generations: return if generation >= self.generations: return
selected = 2 * selected selected = 2 * selected
start,stop,male,state = self.gui.fan.angle[generation][selected] start,stop,male,state = self.angle[generation][selected]
if state > 0: if state in [self.NORMAL, self.EXPANDED]:
slice = (stop - start) / 2.0 slice = (stop - start) / 2.0
self.gui.fan.angle[generation][selected] = [current, current + slice, male,state] self.angle[generation][selected] = [current, current + slice,
male,state]
self.shrink_parents(generation + 1, selected, current) self.shrink_parents(generation + 1, selected, current)
current = current + slice current = current + slice
start,stop,male,state = self.gui.fan.angle[generation][selected+1] start,stop,male,state = self.angle[generation][selected+1]
if state > 0: if state in [self.NORMAL, self.EXPANDED]:
slice = (stop - start) / 2.0 slice = (stop - start) / 2.0
self.gui.fan.angle[generation][selected+1] = [current,current+slice,male,state] self.angle[generation][selected+1] = [current,current+slice,
male,state]
self.shrink_parents(generation + 1, selected+1, current) self.shrink_parents(generation + 1, selected+1, current)
def change_slice(self, generation, selected): def change_slice(self, generation, selected):
gstart, gstop, gmale, gstate = self.gui.fan.angle[generation][selected] gstart, gstop, gmale, gstate = self.angle[generation][selected]
if gstate == 1: # normal, let's expand if gstate == self.NORMAL: # let's expand
if gmale: if gmale:
# go to right # go to right
stop = gstop + (gstop - gstart) stop = gstop + (gstop - gstart)
self.gui.fan.angle[generation][selected] = [gstart,stop,gmale,2] self.angle[generation][selected] = [gstart,stop,gmale,
self.EXPANDED]
self.expand_parents(generation + 1, selected, gstart) self.expand_parents(generation + 1, selected, gstart)
start,stop,male,state = self.gui.fan.angle[generation][selected+1] start,stop,male,state = self.angle[generation][selected+1]
self.gui.fan.angle[generation][selected+1] = [stop,stop,male,0] self.angle[generation][selected+1] = [stop,stop,male,
self.COLLAPSED]
self.hide_parents(generation+1, selected+1, stop) self.hide_parents(generation+1, selected+1, stop)
else: else:
# go to left # go to left
start = gstart - (gstop - gstart) start = gstart - (gstop - gstart)
self.gui.fan.angle[generation][selected] = [start,gstop,gmale,2] self.angle[generation][selected] = [start,gstop,gmale,
self.EXPANDED]
self.expand_parents(generation + 1, selected, start) self.expand_parents(generation + 1, selected, start)
start,stop,male,state = self.gui.fan.angle[generation][selected-1] start,stop,male,state = self.angle[generation][selected-1]
self.gui.fan.angle[generation][selected-1] = [start,start,male,0] self.angle[generation][selected-1] = [start,start,male,
self.COLLAPSED]
self.hide_parents(generation+1, selected-1, start) self.hide_parents(generation+1, selected-1, start)
elif gstate == 2: # expanded, let's shrink elif gstate == self.EXPANDED: # let's shrink
if gmale: if gmale:
# shrink from right # shrink from right
slice = (gstop - gstart)/2.0 slice = (gstop - gstart)/2.0
stop = gstop - slice stop = gstop - slice
self.gui.fan.angle[generation][selected] = [gstart,stop,gmale,1] self.angle[generation][selected] = [gstart,stop,gmale,
self.NORMAL]
self.shrink_parents(generation+1, selected, gstart) self.shrink_parents(generation+1, selected, gstart)
start,stop,male,state = self.gui.fan.angle[generation][selected+1] self.angle[generation][selected+1][0] = stop # start
self.gui.fan.angle[generation][selected+1] = [stop-slice,stop,male,1] self.angle[generation][selected+1][1] = stop + slice # stop
self.show_parents(generation+1, selected+1, stop-slice) self.angle[generation][selected+1][3] = self.NORMAL
self.show_parents(generation+1, selected+1, stop, slice/2.0)
else: else:
# shrink from left # shrink from left
slice = (gstop - gstart)/2.0 slice = (gstop - gstart)/2.0
start = gstop - slice start = gstop - slice
self.gui.fan.angle[generation][selected] = [start,gstop,gmale,1] self.angle[generation][selected] = [start,gstop,gmale,
self.NORMAL]
self.shrink_parents(generation+1, selected, start) self.shrink_parents(generation+1, selected, start)
start,stop,male,state = self.gui.fan.angle[generation][selected-1] start,stop,male,state = self.angle[generation][selected-1]
self.gui.fan.angle[generation][selected-1] = [start,start+slice,male,1] self.angle[generation][selected-1] = [start,start+slice,male,
self.show_parents(generation+1, selected-1, start) self.NORMAL]
self.show_parents(generation+1, selected-1, start, slice/2.0)
def on_mouse_down(self, widget, e): def on_mouse_down(self, widget, e):
# compute angle, radius, find out who would be there (rotated) # compute angle, radius, find out who would be there (rotated)
x, y, w, h = self.gui.fan.allocation x, y, w, h = self.allocation
cx = w/2 cx = w/2
cy = h/2 cy = h/2
radius = math.sqrt((e.x - cx) ** 2 + (e.y - cy) ** 2) radius = math.sqrt((e.x - cx) ** 2 + (e.y - cy) ** 2)
if radius < self.gui.fan.center: if radius < self.center:
generation = 0 generation = 0
else: else:
generation = int((radius - self.gui.fan.center) / generation = int((radius - self.center) /
self.gui.fan.pixels_per_generation) + 1 self.pixels_per_generation) + 1
rads = math.atan2( (e.y - cy), (e.x - cx) ) rads = math.atan2( (e.y - cy), (e.x - cx) )
if rads < 0: if rads < 0: # second half of unit circle
rads = math.pi + (math.pi + rads) rads = math.pi + (math.pi + rads)
pos = ((rads/(math.pi * 2) - self.gui.fan.rotate_value/360.) * 360.0) % 360 pos = ((rads/(math.pi * 2) - self.rotate_value/360.) * 360.0) % 360
# find what person is in this position: # find what person is in this position:
selected = None selected = None
if (0 < generation < self.generations): if (0 < generation < self.generations):
for p in range(len(self.gui.fan.angle[generation])): for p in range(len(self.angle[generation])):
start, stop, male, state = self.gui.fan.angle[generation][p] start, stop, male, state = self.angle[generation][p]
if state == 0: continue if state == self.COLLAPSED: continue
if start <= pos <= stop: if start <= pos <= stop:
selected = p selected = p
break break
if selected == None: if selected == None:
if radius < self.gui.fan.center: if radius < self.center:
print "TODO: select children" print "TODO: select children"
pass # TODO: select children
elif e.x > cx: # on right elif e.x > cx: # on right
if e.y > cy: # bottom right, rotate clockwise if e.y > cy: # bottom right, rotate clockwise
self.gui.fan.rotate_value += 45.0 self.rotate_value += 45.0
else: # top right, rotate counter clockwise else: # top right, rotate counter clockwise
self.gui.fan.rotate_value -= 45.0 self.rotate_value -= 45.0
else: # on left else: # on left
if e.y > cy: # bottom left, rotate counter clockwise if e.y > cy: # bottom left, rotate counter clockwise
self.gui.fan.rotate_value -= 45.0 self.rotate_value -= 45.0
else: # top left, rotate clockwise else: # top left, rotate clockwise
self.gui.fan.rotate_value += 45.0 self.rotate_value += 45.0
self.gui.fan.rotate_value %= 360 self.rotate_value %= 360
self.gui.fan.queue_draw() self.queue_draw()
return True return True
# Do things based on state, e.state, or button, e.button # Do things based on state, e.state, or button, e.button
if e.button == 1: # left mouse if e.button == 1: # left mouse
self.change_slice(generation, selected) self.change_slice(generation, selected)
elif e.button == 3: # right mouse elif e.button == 3: # right mouse
person = self.gui.fan.data[generation][selected] person = self.data[generation][selected]
if person: if person and self.right_click_callback:
name = name_displayer.display(person) self.right_click_callback(person)
self.dbstate.change_active_person(person) self.queue_draw()
self.gui.fan.queue_draw()
return True return True
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.generations = 6
self.gui.fan = FanChartWidget(self.generations,
right_click_callback=self.dbstate.change_active_person)
# 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)
# Make sure it is visible:
self.gui.fan.show()
def db_changed(self): def db_changed(self):
"""
Method called when database changes.
"""
# reset the db connection
self.sa = SimpleAccess(self.dbstate.db) self.sa = SimpleAccess(self.dbstate.db)
def active_changed(self, handle): def active_changed(self, handle):
"""
Method called when active person changes.
"""
# Reset everything but rotation angle (leave it as is) # Reset everything but rotation angle (leave it as is)
self.gui.fan.reset_generations() self.gui.fan.reset_generations()
self.update() self.update()
def main(self): def main(self):
""" """
Fill the data structures with the active data. This initializes all data. Fill the data structures with the active data. This initializes all
data.
""" """
self.gui.fan.data[0][0] = self.dbstate.get_active_person() self.gui.fan.data[0][0] = self.dbstate.get_active_person()
for current in range(1, self.generations): for current in range(1, self.generations):
@ -491,11 +513,13 @@ class FanChartGramplet(Gramplet):
for p in self.gui.fan.data[current - 1]: for p in self.gui.fan.data[current - 1]:
self.gui.fan.data[current][parent] = self.sa.father(p) self.gui.fan.data[current][parent] = self.sa.father(p)
if self.gui.fan.data[current][parent] is None: if self.gui.fan.data[current][parent] is None:
self.gui.fan.angle[current][parent][3] = 0 # start,stop,g,state # start,stop,male/right,state
self.gui.fan.angle[current][parent][3] = self.gui.fan.COLLAPSED
parent += 1 parent += 1
self.gui.fan.data[current][parent] = self.sa.mother(p) self.gui.fan.data[current][parent] = self.sa.mother(p)
if self.gui.fan.data[current][parent] is None: if self.gui.fan.data[current][parent] is None:
self.gui.fan.angle[current][parent][3] = 0 # start,stop,g,state # start,stop,male/left,state
self.gui.fan.angle[current][parent][3] = self.gui.fan.COLLAPSED
parent += 1 parent += 1
self.gui.fan.queue_draw() self.gui.fan.queue_draw()