Relationship graph plugin

svn: r303
This commit is contained in:
Don Allingham 2001-08-07 13:35:26 +00:00
parent 21f52372d5
commit 8889385911

394
gramps/src/plugins/Graph.py Normal file
View File

@ -0,0 +1,394 @@
#
# Graph.py - a graphical user interface for gramps
#
# Copyright (C) 2001 Jesper Zedlitz
#
# 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
#
"Graph/Graph"
from RelLib import *
import os
import posixpath
import re
import sort
import string
import utils
import intl
_ = intl.gettext
from gtk import *
from gnome.ui import *
from libglade import *
pixmap = None
sizeX = 20
sizeY = 20
boxes = []
lines = []
treffer = -1
distX = 0
distY = 0
spaceX = 10
spaceY = 40
popup_win = None
popped_up = FALSE
label = None
db = None
select = FALSE
old_selection= (0,0,0,0)
selected_boxes = {}
red_gc = None
lightgreen_gc = None
select_gc = None
sorted = {}
done = {}
#
# This function is called recursivly to determin the generation a person belongs to
# If nothing is know about the person (i.e. first call to this function) generation #0 is used.
# First the ancestors of a person are followed. If there are no more ancestors (or already visited = entry in
# map "done") the decendances (if any) are traced.
# The function returns imediately if the person has already been visited -> stop of recursion
#
def calc_gen( person, gen):
global sorted, done
id = person.getId()
if done.has_key(id):
# stop the recursion
return
sorted[ id ] = gen
done[ id ] = TRUE
# going into the past...
family = person.getMainFamily()
if family != None:
father = family.getFather()
mother = family.getMother()
if( person!=father and person!=mother):
# person is a child of this family
if father != None:
calc_gen( father, gen-1 )
if mother != None:
calc_gen( mother, gen-1 )
# do I need getFamilyList() when I want to find the parents ?
for family in person.getFamilyList():
father = family.getFather()
mother = family.getMother()
if( person!=father and person!=mother):
# person is a child of this family
if father != None:
calc_gen( father, gen-1 )
if mother != None:
calc_gen( mother, gen-1 )
# going into the future...
for family in person.getFamilyList():
father = family.getFather()
mother = family.getMother()
if( person==father or person==mother):
# person is a parent of this family
if person==father:
if mother != None:
calc_gen( mother, gen )
if person==mother:
if father != None:
calc_gen( father, gen )
for child in family.getChildList():
calc_gen( child, gen+1 )
def report(database,person):
global sorted, done
sorted = {}
done = {}
global db
db = database
global boxes
boxes = []
global lines
lines = []
winSizeX = 400
winSizeY = 400
global selected_boxes
selected_boxes = {}
personList = database.getPersonMap()
# loop over all persons in the database
# usually a lot of persons will the in "done" after the first call of "calc_gen", but if there are
# disjunct parts in the database (or completely seperated people) we have to check everyone
for id in personList.keys():
if not done.has_key( id ):
calc_gen( personList[id], 0)
# don't want to have negative generation numbers, so have to substract the lowest (negative) number
mini = min( sorted.values() )
# position the boxes
# this can be done much better - i.e. children should be put close to their parents...
length = {}
for id in sorted.keys():
y = ( sorted[id]-mini) * (sizeY +spaceY) + 10
x = length.get( sorted[id]-mini, 0 ) + spaceX
length[ sorted[id]-mini ] = x + sizeX
if personList.has_key( id ):
pos = personList[id].getPosition()
if pos != None:
boxes.append( (pos[0], pos[1], (personList[id].getGender() == Person.female), id ) )
else:
boxes.append( (x, y, (personList[id].getGender() == Person.female), id ) )
personList[id].setPosition( (x,y) )
utils.modified()
else:
print "just lost person with key %s" % (id)
# add lines between children and parents
for i in range( len(boxes) ):
b = boxes[i]
id = b[3]
person = personList[id]
family = person.getMainFamily()
if family != None:
father = family.getFather()
f=""
m=""
if father!=None:
f = father.getId()
mother = family.getMother()
if mother!=None:
m = mother.getId()
for j in range( len(boxes) ):
# does this box contain the id of the father or the mother?
if boxes[j][3] == f or boxes[j][3] == m:
lines.append( (j, i) )
# logic is done - now the graphic
win = GtkWindow()
win.set_name("Test Input")
win.set_border_width(5)
vbox = GtkVBox(spacing=3)
win.add(vbox)
vbox.show()
drawing_area = GtkDrawingArea()
drawing_area.size(winSizeX, winSizeY)
vbox.pack_start(drawing_area)
drawing_area.show()
drawing_area.connect("expose_event", expose_event)
drawing_area.connect("configure_event", configure_event)
drawing_area.connect("button_press_event", button_press_event)
drawing_area.connect("button_release_event", button_release_event)
drawing_area.connect("motion_notify_event", motion_notify_event)
drawing_area.set_events(GDK.EXPOSURE_MASK |
GDK.LEAVE_NOTIFY_MASK |
GDK.BUTTON_PRESS_MASK |
GDK.BUTTON_RELEASE_MASK |
GDK.POINTER_MOTION_MASK |
GDK.POINTER_MOTION_HINT_MASK)
button = GtkButton("Quit")
hbox = GtkHBox(spacing=3)
vbox.pack_start(button, expand=FALSE, fill=FALSE)
button.connect("clicked", win.destroy)
button.show()
win.show()
drawing_area.get_window().set_cursor(cursor_new(132))
def redraw_tree( widget, area = None ):
global pixmap
global red_gc, select_gc, lightgreen_gc
if area == None:
draw_rectangle(pixmap, widget.get_style().white_gc, TRUE, 0, 0, widget.get_window().width, widget.get_window().height)
else:
draw_rectangle(pixmap, widget.get_style().white_gc, TRUE, area[0], area[1], area[2], area[3])
for i in range( len( boxes) ):
b = boxes[i]
if selected_boxes.has_key(i):
draw_rectangle(pixmap, lightgreen_gc, TRUE, b[0], b[1], sizeX, sizeY)
draw_rectangle(pixmap, widget.get_style().black_gc, FALSE, b[0], b[1], sizeX, sizeY)
for l in lines:
p1 = boxes[l[0]]
p2 = boxes[l[1]]
draw_line( pixmap, red_gc, p1[0]+sizeX/2, p1[1]+sizeY, p2[0]+sizeX/2, p2[1] )
draw_rectangle(pixmap, select_gc, FALSE, old_selection[0], old_selection[1], old_selection[2], old_selection[3])
widget.queue_draw()
def configure_event(widget, event):
global pixmap
global red_gc, select_gc, lightgreen_gc
win = widget.get_window()
pixmap = create_pixmap(win, win.width, win.height, -1)
cm = widget.get_style().colormap
if( red_gc == None ):
red_gc = win.new_gc()
red_gc.foreground = cm.alloc( 60000,0,0)
if( lightgreen_gc == None ):
lightgreen_gc = win.new_gc()
lightgreen_gc.foreground = cm.alloc( 0,60000,0)
if( select_gc == None ):
select_gc = win.new_gc()
select_gc.foreground = cm.alloc( 10000,10000,10000)
select_gc.line_style = 1
redraw_tree( widget )
return TRUE
def expose_event(widget, event):
area = event.area
gc = widget.get_style().fg_gc[STATE_NORMAL]
widget.draw_pixmap(gc, pixmap, area[0], area[1], area[0], area[1],
area[2], area[3])
return FALSE
#
# close the popup-window for one person
# it's not nice that the window closes imediately after leaving the window - a timer should be added here
#
def popdown_cb(widget, event):
global popped_up
global popup_win
widget.hide()
popped_up = FALSE
return FALSE
def button_press_event(widget, event):
global treffer
global distY, distX
global popped_up, popup_win, label
global selX, selY, select, old_selection, selected_boxes
state = event.window.pointer_state
# which box has been hit?
i = 0
for b in boxes:
if( b[0]<=event.x<=b[0]+sizeX and b[1]<=event.y<=b[1]+sizeY):
treffer=i
distX = event.x - b[0]
distY = event.y - b[1]
i=i+1
# left mouse button -> drag 'n drop
if (treffer > -1) and (state & GDK.BUTTON1_MASK):
if not selected_boxes.has_key(treffer):
old_selection = (0,0,0,0)
selected_boxes = { }
selected_boxes[treffer] = TRUE
widget.get_window().set_cursor(cursor_new(58))
# add a box to the current selection
# this should be changes to work with BUTTON1 + CTRL
if treffer>-1 and (state & GDK.BUTTON2_MASK):
if not selected_boxes.has_key(treffer):
old_selection = (0,0,0,0)
selected_boxes[treffer] = TRUE
treffer = -1
# left mouse button outside a box -> selection
if treffer==-1 and (state & GDK.BUTTON1_MASK):
widget.get_window().set_cursor(cursor_new(30))
select = TRUE
old_selection = ( event.x, event.y, 0,0)
# right mouse button -> infobox
if (treffer > -1) and (state & GDK.BUTTON3_MASK) :
if not popped_up:
if not popup_win:
popup_win = GtkWindow(WINDOW_POPUP)
popup_win.set_position(WIN_POS_MOUSE)
popup_win.set_border_width(5)
label = GtkLabel("%s"% boxes[treffer][3])
label.show()
popup_win.add(label)
popup_win.connect("leave_notify_event", popdown_cb)
person = db.findPersonNoMap(boxes[treffer][3])
label.set_text( person.getPrimaryName().getName())
popup_win.show()
popped_up = TRUE
treffer=-1
redraw_tree( widget )
return TRUE
#
# returns a map of numbers
# boxes is the map of all boxes - I could have used the global variable here...
# "old_selection" has the following form: ( position x, position y, width, height )
#
def boxes_in_selection( boxes, old_selection ):
global sizeY, sizeX
res = {}
for i in range( len(boxes) ):
b = boxes[i]
if( old_selection[0]<=b[0]<=old_selection[0]+old_selection[2]-sizeX and old_selection[1]<=b[1]<=old_selection[1]+old_selection[3]-sizeY):
res[i] = TRUE
return res
def button_release_event(widget, event):
global treffer
global distY, distX
global sizeY, sizeX
global selX, selY, select, old_selection, selected_boxes
state = event.state
# draw a selection
if select and (state & GDK.BUTTON1_MASK):
select = FALSE
widget.get_window().set_cursor(cursor_new(132))
selected_boxes = boxes_in_selection(boxes, old_selection)
old_selection = (0,0,0,0)
# drag n' drop
if (treffer > -1) and (state & GDK.BUTTON1_MASK):
treffer=-1
widget.get_window().set_cursor(cursor_new(132))
for k in selected_boxes.keys():
b = boxes[k]
id = b[3]
person = db.findPersonNoMap(id)
person.setPosition( (b[0], b[1]) )
utils.modified()
redraw_tree( widget )
return TRUE
def motion_notify_event(widget, event):
global old_selection, selected_boxes, treffer
global distY, distX
state = event.window.pointer_state
if select and (state & GDK.BUTTON1_MASK):
old_selection = ( old_selection[0], old_selection[1], event.x-old_selection[0], event.y-old_selection[1] )
selected_boxes = boxes_in_selection(boxes, old_selection)
redraw_tree( widget)
if (treffer > -1) and (state & GDK.BUTTON1_MASK):
posX = event.x - distX
posY = event.y - distY
distance = ( posX-boxes[treffer][0], posY-boxes[treffer][1] )
old_selection = (0,0,0,0)
for k in selected_boxes.keys():
b = boxes[k]
boxes.pop(k)
boxes.insert( k, (b[0]+distance[0], b[1]+distance[1], b[2], b[3]) )
redraw_tree( widget)
return TRUE