Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLionel LASKE <llaske@c2s.fr>2011-12-11 22:07:53 (GMT)
committer Lionel LASKE <llaske@c2s.fr>2011-12-11 22:07:53 (GMT)
commit9766a80536a351e45d67e0b3787bd7a6b6bcd569 (patch)
tree18135231d51fb398e4780d95eca7b4979b5c3af9
parent0be87a54defeab50ea772c30ee78b9e1c473822b (diff)
Add parents handling, refactor tree draw, samples tree
-rw-r--r--MANIFEST3
-rw-r--r--activity.py156
-rw-r--r--icons/emptytree.svg107
-rw-r--r--icons/sample1.svg165
-rw-r--r--icons/sample2.svg165
-rw-r--r--src/person.py152
-rw-r--r--src/tree.py163
-rw-r--r--src/union.py67
8 files changed, 841 insertions, 137 deletions
diff --git a/MANIFEST b/MANIFEST
index 77479da..1f71efa 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -11,3 +11,6 @@ src/partialdate.py
src/guid.py
src/const.py
src/__init__.py
+icons/emptytree.svg
+icons/sample1.svg
+icons/sample2.svg
diff --git a/activity.py b/activity.py
index 91d2e24..fec5426 100644
--- a/activity.py
+++ b/activity.py
@@ -7,7 +7,7 @@ from gettext import gettext as _
from src.person import Person
from src.union import Union
from src.tree import Tree
-from src.tree import sample_family1, sample_family2
+from src.tree import empty_tree, sample_family1, sample_family2
import src.const as const
@@ -22,6 +22,9 @@ except ImportError:
const.inSugar = False
+# Init position
+const.tree_initx = 200
+const.tree_inity = 200
@@ -35,18 +38,32 @@ class RootsActivity(activity.Activity):
# Create toolbox
toolbox = activity.ActivityToolbox(self)
- toolbar = Toolbar()
+ toolbarview = Toolbar()
tool = ToolButton('zoom-out')
tool.set_tooltip(_('Zoom out'))
tool.set_accelerator(_('<ctrl>minus'))
tool.connect('clicked', self.zoom_out)
- toolbar.insert(tool, -1)
+ toolbarview.insert(tool, -1)
tool = ToolButton('zoom-in')
tool.set_tooltip(_('Zoom in'))
tool.set_accelerator(_('<ctrl>equal'))
tool.connect('clicked', self.zoom_in)
- toolbar.insert(tool, -1)
- toolbox.add_toolbar(_('View'), toolbar)
+ toolbarview.insert(tool, -1)
+ toolbox.add_toolbar(_('View'), toolbarview)
+ toolbarsample = Toolbar()
+ tool = ToolButton('emptytree')
+ tool.set_tooltip(_('Empty tree'))
+ tool.connect('clicked', self.emptytree)
+ toolbarsample.insert(tool, -1)
+ tool = ToolButton('sample1')
+ tool.set_tooltip(_('Sample 1'))
+ tool.connect('clicked', self.sample1)
+ toolbarsample.insert(tool, -1)
+ tool = ToolButton('sample2')
+ tool.set_tooltip(_('Sample 2'))
+ tool.connect('clicked', self.sample2)
+ toolbarsample.insert(tool, -1)
+ toolbox.add_toolbar(_('Samples'), toolbarsample)
self.set_toolbox(toolbox)
toolbox.show()
@@ -75,12 +92,15 @@ class RootsActivity(activity.Activity):
self.detail_chkfemale = gtk.RadioButton(self.detail_chkmale, "Female")
self.detail_chkfemale.connect("toggled", self.sexradio_checked, 'F')
self.fixed.pack_start(self.detail_chkfemale, False, False, 0)
+ self.detail_btnaddparent = gtk.Button("Add parent")
+ self.detail_btnaddparent.connect("clicked", self.addparent_clicked)
self.detail_btnaddunion = gtk.Button("Add union")
self.detail_btnaddunion.connect("clicked", self.addunion_clicked)
self.detail_btnaddchild = gtk.Button("Add child")
self.detail_btnaddchild.connect("clicked", self.addchild_clicked)
self.detail_btndelete = gtk.Button("Delete")
self.detail_btndelete.connect("clicked", self.delete_clicked)
+ self.fixed.pack_start(self.detail_btnaddparent, False, False, 0)
self.fixed.pack_start(self.detail_btnaddunion, False, False, 0)
self.fixed.pack_start(self.detail_btnaddchild, False, False, 0)
self.fixed.pack_start(self.detail_btndelete, False, False, 0)
@@ -89,17 +109,26 @@ class RootsActivity(activity.Activity):
self.box.pack_start(self.area, True, True, 0)
self.set_canvas(self.box)
- # Create sample data
- (self.initx, self.inity) = (500, 50)
- self.tree = sample_family2()
- self.tree.root.compute_draw(self.initx, self.inity)
- self.show_detail(None)
+ # Create empty tree
+ self.tree = None
+ self.init_tree(empty_tree)
# Show all
self.show_all()
self.area.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.ARROW))
+ def init_tree(self, treecallback):
+ "Create and init a tree"
+ if self.tree is not None:
+ self.tree.persons = []
+ self.tree.unions = []
+ self.tree = treecallback()
+ (self.initx, self.inity) = (const.tree_initx, const.tree_inity)
+ self.tree.set_position(self.initx, self.inity)
+ self.show_detail(None)
+
+
def set_center(self, person):
"Set the center of the draw on a person"
rect = self.area.allocation
@@ -121,6 +150,7 @@ class RootsActivity(activity.Activity):
self.detail_chkmale.set_active(True)
self.detail_chkmale.set_sensitive(False)
self.detail_chkfemale.set_sensitive(False)
+ self.detail_btnaddparent.set_sensitive(False)
self.detail_btnaddunion.set_sensitive(False)
self.detail_btnaddchild.set_sensitive(False)
self.detail_btndelete.set_sensitive(False)
@@ -142,7 +172,10 @@ class RootsActivity(activity.Activity):
self.detail_btnaddchild.set_sensitive(len(person.unions) > 0)
unionscount = len(person.unions)
childcount = person.child_count()
+ isfamily = self.tree.is_family(person)
isdescendant = self.tree.is_descendant(person)
+ isascendant = self.tree.is_ascendant(person)
+ isroot = (self.tree.root == person)
if person == self.tree.root:
deletable = False
elif unionscount == 0:
@@ -152,11 +185,15 @@ class RootsActivity(activity.Activity):
elif unionscount == 1 and isdescendant:
deletable = False
elif childcount > 0:
- deletable = False
+ if isascendant and (person.parents is None) and (childcount == 1):
+ deletable = True
+ else:
+ deletable = False
else:
deletable = True
self.detail_btndelete.set_sensitive(deletable)
- self.detail_btnaddunion.set_sensitive(isdescendant)
+ self.detail_btnaddunion.set_sensitive(isfamily)
+ self.detail_btnaddparent.set_sensitive(person.parents is None and (isascendant or isroot))
def area_expose_cb(self, area, event):
@@ -165,7 +202,7 @@ class RootsActivity(activity.Activity):
# Create context then draw tree inside
gc = self.area.window.cairo_create()
pc = self.create_pango_context()
- self.tree.root.draw(gc, pc)
+ self.tree.draw(gc, pc)
gc.stroke()
# Draw cross in middle to debug position
@@ -182,13 +219,12 @@ class RootsActivity(activity.Activity):
"Mouse button clicked, detail a person or start the moving mode"
# Look if click in a person
- for p in self.tree.persons:
- # Found one, show detail
- if p.point_inside(event.x, event.y):
- self.set_center(p)
- self.show_detail(p)
- self.moving = False
- return
+ p = self.tree.person_at(event.x, event.y)
+ if p is not None:
+ self.set_center(p)
+ self.show_detail(p)
+ self.moving = False
+ return
# Not found, pass in moving mode
self.show_detail(None)
@@ -200,8 +236,7 @@ class RootsActivity(activity.Activity):
def translate(self, dx, dy):
# Translate all the tree from deltax, deltay
(self.initx, self.inity) = (self.initx+dx, self.inity+dy)
- for p in self.tree.persons:
- p.translate(dx, dy)
+ self.tree.translate(dx, dy)
self.redraw()
@@ -216,11 +251,11 @@ class RootsActivity(activity.Activity):
else:
# Look if a person is under the cursor
- for p in self.tree.persons:
+ p = self.tree.person_at(event.x, event.y)
+ if p is not None:
# Found one, change cursor
- if p.point_inside(event.x, event.y):
- self.area.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND1))
- return
+ self.area.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND1))
+ return
# Not found, set standard cursor
self.area.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.ARROW))
@@ -236,8 +271,7 @@ class RootsActivity(activity.Activity):
def zoom_in(self, event):
"ToolBar button zoom in clicked"
self.zoomlevel = self.zoomlevel + 1
- for p in self.tree.persons:
- p.scale(20)
+ self.tree.scale(20)
self.redraw()
@@ -246,8 +280,7 @@ class RootsActivity(activity.Activity):
if self.zoomlevel == -3:
return
self.zoomlevel = self.zoomlevel - 1
- for p in self.tree.persons:
- p.scale(-20)
+ self.tree.scale(-20)
self.redraw()
@@ -267,18 +300,34 @@ class RootsActivity(activity.Activity):
self.selected.sex = data
self.redraw()
+
+ def addparent_clicked(self, event):
+ "Add parent clicked"
+ self.zoomlevel = 0
+ (x, y) = (self.tree.root.x0, self.tree.root.y0)
+ dad = self.tree.Person("", "M")
+ mum = self.tree.Person("", "F")
+ self.tree.Union(dad, mum).append_child(self.selected)
+ self.tree.set_position(x, y)
+ self.set_center(dad)
+ self.show_detail(dad)
+ self.redraw()
+
def addchild_clicked(self, event):
"Add child clicked"
- # TODO: Child add to the first union
+ self.zoomlevel = 0
newchild = self.tree.Person("", "M")
- self.selected.unions[0].append_child(newchild)
- self.tree.root.compute_draw(self.tree.root.x0, self.tree.root.y0)
+ self.selected.unions[0].append_child(newchild) # HACK: Child is add to the first union
+ self.tree.set_position(self.tree.root.x0, self.tree.root.y0)
+ self.set_center(newchild)
self.show_detail(newchild)
+ self.redraw()
def addunion_clicked(self, event):
"Add union clicked"
+ self.zoomlevel = 0
if self.selected.sex == "M":
male = self.selected
female = newunion = self.tree.Person("", "F")
@@ -286,36 +335,45 @@ class RootsActivity(activity.Activity):
male = newunion = self.tree.Person("", "M")
female = self.selected
self.tree.Union(male, female)
- self.tree.root.compute_draw(self.tree.root.x0, self.tree.root.y0)
+ self.tree.set_position(self.tree.root.x0, self.tree.root.y0)
+ self.set_center(newunion)
self.show_detail(newunion)
+ self.redraw()
def delete_clicked(self, event):
"Delete button clicked"
- # Remove from union
- for u in self.selected.unions:
- self.tree.unions.remove(u)
- if u.dad == self.selected:
- u.mum.unions.remove(u)
- else:
- u.dad.unions.remove(u)
-
- # Remove from parent child
- if self.selected.parents is not None:
- self.selected.parents.childs.remove(self.selected)
-
- # Remove from tree
- self.tree.persons.remove(self.selected)
+
+ # Delete as union or person
+ if self.tree.is_ascendant(self.selected) and (self.selected.parents is None) and (self.selected.child_count() == 1):
+ self.tree.remove_union(self.selected.unions[0])
+ else:
+ self.tree.remove(self.selected)
# Recompute and redraw
- self.tree.root.compute_draw(self.tree.root.x0, self.tree.root.y0)
+ self.zoomlevel = 0
+ self.tree.set_position(self.tree.root.x0, self.tree.root.y0)
self.show_detail(None)
def redraw(self):
"Redraw area"
self.area.queue_draw_area(0, 0, self.area.allocation.width, self.area.allocation.height)
+
+ def emptytree(self, event):
+ "Init with an empty tree"
+ self.init_tree(empty_tree)
+
+
+ def sample1(self, event):
+ "Init with a sample tree"
+ self.init_tree(sample_family1)
+
+
+ def sample2(self, event):
+ "Init with a sample tree"
+ self.init_tree(sample_family2)
# Dummy call to allow running on Windows
diff --git a/icons/emptytree.svg b/icons/emptytree.svg
new file mode 100644
index 0000000..3592301
--- /dev/null
+++ b/icons/emptytree.svg
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="68"
+ height="68"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.1 "
+ sodipodi:docname="Nouveau document 1">
+ <defs
+ id="defs4">
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath18">
+ <path
+ inkscape:connector-curvature="0"
+ d="M 0,0 55,0 55,55 0,55 0,0 z"
+ id="path20" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath30">
+ <path
+ inkscape:connector-curvature="0"
+ d="M 14.31427,13.05071 21.25893,9.041206 40.36538,42.13454 33.42072,46.14405 14.31427,13.05071 z"
+ id="path32" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath40">
+ <path
+ inkscape:connector-curvature="0"
+ d="M 14.31427,13.0507 21.25893,9.041206 40.36538,42.13454 33.42072,46.14404 14.31427,13.0507 z"
+ id="path42" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath52">
+ <path
+ inkscape:connector-curvature="0"
+ d="M 14.31427,13.05071 21.25893,9.041206 40.36538,42.13454 33.42072,46.14405 14.31427,13.05071 z"
+ id="path54" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath62">
+ <path
+ inkscape:connector-curvature="0"
+ d="M 14.31427,13.05071 21.25893,9.041206 40.36538,42.13454 33.42072,46.14405 14.31427,13.05071 z"
+ id="path64" />
+ </clipPath>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="5.260957"
+ inkscape:cx="19.267823"
+ inkscape:cy="32.389722"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:window-width="1280"
+ inkscape:window-height="738"
+ inkscape:window-x="-8"
+ inkscape:window-y="-8"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Calque 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-984.36218)">
+ <g
+ id="g12"
+ transform="matrix(1.25,0,0,-1.25,62.422549,388.07422)" />
+ <rect
+ style="fill:#ffffff;stroke:#666666;stroke-width:0.89999998;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="rect4031"
+ width="15.714286"
+ height="8.9285717"
+ x="26.87933"
+ y="1012.6174" />
+ </g>
+</svg>
diff --git a/icons/sample1.svg b/icons/sample1.svg
new file mode 100644
index 0000000..80cd12b
--- /dev/null
+++ b/icons/sample1.svg
@@ -0,0 +1,165 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="68"
+ height="68"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.1 "
+ sodipodi:docname="sample1.svg">
+ <defs
+ id="defs4">
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath18">
+ <path
+ inkscape:connector-curvature="0"
+ d="M 0,0 55,0 55,55 0,55 0,0 z"
+ id="path20" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath30">
+ <path
+ inkscape:connector-curvature="0"
+ d="M 14.31427,13.05071 21.25893,9.041206 40.36538,42.13454 33.42072,46.14405 14.31427,13.05071 z"
+ id="path32" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath40">
+ <path
+ inkscape:connector-curvature="0"
+ d="M 14.31427,13.0507 21.25893,9.041206 40.36538,42.13454 33.42072,46.14404 14.31427,13.0507 z"
+ id="path42" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath52">
+ <path
+ inkscape:connector-curvature="0"
+ d="M 14.31427,13.05071 21.25893,9.041206 40.36538,42.13454 33.42072,46.14405 14.31427,13.05071 z"
+ id="path54" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath62">
+ <path
+ inkscape:connector-curvature="0"
+ d="M 14.31427,13.05071 21.25893,9.041206 40.36538,42.13454 33.42072,46.14405 14.31427,13.05071 z"
+ id="path64" />
+ </clipPath>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="10.521914"
+ inkscape:cx="44.767642"
+ inkscape:cy="30.170057"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:window-width="1280"
+ inkscape:window-height="738"
+ inkscape:window-x="-8"
+ inkscape:window-y="-8"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Calque 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-984.36218)">
+ <g
+ id="g12"
+ transform="matrix(1.25,0,0,-1.25,62.422549,388.07422)" />
+ <rect
+ style="fill:#ffffff;stroke:#666666;stroke-width:0.89999998;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="rect4031"
+ width="15.714286"
+ height="8.9285717"
+ x="16.87933"
+ y="1008.5432" />
+ <rect
+ style="fill:#ffffff;stroke:#666666;stroke-width:0.89999998;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="rect4031-1"
+ width="15.714286"
+ height="8.9285717"
+ x="35.28257"
+ y="1008.5432" />
+ <rect
+ style="fill:#ffffff;stroke:#666666;stroke-width:0.89999998;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="rect4031-7"
+ width="15.714286"
+ height="8.9285717"
+ x="5.6135082"
+ y="1022.9149" />
+ <path
+ style="fill:none;stroke:#666666;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 32.313513,29.033705 2.661112,0"
+ id="path3026"
+ inkscape:connector-curvature="0"
+ transform="translate(0,984.36218)" />
+ <rect
+ style="fill:#ffffff;stroke:#666666;stroke-width:0.89999998;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="rect4031-0"
+ width="15.714286"
+ height="8.9285717"
+ x="25.204765"
+ y="1022.9149" />
+ <rect
+ style="fill:#ffffff;stroke:#666666;stroke-width:0.89999998;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="rect4031-1-8"
+ width="15.714286"
+ height="8.9285717"
+ x="43.608006"
+ y="1022.9149" />
+ <path
+ style="fill:none;stroke:#666666;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 40.638947,1027.7676 2.661112,0"
+ id="path3026-7"
+ inkscape:connector-curvature="0" />
+ <rect
+ style="fill:#ffffff;stroke:#666666;stroke-width:0.89999998;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="rect4031-7-3"
+ width="15.714286"
+ height="8.9285717"
+ x="33.90279"
+ y="1035.9354" />
+ <path
+ style="fill:none;stroke:#666666;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 33.739109,28.843626 -0.09504,9.503974 0,-2.471033 -21.098823,0.09504 0,2.471033"
+ id="path3856"
+ inkscape:connector-curvature="0"
+ transform="translate(0,984.36218)" />
+ <path
+ style="fill:none;stroke:#666666;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 42.292686,43.384707 0,7.983338"
+ id="path3862"
+ inkscape:connector-curvature="0"
+ transform="translate(0,984.36218)" />
+ </g>
+</svg>
diff --git a/icons/sample2.svg b/icons/sample2.svg
new file mode 100644
index 0000000..80cd12b
--- /dev/null
+++ b/icons/sample2.svg
@@ -0,0 +1,165 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="68"
+ height="68"
+ id="svg2"
+ version="1.1"
+ inkscape:version="0.48.1 "
+ sodipodi:docname="sample1.svg">
+ <defs
+ id="defs4">
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath18">
+ <path
+ inkscape:connector-curvature="0"
+ d="M 0,0 55,0 55,55 0,55 0,0 z"
+ id="path20" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath30">
+ <path
+ inkscape:connector-curvature="0"
+ d="M 14.31427,13.05071 21.25893,9.041206 40.36538,42.13454 33.42072,46.14405 14.31427,13.05071 z"
+ id="path32" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath40">
+ <path
+ inkscape:connector-curvature="0"
+ d="M 14.31427,13.0507 21.25893,9.041206 40.36538,42.13454 33.42072,46.14404 14.31427,13.0507 z"
+ id="path42" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath52">
+ <path
+ inkscape:connector-curvature="0"
+ d="M 14.31427,13.05071 21.25893,9.041206 40.36538,42.13454 33.42072,46.14405 14.31427,13.05071 z"
+ id="path54" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath62">
+ <path
+ inkscape:connector-curvature="0"
+ d="M 14.31427,13.05071 21.25893,9.041206 40.36538,42.13454 33.42072,46.14405 14.31427,13.05071 z"
+ id="path64" />
+ </clipPath>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="10.521914"
+ inkscape:cx="44.767642"
+ inkscape:cy="30.170057"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:window-width="1280"
+ inkscape:window-height="738"
+ inkscape:window-x="-8"
+ inkscape:window-y="-8"
+ inkscape:window-maximized="1" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Calque 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-984.36218)">
+ <g
+ id="g12"
+ transform="matrix(1.25,0,0,-1.25,62.422549,388.07422)" />
+ <rect
+ style="fill:#ffffff;stroke:#666666;stroke-width:0.89999998;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="rect4031"
+ width="15.714286"
+ height="8.9285717"
+ x="16.87933"
+ y="1008.5432" />
+ <rect
+ style="fill:#ffffff;stroke:#666666;stroke-width:0.89999998;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="rect4031-1"
+ width="15.714286"
+ height="8.9285717"
+ x="35.28257"
+ y="1008.5432" />
+ <rect
+ style="fill:#ffffff;stroke:#666666;stroke-width:0.89999998;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="rect4031-7"
+ width="15.714286"
+ height="8.9285717"
+ x="5.6135082"
+ y="1022.9149" />
+ <path
+ style="fill:none;stroke:#666666;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 32.313513,29.033705 2.661112,0"
+ id="path3026"
+ inkscape:connector-curvature="0"
+ transform="translate(0,984.36218)" />
+ <rect
+ style="fill:#ffffff;stroke:#666666;stroke-width:0.89999998;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="rect4031-0"
+ width="15.714286"
+ height="8.9285717"
+ x="25.204765"
+ y="1022.9149" />
+ <rect
+ style="fill:#ffffff;stroke:#666666;stroke-width:0.89999998;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="rect4031-1-8"
+ width="15.714286"
+ height="8.9285717"
+ x="43.608006"
+ y="1022.9149" />
+ <path
+ style="fill:none;stroke:#666666;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 40.638947,1027.7676 2.661112,0"
+ id="path3026-7"
+ inkscape:connector-curvature="0" />
+ <rect
+ style="fill:#ffffff;stroke:#666666;stroke-width:0.89999998;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+ id="rect4031-7-3"
+ width="15.714286"
+ height="8.9285717"
+ x="33.90279"
+ y="1035.9354" />
+ <path
+ style="fill:none;stroke:#666666;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 33.739109,28.843626 -0.09504,9.503974 0,-2.471033 -21.098823,0.09504 0,2.471033"
+ id="path3856"
+ inkscape:connector-curvature="0"
+ transform="translate(0,984.36218)" />
+ <path
+ style="fill:none;stroke:#666666;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 42.292686,43.384707 0,7.983338"
+ id="path3862"
+ inkscape:connector-curvature="0"
+ transform="translate(0,984.36218)" />
+ </g>
+</svg>
diff --git a/src/person.py b/src/person.py
index 75115f5..c960fa3 100644
--- a/src/person.py
+++ b/src/person.py
@@ -32,7 +32,16 @@ class Person:
self.sex = 'F'
self.parents = None
self.unions = []
- (self.x0, self.y0, self.x1, self.y1, self.fontsize) = (-1, -1, -1, -1, const._person_fontsize)
+ (self.x0, self.y0, self.x1, self.y1, self.fontsize) = (None, None, None, None, const._person_fontsize)
+ self.computed = False
+
+
+ def width_margin(self):
+ return const._person_width / const._person_wmargin_ratio
+
+
+ def height_margin(self):
+ return const._person_height / const._person_hmargin_ratio
def append_union(self, union):
@@ -57,13 +66,13 @@ class Person:
return buf
- def size_to_draw(self):
+ def size_desc(self):
"Compute size to draw the person an its subtree"
if self.unions == []:
return 1
totlen = 0
for u in self.unions:
- totlen = totlen + u.size_to_draw()
+ totlen = totlen + u.size_desc()
return totlen
@@ -73,69 +82,98 @@ class Person:
def child_count(self):
+ "Compute number of childs"
totchild = 0
for u in self.unions:
totchild = totchild + len(u.childs)
return totchild
-
-
- def draw_node(self, gc, pc):
- "Draw a person"
-
- # draw border
- if self.sex == 'M':
- gc.set_source_rgb(*const._male_color)
- else:
- gc.set_source_rgb(*const._female_color)
- gc.rectangle(self.x0, self.y0, self.x1-self.x0, self.y1-self.y0)
- gc.stroke()
-
- # draw text
- layout = pango.Layout(pc)
- layout.set_width(int(self.x1-self.x0)*pango.SCALE)
- layout.set_text(self.name)
- layout.set_alignment(pango.ALIGN_CENTER)
- layout.set_wrap(pango.WRAP_WORD)
- layout.set_font_description(pango.FontDescription(const._person_font + " " + str(self.fontsize)))
- gc.move_to(self.x0, self.y0)
- gc.show_layout(layout)
+
+ def level(self, count=0):
+ "Compute number of level in the subtree of the person"
+ childlevel = count
+ for u in self.unions:
+ for c in u.childs:
+ childlevel = max(childlevel, c.level(count+1))
+ return childlevel
- def compute_draw(self, x, y):
- "Compute person and its subtree draw starting at origin"
+
+ def set_pos(self, x, y):
+ "Set person position"
+ # Already done
+ if self.computed:
+ return
+
# Set person position
(self.x0, self.y0) = (x, y)
(self.x1, self.y1) = (x+const._person_width, y+const._person_height)
+ self.computed = True
self.fontsize = const._person_fontsize
+
+
+ def compute_desc_pos(self, x, y):
+ "Compute person and its subtree draw starting at origin"
+
if self.unions != []:
# Set union position
- wmargin = const._person_width / const._person_wmargin_ratio
- hmargin = const._person_height / const._person_hmargin_ratio
for i, u in enumerate(self.unions):
# Next union
if i != 0:
- size = u.size_to_draw()
- x = x + (size*const._person_width+size*wmargin)/2
+ size = u.size_desc()+self.unions[i-1].size_desc()
+ x = x + (size*const._person_width+size*self.width_margin())/2
# Set conjoint position
if self.sex == 'M':
opposite = u.mum
else:
opposite = u.dad
- (opposite.x0, opposite.y0) = (x + const._person_width+wmargin, y)
- (opposite.x1, opposite.y1) = (opposite.x0 + const._person_width, opposite.y0 + const._person_height)
+ opposite.set_pos(x + const._person_width+self.width_margin(), y)
# Set the union and childs of this union
- u.compute_draw(x+const._person_width+(wmargin/2), y+const._person_height+hmargin)
+ u.compute_desc_pos(x+const._person_width+(self.width_margin()/2), y+const._person_height+self.height_margin())
# Shift right
- size = u.size_to_draw()
- x = x + (size*const._person_width+size*wmargin)/2
+ size = u.size_desc()
+ x = x + (size*const._person_width+size*self.width_margin())/2
+ def compute_asc_pos(self):
+ "Compute ascendant and its subtree draw starting at origin"
+ if self.parents is None:
+ return
+
+ # Set parent position
+ #size = self.parents.size_desc()
+ dad = self.parents.dad
+ mum = self.parents.mum
+ if len(self.unions) == 0:
+ x = self.x0 - (const._person_width+self.width_margin())/2
+ else:
+ conjoint = self.unions[0].conjoint(self)
+ if conjoint.parents is None:
+ x = self.x0 - (const._person_width+self.width_margin())/2
+ elif self.x0 < conjoint.x0:
+ size = 1
+ x = self.x0 - (size*3*const._person_width+size*self.width_margin())/2
+ else:
+ size = 1
+ x = self.x0 + (size*const._person_width+size*self.width_margin())/2
+ y = self.y0 - const._person_height - self.height_margin()
+ dad.set_pos(x, y)
+ mum.set_pos(x + const._person_width+self.width_margin(), y)
+
+ # Set parent parent position and their child
+ dad.compute_asc_pos()
+ mum.compute_asc_pos()
+ dad.compute_desc_pos(dad.x0, dad.y0)
+ mum.compute_desc_pos(mum.x0, mum.y0)
+
+
def translate(self, dx, dy):
"Translate coordinate"
+ if self.x0 is None:
+ return
self.x0 = self.x0 + dx
self.y0 = self.y0 + dy
self.x1 = self.x1 + dx
@@ -144,6 +182,8 @@ class Person:
def scale(self, scalerate):
"Scale coordinate and font"
+ if self.x0 is None:
+ return
self.x0 = self.x0 + ((self.x0 * scalerate) / 100)
self.y0 = self.y0 + ((self.y0 * scalerate) / 100)
self.x1 = self.x1 + ((self.x1 * scalerate) / 100)
@@ -152,19 +192,29 @@ class Person:
def draw(self, gc, pc):
- "Draw person and its subtree in the graphical context"
+ "Draw a person"
- # Draw person
- self.draw_node(gc, pc)
- if self.unions != []:
- # Draw union
- for i, u in enumerate(self.unions):
- # Draw conjoint
- if self.sex == 'M':
- opposite = u.mum
- else:
- opposite = u.dad
- opposite.draw_node(gc, pc)
-
- # Draw the union and childs of this union
- u.draw(gc, pc, i)
+ # Do not draw person without coordinate
+ if self.x0 is None:
+ return
+
+ # draw border
+ if self.sex == 'M':
+ gc.set_source_rgb(*const._male_color)
+ else:
+ gc.set_source_rgb(*const._female_color)
+ gc.rectangle(self.x0, self.y0, self.x1-self.x0, self.y1-self.y0)
+ gc.stroke()
+
+ # draw text
+ layout = pango.Layout(pc)
+ layout.set_width(int(self.x1-self.x0)*pango.SCALE)
+ layout.set_text(self.name)
+ layout.set_alignment(pango.ALIGN_CENTER)
+ layout.set_wrap(pango.WRAP_WORD)
+ layout.set_font_description(pango.FontDescription(const._person_font + " " + str(self.fontsize)))
+ gc.move_to(self.x0, self.y0)
+ gc.show_layout(layout)
+
+
+
diff --git a/src/tree.py b/src/tree.py
index 1dfa8c9..a22b3e8 100644
--- a/src/tree.py
+++ b/src/tree.py
@@ -1,6 +1,7 @@
from person import Person
from union import Union
+import const
# Tree class
@@ -27,8 +28,9 @@ class Tree:
self.unions.append(u)
return u
+
def is_descendant(self, person, root=None):
- "Look is person is a descendant of the tree"
+ "Look if person is a descendant of the tree"
start = root
if start is None:
start = self.root
@@ -41,18 +43,163 @@ class Tree:
return False
+ def is_ascendant(self, person, root=None):
+ "Look if person is an ascendant of the tree"
+ start = root
+ if start is None:
+ start = self.root
+ if start.parents is not None:
+ if start.parents.dad == person or start.parents.mum == person:
+ return True
+ if self.is_ascendant(person, start.parents.dad):
+ return True
+ if self.is_ascendant(person, start.parents.mum):
+ return True
+ return False
+
+
+ def is_family(self, person, root=None):
+ "Look if person is a direct family member"
+
+ # Start point
+ start = root
+ if start is None:
+ start = self.root
+
+ # Test
+ if start == person:
+ return True
+ if self.is_descendant(person, start):
+ return True
+
+ # Test parents
+ if start.parents is not None:
+ if self.is_family(person, start.parents.dad):
+ return True
+ if self.is_family(person, start.parents.mum):
+ return True
+
+ return False
+
+
+ def set_position(self, initx, inity):
+ "Set the tree to this position"
+
+ # Compute levels
+ levels = dict()
+ maxlevel = -1
+ for p in self.persons:
+ levelvalue = p.level()
+ maxlevel = max(levelvalue, maxlevel)
+ if levelvalue not in levels:
+ levels[levelvalue] = []
+ levels[levelvalue].append(p)
+ p.computed = False
+
+ # Set pos start starting on higher level
+ (x, y) = (initx, inity)
+ for levelvalue in reversed(range(maxlevel+1)):
+ roots = levels[levelvalue]
+ for i, root in enumerate(roots):
+ root.set_pos(x, y)
+ root.compute_desc_pos(x, y)
+ size = root.size_desc()
+ if i > 0:
+ size = size + roots[i].size_desc()
+ x = x + (size*const._person_width+size*root.width_margin())/2
+ x = initx
+ y = y + const._person_height+root.height_margin()
+
+
+ def draw(self, gc, pc):
+ "Draw the tree in the graphical and pango context"
+ for p in self.persons:
+ p.draw(gc, pc)
+
+ for u in self.unions:
+ u.draw(gc, pc)
+
+
+ def person_at(self, x, y):
+ "Look for a person at this position"
+ for p in self.persons:
+ if p.point_inside(x, y):
+ return p
+ return None
+
+
+ def translate(self, dx, dy):
+ "Translate tree"
+ for p in self.persons:
+ p.translate(dx, dy)
+
+
+ def scale(self, scalelevel):
+ "Scale tree"
+ for p in self.persons:
+ p.scale(scalelevel)
+
+
+ def remove(self, person):
+ "Remove a person from the tree"
+ # Remove from union
+ for u in person.unions:
+ self.unions.remove(u)
+ if u.dad == person:
+ u.mum.unions.remove(u)
+ else:
+ u.dad.unions.remove(u)
+
+ # Remove from parent child
+ if person.parents is not None:
+ person.parents.childs.remove(person)
+
+ # Remove from tree
+ self.persons.remove(person)
+
+
+ def remove_union(self, union):
+ "Remove an union from the tree"
+ # Remove from child
+ for c in union.childs:
+ c.parents = None
+
+ # Remove from parent
+ union.dad.unions.remove(union)
+ union.mum.unions.remove(union)
+
+ # Remove from tree
+ self.unions.remove(union)
+
+ # Remove dad and mum
+ self.remove(union.dad)
+ self.remove(union.mum)
+
# Create the samples family
-def sample_family1():
+def empty_tree():
+ tree = Tree()
+ tree.root = tree.Person("", "M")
+
+ return tree
+
+def sample_family1():
+ # Empty family
tree = Tree()
- tree.root = tree.Person("", "M")
+ tree.root = tree.Person("C", "M")
+
+ a = tree.Person("A", "M")
+ b = tree.Person("B", "F")
+ tree.Union(a, b).append_child(tree.root)
+ bb = tree.Person("BB", "F")
+ tree.Union(a, bb)
return tree
def sample_family2():
-
+ # Large family
tree = Tree()
l = tree.Person("Lucien", "M")
@@ -73,7 +220,8 @@ def sample_family2():
c = tree.Person("Christian", "M")
up.append_child(c)
- ub = tree.Union(tree.Person("Jonathan", "M"), j)
+ jo = tree.Person("Jonathan", "M")
+ ub = tree.Union(jo, j)
ub.append_child(tree.Person("Charlie", "F"))
rs = tree.Person("Renee", "F")
@@ -95,9 +243,10 @@ def sample_family2():
sa = tree.Person("Sandrine", "F")
uc.append_child(sa)
uc2 = tree.Union(c, tree.Person("Vivianne", "F"))
- uc2.append_child(tree.Person("Pierre", "M"))
+ pi = tree.Person("Pierre", "M")
+ uc2.append_child(pi)
uc2.append_child(tree.Person("Camille", "F"))
- tree.root = vm
+ tree.root = d #vm
return tree
diff --git a/src/union.py b/src/union.py
index 96016c9..fdfff51 100644
--- a/src/union.py
+++ b/src/union.py
@@ -37,17 +37,17 @@ class Union:
return str
- def size_to_draw(self):
+ def size_desc(self):
"Compute size to draw the union an its subtree"
totlen = 0
if self.childs == []:
- return 3
+ return 2
for c in self.childs:
- totlen = totlen + c.size_to_draw()
- return max(3,totlen)
+ totlen = totlen + c.size_desc()
+ return max(2,totlen)
-
- def compute_draw(self, x, y):
+
+ def compute_desc_pos(self, x, y):
"Compute union subtree position"
# Reinit size
@@ -57,56 +57,63 @@ class Union:
# Compute childs size
size = 0
for c in self.childs:
- size = size + c.size_to_draw()
+ size = size + c.size_desc()
# Compute origin
- wmargin = const._person_width/const._person_wmargin_ratio
if len(self.childs) == 1:
x = x - (const._person_width)/2
else:
- x = x - (size*const._person_width+(size-1)*wmargin)/2
+ x = x - (size*const._person_width+(size-1)*self.dad.width_margin())/2
# Compute draw for childs
for c in self.childs:
- c.compute_draw(x, y)
- size = c.size_to_draw()
- x = x + (size*const._person_width+size*wmargin)
+ c.set_pos(x, y)
+ c.compute_desc_pos(x, y)
+ size = c.size_desc()
+ x = x + (size*const._person_width+size*c.width_margin())
+
+
+ def conjoint(self, p):
+ "Return conjoint"
+ if p == self.dad:
+ return self.mum
+ else:
+ return self.dad
- def draw(self, gc, pc, unionnumber):
+ def draw(self, gc, pc):
"Draw person and its subtree in the graphical context"
+ # Do not draw person without coordinate
+ if self.dad.x0 is None or self.mum.x0 is None:
+ return
+
# Draw link between parents
if self.dad.x0 < self.mum.x0:
(left, right) = (self.dad, self.mum)
- if (unionnumber != 0):
- left = left.unions[unionnumber-1].mum
else:
(left, right) = (self.mum, self.dad)
- if (unionnumber != 0):
- left = left.unions[unionnumber-1].dad
+ if len(left.unions) > 1 and self != left.unions[0]:
+ i = 1
+ while left.unions[i].conjoint(left) != right:
+ i = i + 1
+ left = left.unions[i-1].conjoint(left)
width = left.x1 - left.x0
height = left.y1 - left.y0
- wmargin = width / const._person_wmargin_ratio
- hmargin = height / const._person_hmargin_ratio
gc.move_to(left.x0+width, left.y0+(height/2))
gc.line_to(right.x0, right.y0+(height/2))
gc.set_source_rgb(*const._union_color)
gc.stroke()
- # Draw each child
+ # Draw link to child
size = len(self.childs)
for c in self.childs:
- # Draw the child
- c.draw(gc, pc)
-
# Draw link to child
- gc.move_to(right.x0-(wmargin/2), right.y0+(height/2))
- gc.line_to(right.x0-(wmargin/2), right.y0+height+(hmargin/2))
- if size == 1:
- gc.line_to(right.x0-(wmargin/2), c.y0)
- else:
- gc.line_to(c.x0+(width/2), right.y0+height+(hmargin/2))
- gc.line_to(c.x0+(width/2), c.y0)
+ if c.x0 is None:
+ continue
+ gc.move_to(right.x0-(c.width_margin()/2), right.y0+(height/2))
+ gc.line_to(right.x0-(c.width_margin()/2), right.y0+height+(c.height_margin()/2))
+ gc.line_to(c.x0+(width/2), right.y0+height+(c.height_margin()/2))
+ gc.line_to(c.x0+(width/2), c.y0)
gc.set_source_rgb(*const._union_color)
gc.stroke() \ No newline at end of file