From 0be87a54defeab50ea772c30ee78b9e1c473822b Mon Sep 17 00:00:00 2001 From: Lionel LASKE Date: Sat, 03 Dec 2011 14:48:39 +0000 Subject: Handle add/delete button, fix zoom and detail view --- diff --git a/MANIFEST b/MANIFEST index a8478f6..77479da 100644 --- a/MANIFEST +++ b/MANIFEST @@ -1,12 +1,12 @@ sugardummy.py setup.py activity.py -MANIFEST COPYING activity/roots-icon.svg activity/activity.info src/union.py src/person.py +src/tree.py src/partialdate.py src/guid.py src/const.py diff --git a/activity.py b/activity.py index 5573f58..91d2e24 100644 --- a/activity.py +++ b/activity.py @@ -5,8 +5,9 @@ from gettext import gettext as _ # Local import from src.person import Person -from src.person import persons from src.union import Union +from src.tree import Tree +from src.tree import sample_family1, sample_family2 import src.const as const @@ -49,15 +50,10 @@ class RootsActivity(activity.Activity): self.set_toolbox(toolbox) toolbox.show() - # Create sample data - (self.initx, self.inity, self.initzoom) = (500, 50, 0) - self.tree = self.init_family() - self.tree.compute_draw(self.initx, self.inity) - # Create drawing area - self.zoomlevel = 1 + self.zoomlevel = 0 self.area = gtk.DrawingArea() - self.area.set_size_request(600, 300) + self.area.set_size_request(750, 600) self.area.set_events(gtk.gdk.BUTTON_PRESS_MASK|gtk.gdk.BUTTON_RELEASE_MASK|gtk.gdk.BUTTON_MOTION_MASK|gtk.gdk.POINTER_MOTION_MASK) self.area.connect("expose_event", self.area_expose_cb) self.area.connect("button_press_event", self.press_button) @@ -65,104 +61,162 @@ class RootsActivity(activity.Activity): self.area.connect("motion_notify_event", self.mouse_move) self.moving = False - # Draw - self.box = gtk.VBox(False) + # Create detail view + self.fixed = gtk.VBox() + self.fixed.set_size_request(200, 300) + self.fixed.pack_start(gtk.Label("Name:"), False, False, 0) + self.detail_name = gtk.TextView() + self.detail_name.set_cursor_visible(True) + self.detail_name.get_buffer().connect("changed", self.detail_changed) + self.fixed.pack_start(self.detail_name, False, False, 0) + self.detail_chkmale = gtk.RadioButton(None, "Male") + self.detail_chkmale.connect("toggled", self.sexradio_checked, 'M') + self.fixed.pack_start(self.detail_chkmale, False, False, 0) + 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_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_btnaddunion, False, False, 0) + self.fixed.pack_start(self.detail_btnaddchild, False, False, 0) + self.fixed.pack_start(self.detail_btndelete, False, False, 0) + self.box = gtk.HBox(False) + self.box.pack_start(self.fixed, True, True, 0) 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) + + # Show all self.show_all() self.area.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.ARROW)) - - def init_family(self): - "Init a sample tree" - l = Person("Lucien", "M") - a = Person("Annick", "F") - u = Union(l, a) - - d = Person("Dominique", "M") - u.append_child(d) - au = Person("Anne", "F") - u.append_child(au) - m = Person("Madeleine", "F") - u.append_child(m) - - jp = Person("Jean-Pierre", "M") - j = Person("Julie", "F") - up = Union(jp, j) - up.append_child(l) - c = Person("Christian", "M") - up.append_child(c) - - ub = Union(Person("Jonathan", "M"), j) - ub.append_child(Person("Charlie", "F")) - - rs = Person("Renee", "F") - vm = Person("Vivien", "M") - urv = Union(vm, rs) - urv.append_child(j) - - jr = Person("Jean-Rene", "M") - ua = Union(jr, Person("Micheline", "F")) - ua.append_child(a) - i = Person("Irene", "F") - ua.append_child(i) - ui = Union(Person("Nathan", "M"), i) - ui.append_child(Person("Marie", "F")) - ui.append_child(Person("Noel", "M")) - ui.append_child(Person("Thierry", "M")) - - uc = Union(c, Person("Clarah", "F")) - uc.append_child(Person("Sandrine", "F")) - uc2 = Union(c, Person("Vivianne", "F")) - uc2.append_child(Person("Pierre", "M")) - uc2.append_child(Person("Camille", "F")) - - return vm - + + def set_center(self, person): + "Set the center of the draw on a person" + rect = self.area.allocation + dx = (rect.width/2) - self.tree.root.x0 - (person.x0 - self.tree.root.x0) - (person.x1 - person.x0)/2 + dy = (rect.height/2) - self.tree.root.y0 - (person.y0 - self.tree.root.y0) - (person.y1 - person.y0)/2 + self.translate(dx, dy) + + + def show_detail(self, person): + "Show detail information for a person" + + # Change selection + self.selected = person + + # No selection + if person == None: + self.detail_name.set_sensitive(False) + self.detail_name.get_buffer().set_text("Click one a node to select it") + self.detail_chkmale.set_active(True) + self.detail_chkmale.set_sensitive(False) + self.detail_chkfemale.set_sensitive(False) + self.detail_btnaddunion.set_sensitive(False) + self.detail_btnaddchild.set_sensitive(False) + self.detail_btndelete.set_sensitive(False) + return + + # A node is selected + self.detail_name.set_sensitive(True) + self.detail_name.grab_focus() + self.detail_name.get_buffer().set_text(person.name) + if person.sex == 'M': + self.detail_chkmale.set_active(True) + else: + self.detail_chkfemale.set_active(True) + + # Compute button status + checkable = (len(person.unions) == 0) + self.detail_chkmale.set_sensitive(checkable) + self.detail_chkfemale.set_sensitive(checkable) + self.detail_btnaddchild.set_sensitive(len(person.unions) > 0) + unionscount = len(person.unions) + childcount = person.child_count() + isdescendant = self.tree.is_descendant(person) + if person == self.tree.root: + deletable = False + elif unionscount == 0: + deletable = True + elif unionscount > 1: + deletable = False + elif unionscount == 1 and isdescendant: + deletable = False + elif childcount > 0: + deletable = False + else: + deletable = True + self.detail_btndelete.set_sensitive(deletable) + self.detail_btnaddunion.set_sensitive(isdescendant) + def area_expose_cb(self, area, event): "Draw tree event" + + # Create context then draw tree inside gc = self.area.window.cairo_create() pc = self.create_pango_context() - self.tree.draw(gc, pc) + self.tree.root.draw(gc, pc) gc.stroke() + + # Draw cross in middle to debug position + rect = self.area.allocation + gc.move_to((rect.width/2)-10, (rect.height/2)) + gc.line_to((rect.width/2)+10, (rect.height/2)) + gc.move_to((rect.width/2), (rect.height/2)-10) + gc.line_to((rect.width/2), (rect.height/2)+10) + gc.set_source_rgb(255, 255, 255) + gc.stroke() def press_button(self, widget, event): "Mouse button clicked, detail a person or start the moving mode" # Look if click in a person - for p in persons: + for p in self.tree.persons: # Found one, show detail if p.point_inside(event.x, event.y): - print p.name + self.set_center(p) + self.show_detail(p) self.moving = False return # Not found, pass in moving mode + self.show_detail(None) self.movingStart = (event.x, event.y) self.moving = True self.area.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.FLEUR)) - + + 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.redraw() + + def mouse_move(self, widget, event): "Mouse move event, in moving mode translate draw if in moving mode else change cursor on person" # In moving mode ? if self.moving: # Compute translation - (dx, dy) = (event.x-self.movingStart[0], event.y-self.movingStart[1]) - (self.initx, self.inity) = (self.initx+dx, self.inity+dy) - for p in persons: - p.translate(dx, dy) + self.translate(event.x-self.movingStart[0], event.y-self.movingStart[1]) self.movingStart = (event.x, event.y) - - # Redraw - self.redraw() else: # Look if a person is under the cursor - for p in persons: + for p in self.tree.persons: # Found one, change cursor if p.point_inside(event.x, event.y): self.area.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND1)) @@ -181,21 +235,85 @@ class RootsActivity(activity.Activity): def zoom_in(self, event): "ToolBar button zoom in clicked" - self.initzoom = self.initzoom + 20 + self.zoomlevel = self.zoomlevel + 1 + for p in self.tree.persons: + p.scale(20) self.redraw() def zoom_out(self, event): "ToolBar button zoom out clicked" - self.initzoom = self.initzoom - 20 + if self.zoomlevel == -3: + return + self.zoomlevel = self.zoomlevel - 1 + for p in self.tree.persons: + p.scale(-20) + self.redraw() + + + def detail_changed(self, event): + "Textfield for the detail has changed, change the matching person name" + if self.selected == None: + self.redraw() + return + buffer = self.detail_name.get_buffer() + self.selected.name = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter()) + self.redraw() + + + def sexradio_checked(self, widget, data): + "Radio button for sex checked, change sex for the selected person" + if self.selected is not None: + self.selected.sex = data self.redraw() + + def addchild_clicked(self, event): + "Add child clicked" + # TODO: Child add to the first union + 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.show_detail(newchild) + + + def addunion_clicked(self, event): + "Add union clicked" + if self.selected.sex == "M": + male = self.selected + female = newunion = self.tree.Person("", "F") + else: + 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.show_detail(newunion) + + + 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) + + # Recompute and redraw + self.tree.root.compute_draw(self.tree.root.x0, self.tree.root.y0) + self.show_detail(None) + + def redraw(self): "Redraw area" - self.tree.compute_draw(self.initx, self.inity) - for p in persons: - p.scale(self.initzoom) self.area.queue_draw_area(0, 0, self.area.allocation.width, self.area.allocation.height) diff --git a/src/person.py b/src/person.py index 094eeb8..75115f5 100644 --- a/src/person.py +++ b/src/person.py @@ -15,10 +15,6 @@ const._person_font = 'Arial' const._person_fontsize = 8 -# Set of person -persons = [] - - # Person class class Person: @@ -37,7 +33,6 @@ class Person: self.parents = None self.unions = [] (self.x0, self.y0, self.x1, self.y1, self.fontsize) = (-1, -1, -1, -1, const._person_fontsize) - persons.append(self) def append_union(self, union): @@ -47,19 +42,19 @@ class Person: def tostring(self, level=1): "Translate to a formatted string, tree is horizontal" - str = 'P{0}\n'.format(self.id) + buf = 'P'+str(self.id)+'\n' if self.unions == []: - str += '\t'*level + self.name + " " + self.sex + buf += '\t'*level + self.name + " " + self.sex else: for u in self.unions: if self.sex == 'M': opposite = u.mum else: opposite = u.dad - str += '\t'*level + self.name + " " + self.sex + '\n+' + '\t'*level + opposite.name + " " + opposite.sex + '\n' - str += u.tostring(level) + buf += '\t'*level + self.name + " " + self.sex + '\n+' + '\t'*level + opposite.name + " " + opposite.sex + '\n' + buf += u.tostring(level) - return str + return buf def size_to_draw(self): @@ -77,6 +72,13 @@ class Person: return x >= self.x0 and x <= self.x1 and y >= self.y0 and y <= self.y1 + def child_count(self): + totchild = 0 + for u in self.unions: + totchild = totchild + len(u.childs) + return totchild + + def draw_node(self, gc, pc): "Draw a person" @@ -105,6 +107,7 @@ class Person: # Set person position (self.x0, self.y0) = (x, y) (self.x1, self.y1) = (x+const._person_width, y+const._person_height) + self.fontsize = const._person_fontsize if self.unions != []: # Set union position wmargin = const._person_width / const._person_wmargin_ratio @@ -145,7 +148,7 @@ class Person: self.y0 = self.y0 + ((self.y0 * scalerate) / 100) self.x1 = self.x1 + ((self.x1 * scalerate) / 100) self.y1 = self.y1 + ((self.y1 * scalerate) / 100) - self.fontsize = const._person_fontsize + (scalerate / 10) + self.fontsize = self.fontsize + (scalerate / 10) def draw(self, gc, pc): diff --git a/src/tree.py b/src/tree.py new file mode 100644 index 0000000..1dfa8c9 --- /dev/null +++ b/src/tree.py @@ -0,0 +1,103 @@ + +from person import Person +from union import Union + + +# Tree class +class Tree: + "Class to represent a tree: a sum of persons and unions" + + def __init__(self): + "Constructor, init fields to None" + self.root = None + self.persons = [] + self.unions = [] + + + def Person(self, name, sex): + "Add a new person" + p = Person(name, sex) + self.persons.append(p) + return p + + + def Union(self, dad, mum): + "Add a new union" + u = Union(dad, mum) + self.unions.append(u) + return u + + def is_descendant(self, person, root=None): + "Look is person is a descendant of the tree" + start = root + if start is None: + start = self.root + for u in start.unions: + for c in u.childs: + if c == person: + return True + if self.is_descendant(person, c): + return True + return False + + + +# Create the samples family +def sample_family1(): + + tree = Tree() + tree.root = tree.Person("", "M") + + return tree + + +def sample_family2(): + + tree = Tree() + + l = tree.Person("Lucien", "M") + a = tree.Person("Annick", "F") + u = tree.Union(l, a) + + d = tree.Person("Dominique", "M") + u.append_child(d) + au = tree.Person("Anne", "F") + u.append_child(au) + m = tree.Person("Madeleine", "F") + u.append_child(m) + + jp = tree.Person("Jean-Pierre", "M") + j = tree.Person("Julie", "F") + up = tree.Union(jp, j) + up.append_child(l) + c = tree.Person("Christian", "M") + up.append_child(c) + + ub = tree.Union(tree.Person("Jonathan", "M"), j) + ub.append_child(tree.Person("Charlie", "F")) + + rs = tree.Person("Renee", "F") + vm = tree.Person("Vivien", "M") + urv = tree.Union(vm, rs) + urv.append_child(j) + + jr = tree.Person("Jean-Rene", "M") + ua = tree.Union(jr, tree.Person("Micheline", "F")) + ua.append_child(a) + i = tree.Person("Irene", "F") + ua.append_child(i) + ui = tree.Union(tree.Person("Nathan", "M"), i) + ui.append_child(tree.Person("Marie", "F")) + ui.append_child(tree.Person("Noel", "M")) + ui.append_child(tree.Person("Thierry", "M")) + + uc = tree.Union(c, tree.Person("Clarah", "F")) + sa = tree.Person("Sandrine", "F") + uc.append_child(sa) + uc2 = tree.Union(c, tree.Person("Vivianne", "F")) + uc2.append_child(tree.Person("Pierre", "M")) + uc2.append_child(tree.Person("Camille", "F")) + + tree.root = vm + + return tree diff --git a/src/union.py b/src/union.py index 05c3a2d..96016c9 100644 --- a/src/union.py +++ b/src/union.py @@ -49,6 +49,10 @@ class Union: def compute_draw(self, x, y): "Compute union subtree position" + + # Reinit size + self.dad.fontsize = const._person_fontsize + self.mum.fontsize = const._person_fontsize # Compute childs size size = 0 @@ -62,9 +66,8 @@ class Union: else: x = x - (size*const._person_width+(size-1)*wmargin)/2 - # Draw each child + # Compute draw for childs for c in self.childs: - # Draw the child c.compute_draw(x, y) size = c.size_to_draw() x = x + (size*const._person_width+size*wmargin) -- cgit v0.9.1