diff options
author | Lionel LASKE <llaske@c2s.fr> | 2011-12-11 22:07:53 (GMT) |
---|---|---|
committer | Lionel LASKE <llaske@c2s.fr> | 2011-12-11 22:07:53 (GMT) |
commit | 9766a80536a351e45d67e0b3787bd7a6b6bcd569 (patch) | |
tree | 18135231d51fb398e4780d95eca7b4979b5c3af9 | |
parent | 0be87a54defeab50ea772c30ee78b9e1c473822b (diff) |
Add parents handling, refactor tree draw, samples tree
-rw-r--r-- | MANIFEST | 3 | ||||
-rw-r--r-- | activity.py | 156 | ||||
-rw-r--r-- | icons/emptytree.svg | 107 | ||||
-rw-r--r-- | icons/sample1.svg | 165 | ||||
-rw-r--r-- | icons/sample2.svg | 165 | ||||
-rw-r--r-- | src/person.py | 152 | ||||
-rw-r--r-- | src/tree.py | 163 | ||||
-rw-r--r-- | src/union.py | 67 |
8 files changed, 841 insertions, 137 deletions
@@ -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 |