From ba84b5a03e2e93ac589c48cea9f585ed75d2b05c Mon Sep 17 00:00:00 2001 From: Don Allingham Date: Wed, 7 Jan 2004 23:55:45 +0000 Subject: [PATCH] New file svn: r2604 --- gramps2/src/plugins/AncestorChart2.py | 710 ++++++++++++++++++++++++++ 1 file changed, 710 insertions(+) create mode 100644 gramps2/src/plugins/AncestorChart2.py diff --git a/gramps2/src/plugins/AncestorChart2.py b/gramps2/src/plugins/AncestorChart2.py new file mode 100644 index 000000000..d2a5845c8 --- /dev/null +++ b/gramps2/src/plugins/AncestorChart2.py @@ -0,0 +1,710 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2003 Donald N. Allingham +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +"Graphical Reports/Ancestor Chart" + +#------------------------------------------------------------------------ +# +# python modules +# +#------------------------------------------------------------------------ +import os +import string +import math + +#------------------------------------------------------------------------ +# +# GRAMPS modules +# +#------------------------------------------------------------------------ +import GrampsCfg +import BaseDoc +import Report +import Errors +import FontScale +from QuestionDialog import ErrorDialog +from SubstKeywords import SubstKeywords +from gettext import gettext as _ + +_BORN = _('b.') +_DIED = _('d.') + +def log2(val): + return int(math.log10(val)/math.log10(2)) + +#------------------------------------------------------------------------ +# +# pt2cm - convert points to centimeters +# +#------------------------------------------------------------------------ +def pt2cm(pt): + return (float(pt)/72.0)*(254.0/100.0) + +#------------------------------------------------------------------------ +# +# Layout class +# +#------------------------------------------------------------------------ +class GenChart : + + def __init__(self,generations): + self.generations = generations + self.size = (2**(generations)) + self.array = [None]*(self.size) + self.map = {} + self.compress_map = {} + + for i in range(0,(self.size)): + self.array[i] = [0]*generations + + self.max_x = 0 + + def set(self,index,value): + x = log2(index) + y = index - (2**x) + delta = int((self.size/(2**(x)))) + new_y = int((delta/2) + (y)*delta) + self.array[new_y][x] = (value,index) + self.max_x = max(x,self.max_x) + self.map[value] = (new_y,x) + + def index_to_xy(self,index): + if index: + x = log2(index) + ty = index - (2**x) + delta = int(self.size/(2**x)) + y = int(delta/2 + ty*delta) + else: + x = 0 + y = self.size/2 + + if self.compress: + return (x,self.compress_map[y]) + else: + return (x,y) + + def get(self,index): + (x,y) = self.index_to_xy(index) + return self.array[y][x] + + def get_xy(self,x,y): + return self.array[y][x] + + def dimensions(self): + return (len(self.array),self.max_x+1) + + def compress(self): + new_map = {} + new_array = [] + old_y = 0 + new_y = 0 + + for i in self.array: + if i and self.not_blank(i): + self.compress_map[old_y] = new_y + new_array.append(i) + x = 0 + for entry in i: + if entry: + new_map[entry] = (new_y,x) + x =+ 1 + new_y += 1 + old_y += 1 + self.array = new_array + self.map = new_map + + def display(self): + index = 0 + for i in self.array: + print "%04d" % index,i + index=index+1 + + def not_blank(self,line): + for i in line: + if i: + return 1 + return 0 + +#------------------------------------------------------------------------ +# +# AncestorChart +# +#------------------------------------------------------------------------ +class AncestorChart: + + def __init__(self,database,person,max,display,doc,output,newpage=0): + self.doc = doc + self.doc.creator(database.getResearcher().getName()) + self.map = {} + self.text = {} + self.start = person + self.max_generations = max + self.output = output + self.box_width = 0 + self.height = 0 + self.lines = 0 + self.display = display + self.newpage = newpage + if output: + self.standalone = 1 + self.doc.open(output) + else: + self.standalone = 0 + self.font = self.doc.style_list["AC-Normal"].get_font() + self.calc() + + keys = self.map.keys() + keys.sort() + max_key = log2(keys[-1]) + + self.genchart = GenChart(max_key+1) + for key in self.map.keys(): + self.genchart.set(key,self.map[key]) + + def filter(self,person,index): + """traverse the ancestors recursively until either the end + of a line is found, or until we reach the maximum number of + generations that we want to deal with""" + + if person == None or index >= 2**self.max_generations: + return + self.map[index] = person + + self.text[index] = [] + + subst = SubstKeywords(person) + + for line in self.display: + self.text[index].append(subst.replace(line)) + + for line in self.text[index]: + self.box_width = max(self.box_width,FontScale.string_width(self.font,line)) + + self.lines = max(self.lines,len(self.text[index])) + + family = person.getMainParents() + if family != None: + self.filter(family.getFather(),index*2) + self.filter(family.getMother(),(index*2)+1) + + def write_report(self): + + if self.newpage: + self.doc.page_break() + + generation = 1 + done = 0 + page = 1 + while done == 0: + done = 1 + start = 2**(generation-1) + for index in range(start, (start*2)): + values = [] + self.get_numbers(index,1,values) + if len(values) > 1 or generation == 1: + done = 0 + self.print_page(index, generation, page) + page = page + 1 + generation = generation + 3 + if self.standalone: + self.doc.close() + + def calc(self): + """ + calc - calculate the maximum width that a box needs to be. From + that and the page dimensions, calculate the proper place to put + the elements on a page. + """ + + self.filter(self.start,1) + + self.box_pad_pts = 5 + uh = self.doc.get_usable_height() + uw = self.doc.get_usable_width() - pt2cm(2*self.box_pad_pts) + + self.height = self.lines*pt2cm(1.25*self.font.get_size()) + + self.box_width = pt2cm(self.box_width) + + maxh = int(uh/self.height) + maxw = int(uw/self.box_width) + + if log2(maxh) < maxw: + self.generations_per_page = int(log2(maxh)) + else: + self.generations_per_page = maxw + 1 + acty = 2**self.generations_per_page + + # build array of x indices + + xstart = pt2cm(2) + ystart = -self.height/2.0 + self.delta = pt2cm(10) + self.box_width +# delta = pt2cm(10)+ uw/self.generations_per_page + + self.x = [ xstart ] + self.y = [ ystart + uh/2.0 ] + + for val in range(1,self.generations_per_page): + xstart += self.delta + self.x.append(xstart) + + div = 2**(val+1) + for i in range(0,div/2): + self.y.append(ystart + (1+(i*2))*uh/div) + + g = BaseDoc.GraphicsStyle() + g.set_height(self.height) + g.set_width(self.box_width) + g.set_paragraph_style("AC-Normal") + g.set_shadow(1) + g.set_fill_color((255,255,255)) + self.doc.add_draw_style("box",g) + + g = BaseDoc.GraphicsStyle() + self.doc.add_draw_style("line",g) + if self.standalone: + self.doc.init() + + def get_numbers(self,start,index,vals): + if index > 4: + return + if self.map.has_key(start): + vals.append(start) + self.get_numbers(start*2,index+1,vals) + self.get_numbers((start*2)+1,index+1,vals) + + def print_page(self,start,generation, page): + self.genchart.compress() +# self.genchart.display() + (maxy,maxx) = self.genchart.dimensions() + self.doc.start_page() + for y in range(0,maxy): + for x in range(0,maxx): + value = self.genchart.get_xy(x,y) + if value: + (person,index) = value + text = string.join(self.text[index],"\n") + self.doc.draw_box("box",text,x*self.delta,y*self.height) + if index > 1: + parent = int(index>>1) + (px,py) = self.genchart.index_to_xy(parent) + x1 = px*self.delta+(0.5 * self.delta) + x2 = x*self.delta + if py > y: + y2 = y * self.height + 0.5*self.height + y1 = py * self.height + 0.5*self.height + else: + y1 = (py+1) * self.height + y2 = y * self.height + 0.5*self.height + self.doc.draw_line("line",x1,y1,x1,y2) + self.doc.draw_line("line",x1,y2,x2,y2) + + self.doc.end_page() + + def draw_graph(self,index,start,level): + if self.map.has_key(start) and index < 2**self.generations_per_page: + text = self.text[start] + + name = string.join(text,"\n") + self.doc.draw_box("box",name,self.x[level],self.y[index-1]) + + if index > 1: + old_index = int(index/2)-1 + x2 = self.x[level] + x1 = self.x[level-1]+(self.box_width*0.75) + if index % 2 == 1: + y1 = self.y[old_index]+self.height + else: + y1 = self.y[old_index] + + y2 = self.y[index-1]+(self.height/2.0) + self.doc.draw_line("line",x1,y1,x1,y2) + self.doc.draw_line("line",x1,y2,x2,y2) + self.draw_graph(index*2,start*2,level+1) + self.draw_graph((index*2)+1,(start*2)+1,level+1) + +#------------------------------------------------------------------------ +# +# +# +#------------------------------------------------------------------------ +def _make_default_style(default_style): + """Make the default output style for the Ancestor Chart report.""" + f = BaseDoc.FontStyle() + f.set_size(9) + f.set_type_face(BaseDoc.FONT_SANS_SERIF) + p = BaseDoc.ParagraphStyle() + p.set_font(f) + p.set_description(_('The basic style used for the text display.')) + default_style.add_style("AC-Normal",p) + +#------------------------------------------------------------------------ +# +# AncestorChartDialog +# +#------------------------------------------------------------------------ +class AncestorChartDialog(Report.DrawReportDialog): + + report_options = {} + + def __init__(self,database,person): + Report.DrawReportDialog.__init__(self,database,person,self.report_options) + + def get_title(self): + """The window title for this dialog""" + return "%s - %s - GRAMPS" % (_("Ancestor Chart"),_("Graphical Reports")) + + def get_header(self, name): + """The header line at the top of the dialog contents.""" + return _("Ancestor Chart for %s") % name + + def get_target_browser_title(self): + """The title of the window created when the 'browse' button is + clicked in the 'Save As' frame.""" + return _("Save Ancestor Chart") + + def get_stylesheet_savefile(self): + """Where to save user defined styles for this report.""" + return _style_file + + def get_report_generations(self): + """Default to 10 generations, no page breaks.""" + return (10, 0) + + def get_report_extra_textbox_info(self): + """Label the textbox and provide the default contents.""" + return (_("Display Format"), "$n\n%s $b\n%s $d" % (_BORN,_DIED), + _("Allows you to customize the data in the boxes in the report")) + + def make_default_style(self): + _make_default_style(self.default_style) + + def make_report(self): + """Create the object that will produce the Ancestor Chart. + All user dialog has already been handled and the output file + opened.""" + + try: + MyReport = AncestorChart(self.db, self.person, + self.max_gen, self.report_text, self.doc,self.target_path) + MyReport.write_report() + except Errors.ReportError, msg: + (m1,m2) = msg.messages() + ErrorDialog(m1,m2) + except Errors.FilterError, msg: + (m1,m2) = msg.messages() + ErrorDialog(m1,m2) + except: + import DisplayTrace + DisplayTrace.DisplayTrace() + +#------------------------------------------------------------------------ +# +# entry point +# +#------------------------------------------------------------------------ +def report(database,person): + AncestorChartDialog(database,person) + +#------------------------------------------------------------------------ +# +# Set up sane defaults for the book_item +# +#------------------------------------------------------------------------ +_style_file = "ancestor_chart.xml" +_style_name = "default" + +_person_id = "" +_max_gen = 10 +_disp_format = [ "$n", "%s $b" % _BORN, "%s $d" % _DIED ] +_options = ( _person_id, _max_gen, _disp_format ) + +#------------------------------------------------------------------------ +# +# Book Item Options dialog +# +#------------------------------------------------------------------------ +class AncestorChartBareDialog(Report.BareReportDialog): + + def __init__(self,database,person,opt,stl): + + self.options = opt + self.db = database + if self.options[0]: + self.person = self.db.getPerson(self.options[0]) + else: + self.person = person + self.style_name = stl + + Report.BareReportDialog.__init__(self,database,self.person) + + self.max_gen = int(self.options[1]) + self.disp_format = string.join(self.options[2],'\n') + self.new_person = None + + self.generations_spinbox.set_value(self.max_gen) + self.extra_textbox.get_buffer().set_text( + self.disp_format,len(self.disp_format)) + + self.window.run() + + #------------------------------------------------------------------------ + # + # Customization hooks + # + #------------------------------------------------------------------------ + def get_title(self): + """The window title for this dialog""" + return "%s - GRAMPS Book" % (_("Ancestor Chart")) + + def get_header(self, name): + """The header line at the top of the dialog contents""" + return _("Ancestor Chart for GRAMPS Book") + + def get_stylesheet_savefile(self): + """Where to save styles for this report.""" + return _style_file + + def get_report_generations(self): + """Default to 10 generations, no page breaks.""" + return (10, 0) + + def get_report_extra_textbox_info(self): + """Label the textbox and provide the default contents.""" + return (_("Display Format"), "$n\n%s $b\n%s $d" % (_BORN,_DIED), + _("Allows you to customize the data in the boxes in the report")) + + def make_default_style(self): + _make_default_style(self.default_style) + + def on_cancel(self, obj): + pass + + def on_ok_clicked(self, obj): + """The user is satisfied with the dialog choices. Parse all options + and close the window.""" + + # Preparation + self.parse_style_frame() + self.parse_report_options_frame() + + if self.new_person: + self.person = self.new_person + self.options = ( self.person.getId(), self.max_gen, self.report_text ) + self.style_name = self.selected_style.get_name() + +#------------------------------------------------------------------------ +# +# Function to write Book Item +# +#------------------------------------------------------------------------ +def write_book_item(database,person,doc,options,newpage=0): + """Write the Ancestor Chart using options set. + All user dialog has already been handled and the output file opened.""" + try: + if options[0]: + person = database.getPerson(options[0]) + max_gen = int(options[1]) + disp_format = options[2] + return AncestorChart(database, person, max_gen, + disp_format, doc, None, newpage ) + except Errors.ReportError, msg: + (m1,m2) = msg.messages() + ErrorDialog(m1,m2) + except Errors.FilterError, msg: + (m1,m2) = msg.messages() + ErrorDialog(m1,m2) + except: + import DisplayTrace + DisplayTrace.DisplayTrace() + +#------------------------------------------------------------------------ +# +# +# +#------------------------------------------------------------------------ +def get_xpm_image(): + return [ + "48 48 85 1", + " c None", + ". c #887D6C", + "+ c #8C8A87", + "@ c #787775", + "# c #766D5F", + "$ c #67655F", + "% c #5E5A54", + "& c #55524C", + "* c #BBBAB8", + "= c #B7AFA2", + "- c #A9A5A0", + "; c #99948A", + "> c #FAFAFA", + ", c #F8F6F2", + "' c #F6F2EC", + ") c #E6E5E5", + "! c #D2CCBF", + "~ c #C7C6C3", + "{ c #413F3F", + "] c #DCD9D4", + "^ c #322E2B", + "/ c #4F4E4C", + "( c #908F8D", + "_ c #989897", + ": c #8A8986", + "< c #898885", + "[ c #F5EEE5", + "} c #F5F5F5", + "| c #979695", + "1 c #888784", + "2 c #8B8A87", + "3 c #1A1A1A", + "4 c #858582", + "5 c #949390", + "6 c #858480", + "7 c #92918E", + "8 c #8F8E8B", + "9 c #8E8D8A", + "0 c #797773", + "a c #7B7975", + "b c #81807C", + "c c #817F7C", + "d c #989796", + "e c #807E7B", + "f c #8C8B88", + "g c #E3CAA5", + "h c #F2EADF", + "i c #DDCDB4", + "j c #8E8E8B", + "k c #888785", + "l c #EFE4D2", + "m c #969694", + "n c #9F9F9D", + "o c #E6D4B7", + "p c #A5967E", + "q c #8A8987", + "r c #EBDCC4", + "s c #878683", + "t c #9B9995", + "u c #9A9892", + "v c #807F7B", + "w c #7E7C79", + "x c #8E8C88", + "y c #8F8E8C", + "z c #8D8B88", + "A c #B59871", + "B c #878581", + "C c #8E8B87", + "D c #848480", + "E c #898785", + "F c #8A8886", + "G c #7D7B77", + "H c #8D8C89", + "I c #8B8A86", + "J c #918F8B", + "K c #989795", + "L c #BBA382", + "M c #8D8B86", + "N c #868480", + "O c #8E8C87", + "P c #8E8B86", + "Q c #8A8985", + "R c #807F7A", + "S c #8D8A84", + "T c #898884", + " ", + " ", + " .+....@@#####$$$%$%&$@ ", + " .**************=*-;+%%@ ", + " .*>>,>>>>>>,>>>>')!*..;& ", + " .*>>>>>>>>>>>>>>>,)!=@~;{ ", + " .*,>>>>>>>>>>>>>>>,]]%)~+^ ", + " .*>>>>>>>>>>>>>>>>>))/>)~+^ ", + " .*>>>>>>>>>>>>>>>>>(_/>>)~+^ ", + " .*>>>>>>>>>>>>>>>>>:>/)>>)~+{ ", + " @*>>>>>>>>>>>>>>>>><>/]'>>)~;& ", + " @*>>>>>>>>>>>>>>>>>:>/~][>>)~;$ ", + " #*>>>>>>>>>}}|1<<2>:>/33^{{%$@$@ ", + " .*>>>>>>>>>4:<<<<<56>)~*-;+@$%{$ ", + " #*>>>>>>>>><>|<1<7>8>>)!~=-;+@&{ ", + " #*>>>>>>>>><>>>>>>>9>>,]!~*-;+${ ", + " #*>>>>>>>>><>>>>>>>8>>,))~~*-;@^ ", + " #*>>>>>>>>><>>>>>>>:>(000a!~*-@^ ", + " #*>>>>>>>>>1>>>>>>>b2<<<1c]~~*.^ ", + " #*>>>>>>>>><>>>>>>>,>de<>>>>>>>><>>>>>>,,,''[h]]ii~+^ ", + " $*>>jkkkkj><>>>>>,>'''[[hl]]ig;^ ", + " $*>>mkkkkjn<>>>>>,,'''h[hl]o!!p^ ", + " $*>>jkkkkq><>>>>,'''[)[hhll]i!p^ ", + " $*>>>>>>>>><>>>,,'),[hh)llrro!p^ ", + " $*>>>>>>>>><>>,,'''h[hhhllrriip^ ", + " $*>>>>>>>>><>,'''h[hhlllllrroip^ ", + " %*>>>>>>>>><,''''[[hh|>>>>>>>><'''hhh)tu<>>>>>>>,<''['[[hxly<<>>>>>>,,<'hh)hhlxllrrrrrroiA^ ", + " %*>>>>>>,''1[[[[hllxlrlrroooooA^ ", + " %*>>>>>,,''>>>,'''hDEF<<>>,,'''h)hJ<1>,''[[h[[hllllrlCroroooggogA^ ", + " &*>,,''[h[hlhllrlrrCroooooggggA^ ", + " &=,''[[[[hlhllllrrrMoqkk1NogggL^ ", + " &*''''h)hhlllrrrrrrOPQ