Relationship graph plugin
svn: r303
This commit is contained in:
parent
21f52372d5
commit
8889385911
394
gramps/src/plugins/Graph.py
Normal file
394
gramps/src/plugins/Graph.py
Normal 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
|
Loading…
x
Reference in New Issue
Block a user