1#
2# Gramps - a GTK+/GNOME based genealogy program
3#
4# Copyright (C) 2000-2007  Donald N. Allingham
5# Copyright (C) 2007-2012  Brian G. Matherly
6# Copyright (C) 2010       Jakim Friant
7# Copyright (C) 2009-2010  Craig J. Anderson
8# Copyright (C) 2014       Paul Franklin
9#
10# This program is free software; you can redistribute it and/or modify
11# it under the terms of the GNU General Public License as published by
12# the Free Software Foundation; either version 2 of the License, or
13# (at your option) any later version.
14#
15# This program is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License
21# along with this program; if not, write to the Free Software
22# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23#
24
25"""
26Reports/Graphical Reports/Familial Tree
27Reports/Graphical Reports/Personal Tree
28"""
29
30#------------------------------------------------------------------------
31#
32# Gramps modules
33#
34#------------------------------------------------------------------------
35from gramps.gen.const import GRAMPS_LOCALE as glocale
36_ = glocale.translation.sgettext
37from gramps.gen.errors import ReportError
38from gramps.gen.plug.menu import (TextOption, NumberOption, BooleanOption,
39                                  EnumeratedListOption, StringOption,
40                                  PersonOption, FamilyOption)
41from gramps.gen.plug.report import Report, MenuReportOptions, stdoptions
42from gramps.gen.plug.report import utils
43from gramps.gen.plug.docgen import (FontStyle, ParagraphStyle, GraphicsStyle,
44                                    FONT_SANS_SERIF, PARA_ALIGN_CENTER)
45from gramps.plugins.lib.libtreebase import *
46from gramps.gen.proxy import CacheProxyDb
47from gramps.gen.display.name import displayer as _nd
48from gramps.gen.utils.db import family_name
49
50PT2CM = utils.pt2cm
51
52#------------------------------------------------------------------------
53#
54# Constants
55#
56#------------------------------------------------------------------------
57_BORN = _("birth abbreviation|b."),
58_DIED = _("death abbreviation|d."),
59_MARR = _("marriage abbreviation|m."),
60
61_RPT_NAME = 'descend_chart'
62
63#------------------------------------------------------------------------
64#
65# Box classes
66#
67#------------------------------------------------------------------------
68class DescendantBoxBase(BoxBase):
69    """
70    Base for all descendant boxes.
71    Set the boxstr and some new attributes that are needed
72    """
73
74    def __init__(self, boxstr):
75        BoxBase.__init__(self)
76        self.boxstr = boxstr
77        self.linked_box = None
78        self.father = None
79
80    def calc_text(self, database, person, family):
81        """  A single place to calculate box text """
82
83        gui = GuiConnect()
84        calc = gui.calc_lines(database)
85        self.text = calc.calc_lines(person, family,
86                                    gui.working_lines(self))
87
88class PersonBox(DescendantBoxBase):
89    """
90    Calculates information about the box that will print on a page
91    """
92
93    def __init__(self, level, boldable=0):
94        DescendantBoxBase.__init__(self, "CG2-box")
95        self.level = level
96
97    def set_bold(self):
98        """  update me to a bolded box """
99        self.boxstr = "CG2b-box"
100
101class FamilyBox(DescendantBoxBase):
102    """
103    Calculates information about the box that will print on a page
104    """
105
106    def __init__(self, level):
107        DescendantBoxBase.__init__(self, "CG2-fam-box")
108        self.level = level
109
110class PlaceHolderBox(BoxBase):
111    """
112    I am a box that does not print.  I am used to make sure information
113    does not run over areas that we don't want information (boxes)
114    """
115
116    def __init__(self, level):
117        BoxBase.__init__(self)
118        self.boxstr = "None"
119        self.level = level
120        self.line_to = None
121        self.linked_box = None
122
123    def calc_text(self, database, person, family):
124        """ move along.  Nothing to see here """
125        return
126
127
128#------------------------------------------------------------------------
129#
130# Titles Class(es)
131#
132#------------------------------------------------------------------------
133class DescendantTitleBase(TitleBox):
134    def __init__(self, dbase, doc, locale, name_displayer,
135                 boxstr="CG2-Title-box"):
136        self._nd = name_displayer
137        TitleBox.__init__(self, doc, boxstr)
138        self.database = dbase
139        self._ = locale.translation.sgettext
140
141    def descendant_print(self, person_list, person_list2=[]):
142        """ calculate the Descendant title
143        Person_list will always be passed
144        If in the Family reports and there are two families, person_list2
145        will be used.
146        """
147
148        if len(person_list) == len(person_list2) == 1:
149            person_list = person_list + person_list2
150            person_list2 = []
151
152        names = self._get_names(person_list, self._nd)
153
154        if person_list2:
155            names2 = self._get_names(person_list2, self._nd)
156            if len(names) + len(names2) == 3:
157                if len(names) == 1:
158                    title = self._("Descendant Chart for %(person)s and "
159                                   "%(father1)s, %(mother1)s") % {
160                                       'person':  names[0],
161                                       'father1': names2[0],
162                                       'mother1': names2[1],
163                                       }
164                else: # Should be 2 items in names list
165                    title = self._("Descendant Chart for %(person)s, "
166                                   "%(father1)s and %(mother1)s") % {
167                                       'father1': names[0],
168                                       'mother1': names[1],
169                                       'person':  names2[0],
170                                       }
171            else: # Should be 2 items in both names and names2 lists
172                title = self._("Descendant Chart for %(father1)s, %(father2)s "
173                               "and %(mother1)s, %(mother2)s") % {
174                                   'father1': names[0],
175                                   'mother1': names[1],
176                                   'father2': names2[0],
177                                   'mother2': names2[1],
178                                   }
179        else: # No person_list2: Just one family
180            if len(names) == 1:
181                title = self._(
182                    "Descendant Chart for %(person)s") % {'person': names[0]}
183            else: # Should be two items in names list
184                title = self._("Descendant Chart for %(father)s and "
185                               "%(mother)s") % {
186                                   'father': names[0],
187                                   'mother': names[1],
188                                   }
189        return title
190
191    def get_parents(self, family_id):
192        """ For a family_id, return the father and mother """
193
194        family1 = self.database.get_family_from_gramps_id(family_id)
195        father_h = family1.get_father_handle()
196        mother_h = family1.get_mother_handle()
197
198        parents = [self.database.get_person_from_handle(handle)
199                   for handle in [father_h, mother_h] if handle]
200
201        return parents
202
203class TitleNone(TitleNoDisplay):
204    """No Title class for the report """
205
206    def __init__(self, dbase, doc, locale):
207        TitleNoDisplay.__init__(self, doc, "CG2-Title-box")
208        self._ = locale.translation.sgettext
209
210    def calc_title(self, persons):
211        """Calculate the title of the report"""
212        #we want no text, but need a text for the TOC in a book!
213        self.mark_text = self._('Descendant Graph')
214        self.text = ''
215
216class TitleDPY(DescendantTitleBase):
217    """Descendant (Person yes start with parents) Chart
218    Title class for the report """
219
220    def __init__(self, dbase, doc, locale, name_displayer):
221        DescendantTitleBase.__init__(self, dbase, doc, locale, name_displayer)
222
223    def calc_title(self, person_id):
224        """Calculate the title of the report"""
225
226        center = self.database.get_person_from_gramps_id(person_id)
227        family2_h = center.get_main_parents_family_handle()
228        if family2_h:
229            family2 = self.database.get_family_from_handle(family2_h)
230        else:
231            family2 = None
232
233        person_list = None
234        if family2:
235            father2_h = family2.get_father_handle()
236            mother2_h = family2.get_mother_handle()
237            person_list = [self.database.get_person_from_handle(handle)
238                           for handle in [father2_h, mother2_h] if handle]
239
240        if not person_list:
241            person_list = [center]
242
243        self.text = self.descendant_print(person_list)
244        self.set_box_height_width()
245
246class TitleDPN(DescendantTitleBase):
247    """Descendant (Person no start with parents) Chart
248    Title class for the report """
249
250    def __init__(self, dbase, doc, locale, name_displayer):
251        DescendantTitleBase.__init__(self, dbase, doc, locale, name_displayer)
252
253    def calc_title(self, person_id):
254        """Calculate the title of the report"""
255
256        center = self.database.get_person_from_gramps_id(person_id)
257
258        title = self.descendant_print([center])
259        self.text = title
260        self.set_box_height_width()
261
262class TitleDFY(DescendantTitleBase):
263    """Descendant (Family yes start with parents) Chart
264    Title class for the report """
265
266    def __init__(self, dbase, doc, locale, name_displayer):
267        DescendantTitleBase.__init__(self, dbase, doc, locale, name_displayer)
268
269    def get_parent_list(self, person):
270        """ return a list of my parents.  If none, return me """
271        if not person:
272            return None
273
274        parent_list = None
275        family_h = person.get_main_parents_family_handle()
276        if family_h:
277            family = self.database.get_family_from_handle(family_h)
278        else:
279            family = None
280        if family:  # family = fathers parents
281            father_h = family.get_father_handle()
282            mother_h = family.get_mother_handle()
283            parent_list = [self.database.get_person_from_handle(handle)
284                           for handle in [father_h, mother_h] if handle]
285
286        return parent_list or [person]
287
288    def calc_title(self, family_id):
289        """Calculate the title of the report"""
290
291        my_parents = self.get_parents(family_id)
292
293        dad_parents = self.get_parent_list(my_parents[0])
294
295        mom_parents = []
296        if len(my_parents) > 1:
297            if not dad_parents:
298                dad_parents = self.get_parent_list(my_parents[1])
299            else:
300                mom_parents = self.get_parent_list(my_parents[1])
301
302        self.text = self.descendant_print(dad_parents, mom_parents)
303        self.set_box_height_width()
304
305class TitleDFN(DescendantTitleBase):
306    """Descendant (Family no start with parents) Chart
307    Title class for the report """
308
309    def __init__(self, dbase, doc, locale, name_displayer):
310        DescendantTitleBase.__init__(self, dbase, doc, locale, name_displayer)
311
312    def calc_title(self, family_id):
313        """Calculate the title of the report"""
314
315        self.text = self.descendant_print(self.get_parents(family_id))
316        self.set_box_height_width()
317
318class TitleF(DescendantTitleBase):
319    """Family Chart Title class for the report """
320
321    def __init__(self, dbase, doc, locale, name_displayer):
322        DescendantTitleBase.__init__(self, dbase, doc, locale, name_displayer)
323
324    def calc_title(self, family_id):
325        """Calculate the title of the report"""
326        parents = self.get_parents(family_id)
327
328        names = self._get_names(parents, self._nd)
329
330        if len(parents) == 1:
331            title = self._(
332                "Family Chart for %(person)s") % {'person':  names[0]}
333        elif len(parents) == 2:
334            title = self._(
335                "Family Chart for %(father1)s and %(mother1)s") % {
336                    'father1':  names[0], 'mother1': names[1]}
337        #else:
338        #    title = str(tmp) + " " + str(len(tmp))
339        self.text = title
340        self.set_box_height_width()
341
342class TitleC(DescendantTitleBase):
343    """Cousin Chart Title class for the report """
344
345    def __init__(self, dbase, doc, locale, name_displayer):
346        DescendantTitleBase.__init__(self, dbase, doc, locale, name_displayer)
347
348    def calc_title(self, family_id):
349        """Calculate the title of the report"""
350
351        family = self.database.get_family_from_gramps_id(family_id)
352
353        kids = [self.database.get_person_from_handle(kid.ref)
354                for kid in family.get_child_ref_list()]
355
356        #ok we have the children.  Make a title off of them
357        # translators: needed for Arabic, ignore otherwise
358        cousin_names = self._(', ').join(self._get_names(kids, self._nd))
359
360        self.text = self._(
361            "Cousin Chart for %(names)s") % {'names' : cousin_names}
362
363        self.set_box_height_width()
364
365
366#------------------------------------------------------------------------
367#
368# Class RecurseDown
369#
370#------------------------------------------------------------------------
371class RecurseDown:
372    """
373    The main recursive functions that will use add_person to make
374    the tree of people (Descendants) to be included within the report.
375    """
376    def __init__(self, dbase, canvas):
377        self.database = dbase
378        self.canvas = canvas
379
380        self.families_seen = set()
381        self.cols = []
382        self.__last_direct = []
383
384        gui = GuiConnect()
385        self.do_parents = gui.get_val('show_parents')
386        self.max_generations = gui.get_val('maxgen')
387        self.max_spouses = gui.get_val('maxspouse')
388        self.inlc_marr = gui.get_val("inc_marr")
389        if not self.max_spouses:
390            self.inlc_marr = False
391        self.spouse_indent = gui.get_val('ind_spouse')
392
393        #is the option even available?
394        self.bold_direct = gui.get_val('bolddirect')
395        #can we bold direct descendants?
396        #bold_now will have only three values
397        #0 - no bolding
398        #1 - Only bold the first person
399        #2 - Bold all direct descendants
400        self.bold_now = 0
401        gui = None
402
403    def add_to_col(self, box):
404        """
405        Add the box to a column on the canvas.  we will do these things:
406          set the .linked_box attrib for the boxs in this col
407          get the height and width of this box and set it no the column
408          also we set the .x_cm to any s_level (indentation) here
409            we will calculate the real .x_cm later (with indentation)
410        """
411
412        level = box.level[0]
413        #make the column list of people
414        while len(self.cols) <= level:
415            self.cols.append(None)
416            self.__last_direct.append(None)
417
418        if self.cols[level]:  #if (not the first box in this column)
419            last_box = self.cols[level]
420            last_box.linked_box = box
421
422            #calculate the .y_cm for this box.
423            box.y_cm = last_box.y_cm
424            box.y_cm += last_box.height
425            if last_box.boxstr in ["CG2-box", "CG2b-box"]:
426                box.y_cm += self.canvas.report_opts.box_shadow
427
428            if box.boxstr in ["CG2-box", "CG2b-box"]:
429                box.y_cm += self.canvas.report_opts.box_pgap
430            else:
431                box.y_cm += self.canvas.report_opts.box_mgap
432
433            if box.level[1] == 0 and self.__last_direct[level]:
434                #ok, a new direct descendant.
435                #print level, box.father is not None, \
436                # self.__last_direct[level].father is not None, box.text[0], \
437                # self.__last_direct[level].text[0]
438                if box.father != self.__last_direct[level].father and \
439                   box.father != self.__last_direct[level]:
440                    box.y_cm += self.canvas.report_opts.box_pgap
441
442        self.cols[level] = box
443        if box.level[1] == 0:
444            self.__last_direct[level] = box
445
446        if self.spouse_indent:
447            box.x_cm = self.canvas.report_opts.spouse_offset * box.level[1]
448        else:
449            box.x_cm = 0.0
450
451        self.canvas.set_box_height_width(box)
452
453    def add_person_box(self, level, indi_handle, fams_handle, father):
454        """ Makes a person box and add that person into the Canvas. """
455        myself = PersonBox(level)
456        myself.father = father
457
458        if myself.level[1] == 0 and self.bold_direct and self.bold_now:
459            if self.bold_now == 1:
460                self.bold_now = 0
461            myself.set_bold()
462
463        if level[1] == 0 and father and myself.level[0] != father.level[0]:
464            #I am a child
465            if father.line_to:
466                line = father.line_to
467            else:
468                line = LineBase(father)
469                father.line_to = line
470                #self.canvas.add_line(line)
471
472            line.end.append(myself)
473
474        #calculate the text.
475        myself.calc_text(self.database, indi_handle, fams_handle)
476
477        if indi_handle:
478            myself.add_mark(self.database,
479                            self.database.get_person_from_handle(indi_handle))
480
481        self.add_to_col(myself)
482
483        self.canvas.add_box(myself)
484
485        return myself
486
487    def add_marriage_box(self, level, indi_handle, fams_handle, father):
488        """ Makes a marriage box and add that person into the Canvas. """
489        myself = FamilyBox(level)
490
491        #if father is not None:
492        #    myself.father = father
493        #calculate the text.
494        myself.calc_text(self.database, indi_handle, fams_handle)
495
496        self.add_to_col(myself)
497
498        self.canvas.add_box(myself)
499
500        return myself
501
502    def recurse(self, person_handle, x_level, s_level, father):
503        """traverse the ancestors recursively until
504        either the end of a line is found,
505        or until we reach the maximum number of generations
506        or we reach the max number of spouses
507        that we want to deal with"""
508
509        if not person_handle:
510            return
511        if x_level > self.max_generations:
512            return
513        if s_level > 0 and s_level == self.max_spouses:
514            return
515        if person_handle in self.families_seen:
516            return
517
518        myself = None
519        person = self.database.get_person_from_handle(person_handle)
520        family_handles = person.get_family_handle_list()
521        if s_level == 0:
522            val = family_handles[0] if family_handles else None
523            myself = self.add_person_box((x_level, s_level),
524                                         person_handle, val, father)
525
526        marr = None
527        spouse = None
528
529        if s_level == 1:
530            tmp_bold = self.bold_now
531            self.bold_now = 0
532
533        for family_handle in family_handles:
534            if family_handle not in self.families_seen:
535                self.families_seen.add(family_handle)
536
537                family = self.database.get_family_from_handle(family_handle)
538
539                #Marriage box if the option is there.
540                if self.inlc_marr and self.max_spouses > 0:
541                    marr = self.add_marriage_box((x_level, s_level+1),
542                                                 person_handle, family_handle,
543                                                 father if s_level else myself)
544
545                spouse_handle = utils.find_spouse(person, family)
546                if (self.max_spouses > s_level and
547                        spouse_handle not in self.families_seen):
548                    def _spouse_box(who):
549                        return self.add_person_box((x_level, s_level+1),
550                                                   spouse_handle,
551                                                   family_handle, who)
552                    if s_level > 0:
553                        spouse = _spouse_box(father)
554                    elif self.inlc_marr:
555                        spouse = _spouse_box(marr)
556                    else:
557                        spouse = _spouse_box(myself)
558
559                mykids = [kid.ref for kid in family.get_child_ref_list()]
560
561                def _child_recurse(who):
562                    self.recurse(child_ref, x_level+1, 0, who)
563                for child_ref in mykids:
564                    if self.inlc_marr and self.max_spouses > 0:
565                        _child_recurse(marr)
566                    elif spouse:
567                        _child_recurse(spouse)
568                    else:
569                        _child_recurse(myself)
570
571                if self.max_spouses > s_level and \
572                   spouse_handle not in self.families_seen:
573                    #spouse_handle = utils.find_spouse(person,family)
574                    self.recurse(spouse_handle, x_level, s_level+1, spouse)
575
576        if s_level == 1:
577            self.bold_now = tmp_bold
578
579    def add_family(self, level, family, father2):
580        """
581        Adds a family into the canvas.
582        only will be used for my direct grandparents, and my parents only.
583        """
584
585        family_h = family.get_handle()
586        father_h = family.get_father_handle()
587        mother_h = family.get_mother_handle()
588
589        self.bold_now = 2
590        if father_h:
591            father_b = self.add_person_box(
592                (level, 0), father_h, family_h, father2)
593        else:
594            father_b = self.add_person_box(
595                (level, 0), None, None, father2)
596        retrn = [father_b]
597
598        if self.inlc_marr:
599            family_b = self.add_marriage_box(
600                (level, 1), father_h, family_h, father_b)
601            retrn.append(family_b)
602        self.families_seen.add(family_h)
603
604        if mother_h:
605            mother_b = self.add_person_box(
606                (level, 0), mother_h, family_h, father_b)
607        else:
608            mother_b = self.add_person_box(
609                (level, 0), None, None, father_b)
610        retrn.append(mother_b)
611
612        family_line = family_b if self.inlc_marr else father_b
613        for child_ref in family.get_child_ref_list():
614            self.recurse(child_ref.ref, level+1, 0, family_line)
615
616        self.bold_now = 0
617
618        #Set up the lines for the family
619        if not family_line.line_to:
620            #no children.
621            family_line.line_to = LineBase(family_line)
622        if self.inlc_marr:
623            family_line.line_to.start.append(father_b)
624        family_line.line_to.start.append(mother_b)
625
626        return retrn
627
628    def has_children(self, person_handle):
629        """
630        Quickly check to see if this person has children
631        still we want to respect the FamiliesSeen list
632        """
633
634        if not person_handle or person_handle in self.families_seen:
635            return False
636
637        person = self.database.get_person_from_handle(person_handle)
638
639        for family_handle in person.get_family_handle_list():
640            if family_handle not in self.families_seen:
641
642                family = self.database.get_family_from_handle(family_handle)
643
644                if family.get_child_ref_list():
645                    return True
646        return False
647
648    def recurse_if(self, person_handle, level):
649        """
650        Quickly check to see if we want to continue recursion
651        still we want to respect the FamiliesSeen list
652        """
653
654        person = self.database.get_person_from_handle(person_handle)
655
656        show = False
657        myfams = person.get_family_handle_list()
658        if len(myfams) > 1: #and self.max_spouses > 0
659            show = True
660            if not self.inlc_marr:
661                #if the condition is true, we only want to show
662                #this parent again IF s/he has other children
663                show = self.has_children(person_handle)
664
665        #if self.max_spouses == 0 and not self.has_children(person_handle):
666        #    self.families_seen.add(person_handle)
667        #    show = False
668
669        if show:
670            self.bold_now = 1
671            self.recurse(person_handle, level, 0, None)
672
673
674#------------------------------------------------------------------------
675#
676# Class MakePersonTree (Personal Descendant Tree option)
677#
678#------------------------------------------------------------------------
679class MakePersonTree(RecurseDown):
680    """
681    The main procedure to use recursion to make the tree based off of a person.
682    order of people inserted into Persons is important.
683    makes sure that order is done correctly.
684    """
685    def __init__(self, dbase, canvas):
686        RecurseDown.__init__(self, dbase, canvas)
687        self.max_generations -= 1
688
689    def start(self, person_id):
690        """follow the steps to make a tree off of a person"""
691        persons = []
692
693        center1 = self.database.get_person_from_gramps_id(person_id)
694        if center1 is None:
695            raise ReportError(_("Person %s is not in the Database") % person_id)
696        center1_h = center1.get_handle()  #could be mom too.
697
698        family2 = family2_h = None
699        if self.do_parents:
700            family2_h = center1.get_main_parents_family_handle()
701            if family2_h:
702                family2 = self.database.get_family_from_handle(family2_h)
703
704        mother2_h = father2_h = None
705        if family2:
706            father2_h = family2.get_father_handle()
707            mother2_h = family2.get_mother_handle()
708
709
710        #######################
711        #don't do center person's parents family.
712        if family2_h:
713            self.families_seen.add(family2_h)
714
715        #######################
716        #Center person's Fathers OTHER wives
717        #######################
718        #update to only run if he HAD other wives!
719        if father2_h:
720            self.recurse_if(father2_h, 0)
721
722        #######################
723        #Center persons parents only!
724        #######################
725        #now it will ONLY be my fathers parents
726        if family2:
727            self.add_family(0, family2, None)
728        else:
729            self.bold_now = 2
730            self.recurse(center1_h, 0, 0, None)
731        self.bold_now = 0
732
733        #######################
734        #Center person's mothers OTHER husbands
735        #######################
736        #update to only run if she HAD other husbands!
737        if mother2_h:
738            self.recurse_if(mother2_h, 0)
739
740        return persons
741
742#------------------------------------------------------------------------
743#
744# Class MakeFamilyTree (Familial Descendant Tree option)
745#
746#------------------------------------------------------------------------
747class MakeFamilyTree(RecurseDown):
748    """
749    The main procedure to use recursion to make the tree based off of a family.
750    order of people inserted into Persons is important.
751    makes sure that order is done correctly.
752    """
753
754    def __init__(self, dbase, canvas):
755        RecurseDown.__init__(self, dbase, canvas)
756
757    def start(self, family_id):
758        """follow the steps to make a tree off of a family"""
759        ## (my) referes to the children of family_id
760        # Step 1 print out my fathers, fathers,
761        # other wives families first (if needed)
762        family1 = self.database.get_family_from_gramps_id(family_id)
763        if family1 is None:
764            raise ReportError(_("Family %s is not in the Database") % family_id)
765        family1_h = family1.get_handle()
766
767        #######################
768        #Initial setup of variables
769        #######################
770        father1_h = family1.get_father_handle()
771        mother1_h = family1.get_mother_handle()
772
773        father1 = mother1 = family2 = family2_h = None
774        if father1_h:
775            father1 = self.database.get_person_from_handle(father1_h)
776            if  self.do_parents:  #b3 - remove grandparents?
777                family2_h = father1.get_main_parents_family_handle()
778                if family2_h:
779                    family2 = self.database.get_family_from_handle(family2_h)
780        if mother1_h:
781            mother1 = self.database.get_person_from_handle(mother1_h)
782
783        mother2_h = father2_h = father2 = mother2 = None
784        if family2: #family2 = fathers parents
785            mother2_h = family2.get_mother_handle()
786            if mother2_h:
787                mother2 = self.database.get_person_from_handle(mother2_h)
788            father2_h = family2.get_father_handle()
789            if father2_h:
790                father2 = self.database.get_person_from_handle(father2_h)
791
792        #Helper variables.  Assigned in one section, used in another.
793        father2_id = family2_id = None
794        mother1_id = None
795
796        #######################
797        #don't do my fathers parents family.  will be done later
798        if family2_h:
799            self.families_seen.add(family2_h)
800
801        #######################
802        #my father mothers OTHER husbands
803        #######################
804        #update to only run if she HAD other husbands!
805        if mother2_h:
806            self.recurse_if(mother2_h, 0)
807
808        #######################
809        #father Fathers OTHER wives
810        #######################
811        #update to only run if he HAD other wives!
812        if father2_h:
813            self.recurse_if(father2_h, 0)
814
815        #######################
816        #don't do my parents family in recurse.  will be done later
817        self.families_seen.add(family1_h)
818        ##If dad has no other children from other marriages.  remove him
819        if self.max_spouses == 0 and not self.has_children(father1_h):
820            self.families_seen.add(father1_h)
821
822        #######################
823        #my fathers parents!
824        #######################
825        #now it will ONLY be my fathers parents
826        #will print dads parents.  dad's other wifes will also print
827        if family2:
828            myfams = father1.get_family_handle_list()
829            show = False
830            if len(myfams) > 1:
831                show = True
832                if not self.inlc_marr and self.max_spouses == 0:
833                    #if the condition is true, we only want to show
834                    #this parent again IF s/he has children
835                    show = self.has_children(father1_h)
836            if not show:
837                self.families_seen.add(father1_h)
838
839            family2_l = self.add_family(0, family2, None)
840
841        elif father1:
842            #######################
843            #my father other wives (if all of the above does nothing)
844            #if my father does not have parents (he is the highest)
845            #######################
846            #do his OTHER wives first.
847            self.recurse_if(father1_h, 1)
848
849
850        #######################
851        #my father, marriage info, mother, siblings, me
852        #######################
853        if family2:
854            #We need to add dad to the family
855            family2_line = family2_l[1] if self.inlc_marr else family2_l[0]
856        else:
857            family2_line = None
858
859        family1_l = self.add_family(1, family1, family2_line)
860        mother1_b = family1_l[-1]  #Mom's Box
861
862        #make sure there is at least one child in this family.
863        #if not put in a placeholder
864        family1_line = family1_l[1] if self.inlc_marr else family1_l[0]
865        if family1_line.line_to.end == []:
866            box = PlaceHolderBox((mother1_b.level[0]+1, 0))
867            box.father = family1_l[0]
868            self.add_to_col(box)
869            family1_line.line_to.end = [box]
870
871        #######################
872        #######################
873        #Lower half
874        #This will be quite like the first half.
875        #Just on the mothers side...
876        #Mom has already been printed with the family
877        #######################
878        #######################
879
880        #######################
881        #Initial setup of variables
882        #######################
883        mother1_h = family1.get_mother_handle()
884        family2_h = mother1 = family2 = None
885        if mother1_h:
886            mother1 = self.database.get_person_from_handle(mother1_h)
887            if  self.do_parents:  #b3 - remove grandparents?
888                family2_h = mother1.get_main_parents_family_handle()
889                if family2_h:
890                    family2 = self.database.get_family_from_handle(family2_h)
891
892        mother2_h = father2_h = mother2 = father2 = None
893        if family2:
894            mother2_h = family2.get_mother_handle()
895            if mother2_h:
896                mother2 = self.database.get_person_from_handle(mother2_h)
897            father2_h = family2.get_father_handle()
898            if father2_h:
899                father2 = self.database.get_person_from_handle(father2_h)
900
901        #######################
902        #don't do my parents family.
903        self.families_seen = set([family1_h])
904        ##If mom has no other children from other marriages.  remove her
905        if self.max_spouses == 0 and not self.has_children(mother1_h):
906            self.families_seen.add(mother1_h)
907
908        if mother1_h:
909            myfams = mother1.get_family_handle_list()
910            if len(myfams) < 2:
911                #If mom didn't have any other families, don't even do her
912                #she is already here with dad and will be added later
913                self.families_seen.add(mother1_h)
914
915        #######################
916        #my mother other spouses (if no parents)
917        #######################
918        #if my mother does not have parents (she is the highest)
919        #Then do her OTHER spouses.
920        if not family2 and mother1:
921            self.recurse_if(mother1_h, 1)
922
923        #######################
924        #my mothers parents!
925        #######################
926        if family2:
927            family2_l = self.add_family(0, family2, None)
928            family2_line = family2_l[1] if self.inlc_marr else family2_l[0]
929
930            family2_line = family2_line.line_to
931            if family2_line.end != []:
932                family2_line.end.insert(0, mother1_b)
933            else:
934                family2_line.end = [mother1_b]
935
936            #fix me.  Moms other siblings have been given an extra space
937            #Because Moms-father is not siblings-father right now.
938
939            mother1_b.father = family2_line
940
941        #######################
942        #my mother mothers OTHER husbands
943        #######################
944        #update to only run if she HAD other husbands!
945        if mother2_h:
946            self.recurse_if(mother2_h, 0)
947
948        #######################
949        #mother Fathers OTHER wives
950        #######################
951        #update to only run if he HAD other wives!
952        if father2_h:
953            self.recurse_if(father2_h, 0)
954
955
956#------------------------------------------------------------------------
957#
958# Class MakeReport
959#
960#------------------------------------------------------------------------
961class MakeReport:
962    """
963    Make a report out of a list of people.
964    The list of people is already made.  Use this information to find where
965    people will be placed on the canvas.
966    """
967
968    def __init__(self, dbase, canvas, ind_spouse, compress_tree):
969        self.database = dbase
970        self.canvas = canvas
971
972        gui = GuiConnect()
973        self.do_parents = gui.get_val('show_parents')
974        self.inlc_marr = gui.get_val("inc_marr")
975        self.max_spouses = gui.get_val('maxspouse')
976        gui = None
977
978        self.ind_spouse = ind_spouse
979        self.compress_tree = compress_tree
980        self.cols = [[]]
981        #self.max_generations = 0
982
983    #already done in recurse,
984    #Some of this code needs to be moved up to RecurseDown.add_to_col()
985    def calc_box(self, box):
986        """ calculate the max_box_width and max_box_height for the report """
987        width = box.x_cm + box.width
988        if width > self.canvas.report_opts.max_box_width:
989            self.canvas.report_opts.max_box_width = width
990
991        if box.height > self.canvas.report_opts.max_box_height:
992            self.canvas.report_opts.max_box_height = box.height
993
994        while len(self.cols) <= box.level[0]:
995            self.cols.append([])
996
997        self.cols[box.level[0]].append(box)
998
999        #tmp = box.level[0]
1000        #if tmp > self.max_generations:
1001        #    self.max_generations = tmp
1002
1003    def __move_col_from_here_down(self, box, amount):
1004        """Move me and everyone below me in this column only down"""
1005        while box:
1006            box.y_cm += amount
1007            box = box.linked_box
1008
1009    def __move_next_cols_from_here_down(self, box, amount):
1010        """Move me, everyone below me in this column,
1011        and all of our children (and childrens children) down."""
1012        col = [box]
1013        while col:
1014            if len(col) == 1 and col[0].line_to:
1015                col.append(col[0].line_to.end[0])
1016
1017            col[0].y_cm += amount
1018
1019            col[0] = col[0].linked_box
1020            if col[0] is None:
1021                col.pop(0)
1022
1023    def __next_family_group(self, box):
1024        """ a helper function.  Assume box is at the start of a family block.
1025        get this family block. """
1026        while box:
1027            left_group = []
1028            line = None
1029
1030            #Form the parental (left) group.
1031            #am I a direct descendant?
1032            if box.level[1] == 0:
1033                #I am the father/mother.
1034                left_group.append(box)
1035                if box.line_to:
1036                    line = box.line_to
1037                box = box.linked_box
1038
1039            if box and box.level[1] != 0 and self.inlc_marr:
1040                #add/start with the marriage box
1041                left_group.append(box)
1042                if box.line_to:
1043                    line = box.line_to
1044                box = box.linked_box
1045
1046            if box and box.level[1] != 0 and self.max_spouses > 0:
1047                #add/start with the spousal box
1048                left_group.append(box)
1049                if box.line_to:
1050                    line = box.line_to
1051                box = box.linked_box
1052
1053            if line:
1054                if len(line.start) > 1 and line.start[-1].level[1] == 0:
1055                    #a dad and mom family from RecurseDown.add_family. add mom
1056                    left_group.append(line.start[-1])
1057                    box = box.linked_box
1058
1059                #we now have everyone we want
1060                return left_group, line.end
1061            #else
1062            #  no children, so no family.  go again until we find one to return.
1063
1064        return None, None
1065
1066    def __reverse_family_group(self):
1067        """ go through the n-1 to 0 cols of boxes looking for families
1068        (parents with children) that may need to be moved. """
1069        for x_col in range(len(self.cols)-1, -1, -1):
1070            box = self.cols[x_col][0]   #The first person in this col
1071            while box:
1072                left_group, right_group = self.__next_family_group(box)
1073                if not left_group:
1074                    box = None #we found the end of this col
1075                else:
1076                    yield left_group, right_group
1077                    box = left_group[-1].linked_box
1078
1079    def __calc_movements(self, left_group, right_group):
1080        """ for a family group, see if parents or children need to be
1081        moved down so everyone is to the right/left of each other.
1082
1083        return a right y_cm and a left y_cm.  these points will be used
1084        to move parents/children down.
1085        """
1086        left_up = left_group[0].y_cm
1087        right_up = right_group[0].y_cm
1088
1089        left_center = left_up
1090        right_center = right_up
1091
1092        if self.compress_tree:
1093            #calculate a new left and right move points
1094            for left_line in left_group:
1095                if left_line.line_to:
1096                    break
1097            left_center = left_line.y_cm + (left_line.height /2)
1098
1099            left_down = left_group[-1].y_cm + left_group[-1].height
1100            right_down = right_group[-1].y_cm + right_group[-1].height
1101
1102            #Lazy.  Move down either side only as much as we NEED to.
1103            if left_center < right_up:
1104                right_center = right_group[0].y_cm
1105            elif left_up == right_up:
1106                left_center = left_up #Lets keep it.  top line.
1107            elif left_center > right_down:
1108                right_center = right_down
1109            else:
1110                right_center = left_center
1111
1112        return right_center, left_center
1113
1114    def Make_report(self):
1115        """
1116        Everyone on the page is as far up as they can go.
1117        Move them down to where they belong.
1118
1119        We are going to go through everyone from right to left
1120        top to bottom moving everyone down as needed to make the report.
1121        """
1122        seen_parents = False
1123
1124        for left_group, right_group in self.__reverse_family_group():
1125            right_y_cm, left_y_cm = self.__calc_movements(left_group,
1126                                                          right_group)
1127
1128            #1.  Are my children too high?  if so move then down!
1129            if right_y_cm < left_y_cm:
1130                #we have to push our kids (and their kids) down.
1131                #We also need to push down all the kids (under)
1132                #these kids (in their column)
1133                amt = (left_y_cm - right_y_cm)
1134                self.__move_next_cols_from_here_down(right_group[0], amt)
1135
1136            #2.  Am I (and spouses) too high?  if so move us down!
1137            elif left_y_cm < right_y_cm:
1138                #Ok, I am too high.  Move me down
1139                amt = (right_y_cm - left_y_cm)
1140                self.__move_col_from_here_down(left_group[0], amt)
1141
1142            #6. now check to see if we are working with dad and mom.
1143            #if so we need to move down marriage information
1144            #and mom!
1145            left_line = left_group[0].line_to
1146            if not left_line:
1147                left_line = left_group[1].line_to
1148            #left_line = left_line.start
1149
1150            if len(left_line.start) > 1 and not seen_parents:
1151                #only do Dad and Mom.  len(left_line) > 1
1152                seen_parents = True
1153
1154                mom_cm = left_group[-1].y_cm + left_group[-1].height/2
1155                last_child_cm = right_group[-1].y_cm
1156                if not self.compress_tree:
1157                    last_child_cm += right_group[-1].height/2
1158                move_amt = last_child_cm - mom_cm
1159
1160                #if the moms height is less than the last childs height
1161                #The 0.2 is to see if this is even worth it.
1162                if move_amt > 0.2:
1163                    #our children take up more space than us parents.
1164                    #so space mom out!
1165                    self.__move_col_from_here_down(left_group[-1], move_amt)
1166
1167                    #move marriage info
1168                    if self.inlc_marr:
1169                        left_group[1].y_cm += move_amt/2
1170
1171                if left_line.end[0].boxstr == 'None':
1172                    left_line.end = []
1173
1174    def start(self):
1175        """Make the report"""
1176        #for person in self.persons.depth_first_gen():
1177        for box in self.canvas.boxes:
1178            self.calc_box(box)
1179        #At this point we know everything we need to make the report.
1180        #Width of each column of people - self.rept_opt.box_width
1181        #width of each column (or row) of lines - self.rept_opt.col_width
1182
1183        if not self.cols[0]:
1184            #We wanted to print parents of starting person/family but
1185            #there were none!
1186            #remove column 0 and move everyone back one level
1187            self.cols.pop(0)
1188            for box in self.canvas.boxes:
1189                box.level = (box.level[0] - 1, box.level[1])
1190
1191        #go ahead and set it now.
1192        width = self.canvas.report_opts.max_box_width
1193        for box in self.canvas.boxes:
1194            box.width = width - box.x_cm
1195            box.x_cm += self.canvas.report_opts.littleoffset
1196            box.x_cm += (box.level[0] *
1197                         (self.canvas.report_opts.col_width +
1198                          self.canvas.report_opts.max_box_width))
1199
1200            box.y_cm += self.canvas.report_opts.littleoffset
1201            box.y_cm += self.canvas.title.height
1202
1203        self.Make_report()
1204
1205
1206class GuiConnect:
1207    """ This is a BORG object.  There is ONLY one.
1208    This give some common routines that EVERYONE can use like
1209      get the value from a GUI variable
1210    """
1211
1212    __shared_state = {}
1213    def __init__(self):  #We are BORG!
1214        self.__dict__ = self.__shared_state
1215
1216    def set__opts(self, options, which, locale, name_displayer):
1217        self._opts = options
1218        self._which_report = which.split(",")[0]
1219        self._locale = locale
1220        self._nd = name_displayer
1221
1222    def get_val(self, val):
1223        """ Get a GUI value. """
1224        value = self._opts.get_option_by_name(val)
1225        if value:
1226            return value.get_value()
1227        else:
1228            False
1229
1230    def Title_class(self, database, doc):
1231        Title_type = self.get_val('report_title')
1232        if Title_type == 0:  #None
1233            return TitleNone(database, doc, self._locale)
1234
1235        if Title_type == 1:  #Descendant Chart
1236            if self._which_report == _RPT_NAME:
1237                if self.get_val('show_parents'):
1238                    return TitleDPY(database, doc, self._locale, self._nd)
1239                else:
1240                    return TitleDPN(database, doc, self._locale, self._nd)
1241            else:
1242                if self.get_val('show_parents'):
1243                    return TitleDFY(database, doc, self._locale, self._nd)
1244                else:
1245                    return TitleDFN(database, doc, self._locale, self._nd)
1246
1247        if Title_type == 2:
1248            return TitleF(database, doc, self._locale, self._nd)
1249        else: #Title_type == 3
1250            return TitleC(database, doc, self._locale, self._nd)
1251
1252    def Make_Tree(self, database, canvas):
1253        if self._which_report == _RPT_NAME:
1254            return MakePersonTree(database, canvas)
1255        else:
1256            return MakeFamilyTree(database, canvas)
1257
1258    def calc_lines(self, database):
1259        #calculate the printed lines for each box
1260        display_repl = self.get_val("replace_list")
1261        #str = ""
1262        #if self.get_val('miss_val'):
1263        #    str = "_____"
1264        return CalcLines(database, display_repl, self._locale, self._nd)
1265
1266    def working_lines(self, box):
1267        display = self.get_val("descend_disp")
1268        #if self.get_val('diffspouse'):
1269        display_spou = self.get_val("spouse_disp")
1270        #else:
1271        #    display_spou = display
1272        display_marr = [self.get_val("marr_disp")]
1273
1274        if box.boxstr == "CG2-fam-box":  #(((((
1275            workinglines = display_marr
1276        elif box.level[1] > 0 or (box.level[0] == 0 and box.father):
1277            workinglines = display_spou
1278        else:
1279            workinglines = display
1280        return workinglines
1281
1282
1283#------------------------------------------------------------------------
1284#
1285# DescendTree
1286#
1287#------------------------------------------------------------------------
1288class DescendTree(Report):
1289
1290    def __init__(self, database, options, user):
1291        """
1292        Create DescendTree object that produces the report.
1293        The arguments are:
1294
1295        database        - the Gramps database instance
1296        options         - instance of the Options class for this report
1297        user            - a gen.user.User() instance
1298
1299        incl_private    - Whether to include private data
1300        living_people - How to handle living people
1301        years_past_death - Consider as living this many years after death
1302        """
1303        Report.__init__(self, database, options, user)
1304
1305        self.options = options
1306
1307        self.set_locale(options.menu.get_option_by_name('trans').get_value())
1308        stdoptions.run_date_format_option(self, options.menu)
1309        stdoptions.run_private_data_option(self, options.menu)
1310        stdoptions.run_living_people_option(self, options.menu, self._locale)
1311        self.database = CacheProxyDb(self.database)
1312        stdoptions.run_name_format_option(self, options.menu)
1313        self._nd = self._name_display
1314
1315    def begin_report(self):
1316        """ make the report in its full size and pages to print on
1317        scale one or both as needed/desired.
1318        """
1319
1320        database = self.database
1321
1322        self.Connect = GuiConnect()
1323        self.Connect.set__opts(self.options.menu, self.options.name,
1324                               self._locale, self._nd)
1325
1326        style_sheet = self.doc.get_style_sheet()
1327        font_normal = style_sheet.get_paragraph_style("CG2-Normal").get_font()
1328
1329        #The canvas that we will put our report on and print off of
1330        self.canvas = Canvas(self.doc,
1331                             ReportOptions(self.doc, font_normal, "CG2-line"))
1332
1333        self.canvas.report_opts.box_shadow *= \
1334                        self.Connect.get_val('shadowscale')
1335        self.canvas.report_opts.box_pgap *= self.Connect.get_val('box_Yscale')
1336        self.canvas.report_opts.box_mgap *= self.Connect.get_val('box_Yscale')
1337
1338        center_id = self.Connect.get_val('pid')
1339
1340        #make the tree
1341        tree = self.Connect.Make_Tree(database, self.canvas)
1342        tree.start(center_id)
1343        tree = None
1344
1345        #Title
1346        title = self.Connect.Title_class(database, self.doc)
1347        title.calc_title(center_id)
1348        self.canvas.add_title(title)
1349
1350        #make the report as big as it wants to be.
1351        ind_spouse = self.Connect.get_val("ind_spouse")
1352        compress_tree = self.Connect.get_val('compress_tree')
1353        report = MakeReport(database, self.canvas, ind_spouse, compress_tree)
1354        report.start()
1355        report = None
1356
1357        #note?
1358        if self.Connect.get_val("inc_note"):
1359            note_box = NoteBox(self.doc, "CG2-note-box",
1360                               self.Connect.get_val("note_place"))
1361            subst = SubstKeywords(self.database, self._locale, self._nd,
1362                                  None, None)
1363            note_box.text = subst.replace_and_clean(
1364                self.Connect.get_val('note_disp'))
1365            self.canvas.add_note(note_box)
1366
1367        #Now we have the report in its full size.
1368        #Do we want to scale the report?
1369        one_page = self.Connect.get_val("resize_page")
1370        scale_report = self.Connect.get_val("scale_tree")
1371
1372        scale = self.canvas.scale_report(one_page,
1373                                         scale_report != 0, scale_report == 2)
1374
1375        if scale != 1 or self.Connect.get_val('shadowscale') != 1.0:
1376            self.scale_styles(scale)
1377
1378    def write_report(self):
1379        """ Canvas now has everyone ready to print.  Get some misc stuff
1380        together and print. """
1381
1382        one_page = self.Connect.get_val("resize_page")
1383        scale_report = self.Connect.get_val("scale_tree")
1384
1385        #Inlc_marr = self.Connect.get_val("inc_marr")
1386        inc_border = self.Connect.get_val('inc_border')
1387        incblank = self.Connect.get_val("inc_blank")
1388        prnnum = self.Connect.get_val("inc_pagenum")
1389        #ind_spouse = self.Connect.get_val("ind_spouse")
1390        lines = self.Connect.get_val('note_disp')
1391
1392        #####################
1393        #Setup page information
1394
1395        colsperpage = self.doc.get_usable_width()
1396        colsperpage += self.canvas.report_opts.col_width
1397        tmp = self.canvas.report_opts.max_box_width
1398        tmp += self.canvas.report_opts.col_width
1399        colsperpage = int(colsperpage / tmp)
1400        colsperpage = colsperpage or 1
1401
1402        #####################
1403        #Vars
1404        #p = self.doc.get_style_sheet().get_paragraph_style("CG2-Normal")
1405        #font = p.get_font()
1406        if prnnum:
1407            page_num_box = PageNumberBox(self.doc, 'CG2-box', self._locale)
1408
1409        #####################
1410        #ok, everyone is now ready to print on the canvas.  Paginate?
1411        self.canvas.sort_boxes_on_y_cm()
1412        self.canvas.paginate(colsperpage, one_page)
1413
1414        #####################
1415        #Yeah!!!
1416        #lets finally make some pages!!!
1417        #####################
1418        for page in self.canvas.page_iter_gen(incblank):
1419
1420            self.doc.start_page()
1421
1422            #do we need to print a border?
1423            if inc_border:
1424                page.draw_border('CG2-line')
1425
1426            #Do we need to print the page number?
1427            if prnnum:
1428                page_num_box.display(page)
1429
1430            page.display()
1431
1432            self.doc.end_page()
1433
1434
1435    def scale_styles(self, amount):
1436        """
1437        Scale the styles for this report. This must be done in the constructor.
1438        """
1439        style_sheet = self.doc.get_style_sheet()
1440
1441        graph_style = style_sheet.get_draw_style("CG2-fam-box")
1442        graph_style.set_shadow(graph_style.get_shadow(), 0)
1443        graph_style.set_line_width(graph_style.get_line_width() * amount)
1444        style_sheet.add_draw_style("CG2-fam-box", graph_style)
1445
1446        graph_style = style_sheet.get_draw_style("CG2-box")
1447        graph_style.set_shadow(graph_style.get_shadow(),
1448                               self.canvas.report_opts.box_shadow * amount)
1449        graph_style.set_line_width(graph_style.get_line_width() * amount)
1450        style_sheet.add_draw_style("CG2-box", graph_style)
1451
1452        graph_style = style_sheet.get_draw_style("CG2b-box")
1453        graph_style.set_shadow(graph_style.get_shadow(),
1454                               self.canvas.report_opts.box_shadow * amount)
1455        graph_style.set_line_width(graph_style.get_line_width() * amount)
1456        style_sheet.add_draw_style("CG2b-box", graph_style)
1457
1458        graph_style = style_sheet.get_draw_style("CG2-note-box")
1459        graph_style.set_shadow(graph_style.get_shadow(), 0)
1460        graph_style.set_line_width(graph_style.get_line_width() * amount)
1461        style_sheet.add_draw_style("CG2-note-box", graph_style)
1462
1463        para_style = style_sheet.get_paragraph_style("CG2-Title")
1464        font = para_style.get_font()
1465        font.set_size(font.get_size() * amount)
1466        para_style.set_font(font)
1467        style_sheet.add_paragraph_style("CG2-Title", para_style)
1468
1469        para_style = style_sheet.get_paragraph_style("CG2-Normal")
1470        font = para_style.get_font()
1471        font.set_size(font.get_size() * amount)
1472        para_style.set_font(font)
1473        style_sheet.add_paragraph_style("CG2-Normal", para_style)
1474
1475        para_style = style_sheet.get_paragraph_style("CG2-Bold")
1476        font = para_style.get_font()
1477        font.set_bold(True)
1478        font.set_size(font.get_size() * amount)
1479        para_style.set_font(font)
1480        style_sheet.add_paragraph_style("CG2-Bold", para_style)
1481
1482        para_style = style_sheet.get_paragraph_style("CG2-Note")
1483        font = para_style.get_font()
1484        font.set_size(font.get_size() * amount)
1485        para_style.set_font(font)
1486        style_sheet.add_paragraph_style("CG2-Note", para_style)
1487
1488        self.doc.set_style_sheet(style_sheet)
1489
1490
1491#------------------------------------------------------------------------
1492#
1493# DescendTreeOptions
1494#
1495#------------------------------------------------------------------------
1496class DescendTreeOptions(MenuReportOptions):
1497
1498    """
1499    Defines options and provides handling interface.
1500    """
1501
1502    def __init__(self, name, dbase):
1503        self.__pid = None
1504        self.__onepage = None
1505        self.__inc_title = None
1506        self.__title = None
1507        self.__blank = None
1508        self.scale = None
1509        self.__db = dbase
1510        self.name = name
1511        self.box_Y_sf = None
1512        self.box_shadow_sf = None
1513        MenuReportOptions.__init__(self, name, dbase)
1514
1515    def get_subject(self):
1516        """ Return a string that describes the subject of the report. """
1517        gid = self.__pid.get_value()
1518        if self.name.split(",")[0] == _RPT_NAME:
1519            person = self.__db.get_person_from_gramps_id(gid)
1520            if person:
1521                return _nd.display(person)
1522        else:
1523            family = self.__db.get_family_from_gramps_id(gid)
1524            if family:
1525                return family_name(family, self.__db)
1526        return ""
1527
1528    def add_menu_options(self, menu):
1529        """
1530        Add options to the menu for the descendant report.
1531        """
1532        ##################
1533        category_name = _("Tree Options")
1534
1535        if self.name.split(",")[0] == _RPT_NAME:
1536            self.__pid = PersonOption(_("Report for"))
1537            self.__pid.set_help(_("The main person for the report"))
1538            menu.add_option(category_name, "pid", self.__pid)
1539        else: #if self.name == "familial_descend_tree":
1540            self.__pid = FamilyOption(_("Report for"))
1541            self.__pid.set_help(_("The main family for the report"))
1542            menu.add_option(category_name, "pid", self.__pid)
1543
1544        max_gen = NumberOption(_("Generations"), 10, 1, 50)
1545        max_gen.set_help(_("The number of generations to include in the tree"))
1546        menu.add_option(category_name, "maxgen", max_gen)
1547
1548        max_spouse = NumberOption(_("Level of Spouses"), 1, 0, 10)
1549        max_spouse.set_help(_("0=no Spouses, 1=include Spouses, 2=include "
1550                              "Spouses of the spouse, etc"))
1551        menu.add_option(category_name, "maxspouse", max_spouse)
1552
1553        self.showparents = BooleanOption(
1554            _('Start with the parent(s) of the selected first'),
1555            False)
1556        self.showparents.set_help(
1557            _("Will show the parents, brother and sisters of the "
1558              "selected person.")
1559            )
1560        menu.add_option(category_name, "show_parents", self.showparents)
1561
1562        compresst = BooleanOption(_('Compress tree'), False)
1563        compresst.set_help(_("Whether to move people up, where possible, "
1564                             "resulting in a smaller tree"))
1565        menu.add_option(category_name, "compress_tree", compresst)
1566
1567        bold = BooleanOption(_('Bold direct descendants'), True)
1568        bold.set_help(
1569            _("Whether to bold those people that are direct "
1570              "(not step or half) descendants.")
1571            )
1572        menu.add_option(category_name, "bolddirect", bold)
1573
1574        indspouce = BooleanOption(_('Indent Spouses'), True)
1575        indspouce.set_help(_("Whether to indent the spouses in the tree."))
1576        menu.add_option(category_name, "ind_spouse", indspouce)
1577
1578        ##################
1579        category_name = _("Report Options")
1580
1581        self.title = EnumeratedListOption(_("Report Title"), 0)
1582        self.title.add_item(0, _("Do not include a title"))
1583        self.title.add_item(1, _("Descendant Chart for [selected person(s)]"))
1584        if self.name.split(",")[0] != _RPT_NAME:
1585            self.title.add_item(2,
1586                                _("Family Chart for [names of chosen family]"))
1587            if self.showparents.get_value():
1588                self.title.add_item(3,
1589                                    _("Cousin Chart for [names of children]"))
1590        self.title.set_help(_("Choose a title for the report"))
1591        menu.add_option(category_name, "report_title", self.title)
1592        self.showparents.connect('value-changed', self.__Title_enum)
1593
1594        border = BooleanOption(_('Include a border'), False)
1595        border.set_help(_("Whether to make a border around the report."))
1596        menu.add_option(category_name, "inc_border", border)
1597
1598        prnnum = BooleanOption(_('Include Page Numbers'), False)
1599        prnnum.set_help(_("Whether to include page numbers on each page."))
1600        menu.add_option(category_name, "inc_pagenum", prnnum)
1601
1602        self.scale = EnumeratedListOption(_("Scale tree to fit"), 0)
1603        self.scale.add_item(0, _("Do not scale tree"))
1604        self.scale.add_item(1, _("Scale tree to fit page width only"))
1605        self.scale.add_item(2, _("Scale tree to fit the size of the page"))
1606        self.scale.set_help(
1607            _("Whether to scale the tree to fit a specific paper size")
1608            )
1609        menu.add_option(category_name, "scale_tree", self.scale)
1610        self.scale.connect('value-changed', self.__check_blank)
1611
1612        if "BKI" not in self.name.split(","):
1613            self.__onepage = BooleanOption(
1614                _("Resize Page to Fit Tree size\n"
1615                  "\n"
1616                  "Note: Overrides options in the 'Paper Option' tab"
1617                 ),
1618                False)
1619            self.__onepage.set_help(
1620                _("Whether to resize the page to fit the size \n"
1621                  "of the tree.  Note:  the page will have a \n"
1622                  "non standard size.\n"
1623                  "\n"
1624                  "With this option selected, the following will happen:\n"
1625                  "\n"
1626                  "With the 'Do not scale tree' option the page\n"
1627                  "  is resized to the height/width of the tree\n"
1628                  "\n"
1629                  "With 'Scale tree to fit page width only' the height of\n"
1630                  "  the page is resized to the height of the tree\n"
1631                  "\n"
1632                  "With 'Scale tree to fit the size of the page' the page\n"
1633                  "  is resized to remove any gap in either height or width"
1634                 ))
1635            menu.add_option(category_name, "resize_page", self.__onepage)
1636            self.__onepage.connect('value-changed', self.__check_blank)
1637        else:
1638            self.__onepage = None
1639
1640        self.__blank = BooleanOption(_('Include Blank Pages'), True)
1641        self.__blank.set_help(_("Whether to include pages that are blank."))
1642        menu.add_option(category_name, "inc_blank", self.__blank)
1643
1644        ##################
1645        category_name = _("Report Options (2)")
1646
1647        stdoptions.add_name_format_option(menu, category_name)
1648
1649        stdoptions.add_private_data_option(menu, category_name)
1650
1651        stdoptions.add_living_people_option(menu, category_name)
1652
1653        locale_opt = stdoptions.add_localization_option(menu, category_name)
1654
1655        stdoptions.add_date_format_option(menu, category_name, locale_opt)
1656
1657        ##################
1658        category_name = _("Display")
1659
1660        disp = TextOption(_("Descendant\nDisplay Format"),
1661                          ["$n",
1662                           "%s $b" %_BORN,
1663                           "-{%s $d}" %_DIED])
1664        disp.set_help(_("Display format for a descendant."))
1665        menu.add_option(category_name, "descend_disp", disp)
1666
1667        #bug 4767
1668        #diffspouse = BooleanOption(
1669        #    _("Use separate display format for spouses"),
1670        #    True)
1671        #diffspouse.set_help(_("Whether spouses can have a different format."))
1672        #menu.add_option(category_name, "diffspouse", diffspouse)
1673
1674        sdisp = TextOption(_("Spousal\nDisplay Format"),
1675                           ["$n",
1676                            "%s $b" %_BORN,
1677                            "-{%s $d}" %_DIED])
1678        sdisp.set_help(_("Display format for a spouse."))
1679        menu.add_option(category_name, "spouse_disp", sdisp)
1680
1681        self.incmarr = BooleanOption(_('Include Marriage box'), True)
1682        self.incmarr.set_help(
1683            _("Whether to include a separate marital box in the report"))
1684        menu.add_option(category_name, "inc_marr", self.incmarr)
1685        self.incmarr.connect('value-changed', self._incmarr_changed)
1686
1687        self.marrdisp = StringOption(_("Marriage\nDisplay Format"),
1688                                       "%s $m" % _MARR)
1689        self.marrdisp.set_help(_("Display format for the marital box."))
1690        menu.add_option(category_name, "marr_disp", self.marrdisp)
1691        self._incmarr_changed()
1692
1693        ##################
1694        category_name = _("Advanced")
1695
1696        repldisp = TextOption(
1697            _("Replace Display Format:\n'Replace this'/' with this'"),
1698            [])
1699        repldisp.set_help(_("i.e.\nUnited States of America/U.S.A"))
1700        menu.add_option(category_name, "replace_list", repldisp)
1701
1702        self.usenote = BooleanOption(_('Include a note'), False)
1703        self.usenote.set_help(_("Whether to include a note on the report."))
1704        menu.add_option(category_name, "inc_note", self.usenote)
1705        self.usenote.connect('value-changed', self._usenote_changed)
1706
1707        self.notedisp = TextOption(_("Note"), [])
1708        self.notedisp.set_help(_("Add a note\n\n"
1709                                 "$T inserts today's date"))
1710        menu.add_option(category_name, "note_disp", self.notedisp)
1711
1712        locales = NoteType(0)
1713        self.notelocal = EnumeratedListOption(_("Note Location"), 2)
1714        for num, text in locales.note_locals():
1715            self.notelocal.add_item(num, text)
1716        self.notelocal.set_help(_("Where to place the note."))
1717        menu.add_option(category_name, "note_place", self.notelocal)
1718        self._usenote_changed()
1719
1720        self.box_Y_sf = NumberOption(_("inter-box Y scale factor"),
1721                                     1.00, 0.10, 2.00, 0.01)
1722        self.box_Y_sf.set_help(_("Make the inter-box Y bigger or smaller"))
1723        menu.add_option(category_name, "box_Yscale", self.box_Y_sf)
1724
1725        self.box_shadow_sf = NumberOption(_("box shadow scale factor"),
1726                                          1.00, 0.00, 2.00, 0.01) # down to 0
1727        self.box_shadow_sf.set_help(_("Make the box shadow bigger or smaller"))
1728        menu.add_option(category_name, "shadowscale", self.box_shadow_sf)
1729
1730    def _incmarr_changed(self):
1731        """
1732        If Marriage box is not enabled, disable Marriage Display Format box
1733        """
1734        value = self.incmarr.get_value()
1735        self.marrdisp.set_available(value)
1736
1737    def _usenote_changed(self):
1738        """
1739        If Note box is not enabled, disable Note Location box
1740        """
1741        value = self.usenote.get_value()
1742        self.notelocal.set_available(value)
1743
1744    def __check_blank(self):
1745        """dis/enables the 'print blank pages' checkbox"""
1746        if self.__onepage:
1747            value = not self.__onepage.get_value()
1748        else:
1749            value = True
1750        off = value and (self.scale.get_value() != 2)
1751        self.__blank.set_available(off)
1752
1753    def __Title_enum(self):
1754        item_list = [
1755            [0, _("Do not include a title")],
1756            [1, _("Descendant Chart for [selected person(s)]")],
1757            ]
1758        if self.name.split(",")[0] != _RPT_NAME:
1759            item_list.append(
1760                [2, _("Family Chart for [names of chosen family]")]
1761                )
1762            if self.showparents.get_value():
1763                item_list.append(
1764                    [3, _("Cousin Chart for [names of children]")]
1765                    )
1766        self.title.set_items(item_list)
1767
1768    def make_default_style(self, default_style):
1769        """Make the default output style for the Descendant Tree."""
1770
1771        ## Paragraph Styles:
1772        font = FontStyle()
1773        font.set_size(16)
1774        font.set_type_face(FONT_SANS_SERIF)
1775        para_style = ParagraphStyle()
1776        para_style.set_font(font)
1777        para_style.set_alignment(PARA_ALIGN_CENTER)
1778        para_style.set_description(_("The style used for the title."))
1779        default_style.add_paragraph_style("CG2-Title", para_style)
1780
1781        font = FontStyle()
1782        font.set_size(9)
1783        font.set_type_face(FONT_SANS_SERIF)
1784        para_style = ParagraphStyle()
1785        para_style.set_font(font)
1786        para_style.set_description(
1787            _('The basic style used for the text display.'))
1788        default_style.add_paragraph_style("CG2-Normal", para_style)
1789
1790        #Set the size of the shadow based on the font size!  Much better
1791        #will be set later too.
1792        box_shadow = PT2CM(font.get_size()) * .6
1793
1794        font.set_bold(True)
1795        para_style = ParagraphStyle()
1796        para_style.set_font(font)
1797        para_style.set_description(
1798            _('The bold style used for the text display.'))
1799        default_style.add_paragraph_style("CG2-Bold", para_style)
1800
1801        font = FontStyle()
1802        font.set_size(9)
1803        font.set_type_face(FONT_SANS_SERIF)
1804        para_style = ParagraphStyle()
1805        para_style.set_font(font)
1806        para_style.set_description(
1807            _('The basic style used for the note display.'))
1808        default_style.add_paragraph_style("CG2-Note", para_style)
1809
1810        # TODO this seems meaningless, as only the text is displayed
1811        graph_style = GraphicsStyle()
1812        graph_style.set_paragraph_style("CG2-Title")
1813        graph_style.set_color((0, 0, 0))
1814        graph_style.set_fill_color((255, 255, 255))
1815        graph_style.set_line_width(0)
1816        graph_style.set_description(_("Cannot edit this reference"))
1817        default_style.add_draw_style("CG2-Title-box", graph_style)
1818
1819        ## Draw styles
1820        graph_style = GraphicsStyle()
1821        graph_style.set_paragraph_style("CG2-Normal")
1822        graph_style.set_fill_color((255, 255, 255))
1823        default_style.add_draw_style("CG2-fam-box", graph_style)
1824
1825        graph_style = GraphicsStyle()
1826        graph_style.set_paragraph_style("CG2-Normal")
1827        graph_style.set_shadow(1, box_shadow)
1828        graph_style.set_fill_color((255, 255, 255))
1829        default_style.add_draw_style("CG2-box", graph_style)
1830
1831        graph_style = GraphicsStyle()
1832        graph_style.set_paragraph_style("CG2-Bold")
1833        graph_style.set_shadow(1, box_shadow)
1834        graph_style.set_fill_color((255, 255, 255))
1835        default_style.add_draw_style("CG2b-box", graph_style)
1836
1837        graph_style = GraphicsStyle()
1838        graph_style.set_paragraph_style("CG2-Note")
1839        graph_style.set_fill_color((255, 255, 255))
1840        default_style.add_draw_style("CG2-note-box", graph_style)
1841
1842        graph_style = GraphicsStyle()
1843        default_style.add_draw_style("CG2-line", graph_style)
1844
1845#=====================================
1846#So do not fear, for I am with you; do not be dismayed,
1847#for I am your God.  I will strengthen you and help you;
1848#I will uphold you with my righteous right hand.
1849#Isaiah 41:10
1850