# Base import import gtk import logging from gettext import gettext as _ # Local import from src.person import Person from src.union import Union from src.tree import Tree from src.tree import empty_tree, sample_family1, sample_family2 import src.const as const # Sugar import try: from sugar.activity import activity from sugar.graphics.toolbutton import ToolButton from gtk import Toolbar const.inSugar = True except ImportError: from sugardummy import * const.inSugar = False # Init position const.tree_initx = 200 const.tree_inity = 200 class RootsActivity(activity.Activity): def __init__(self, handle): "Set up the activity." # Sugar init activity.Activity.__init__(self, handle) # Create toolbox toolbox = activity.ActivityToolbox(self) toolbarview = Toolbar() tool = ToolButton('zoom-out') tool.set_tooltip(_('Zoom out')) tool.set_accelerator(_('minus')) tool.connect('clicked', self.zoom_out) toolbarview.insert(tool, -1) tool = ToolButton('zoom-in') tool.set_tooltip(_('Zoom in')) tool.set_accelerator(_('equal')) tool.connect('clicked', self.zoom_in) 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(_('Test')) tool.connect('clicked', self.sample1) toolbarsample.insert(tool, -1) tool = ToolButton('sample2') tool.set_tooltip(_('Napoleon')) tool.connect('clicked', self.sample2) toolbarsample.insert(tool, -1) toolbox.add_toolbar(_('Samples'), toolbarsample) self.set_toolbox(toolbox) toolbox.show() # Create drawing area self.zoomlevel = 0 self.area = gtk.DrawingArea() 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) self.area.connect("button_release_event", self.release_button) self.area.connect("motion_notify_event", self.mouse_move) self.moving = 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_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) 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 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, tree): "Create and init a tree" self.zoomlevel = 0 self.tree = tree (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 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_btnaddparent.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() 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: deletable = True elif unionscount > 1: deletable = False elif unionscount == 1 and isdescendant: deletable = False elif childcount > 0: 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(isfamily) self.detail_btnaddparent.set_sensitive(person.parents is None and (isascendant or isroot)) 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) 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 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) 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) self.tree.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 self.translate(event.x-self.movingStart[0], event.y-self.movingStart[1]) self.movingStart = (event.x, event.y) else: # Look if a person is under the cursor p = self.tree.person_at(event.x, event.y) if p is not None: # Found one, change cursor 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)) def release_button(self, widget, event): "Mouse button released, stop moving mode" self.area.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.ARROW)) self.moving = False def zoom_in(self, event): "ToolBar button zoom in clicked" self.zoomlevel = self.zoomlevel + 1 self.tree.scale(20) self.redraw() def zoom_out(self, event): "ToolBar button zoom out clicked" if self.zoomlevel == -3: return self.zoomlevel = self.zoomlevel - 1 self.tree.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 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" self.zoomlevel = 0 newchild = self.tree.Person("", "M") 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") else: male = newunion = self.tree.Person("", "M") female = self.selected self.tree.Union(male, female) 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" # 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.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()) def write_file(self, file_path): "Called when activity is saved, save the tree in the file" #self.metadata['current_page'] = '3' file = open(file_path, 'wb') try: self.tree.write_to(file) finally: file.close() def read_file(self, file_path): "Called when activity is loaded, load the tree from the file" file = open(file_path, 'rb') try: tree = Tree().read_from(file) finally: file.close() self.init_tree(tree) # Dummy call to allow running on Windows if not const.inSugar: RootsActivity(0) gtk.main()