1#
2# Gramps - a GTK+/GNOME based genealogy program
3#
4# Copyright (C) 2008-2010  Craig J. Anderson
5# Copyright (C) 2014       Paul Franklin
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation; either version 2 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20#
21
22"""Reports/Graphical Reports/Tree_Base"""
23
24#------------------------------------------------------------------------
25#
26# Gramps modules
27#
28#------------------------------------------------------------------------
29from gramps.gen.const import GRAMPS_LOCALE as glocale
30_ = glocale.translation.sgettext
31from gramps.gen.plug.report import utils
32from gramps.plugins.lib.libsubstkeyword import SubstKeywords
33from gramps.gen.plug.docgen import (IndexMark, INDEX_TYPE_TOC)
34
35PT2CM = utils.pt2cm
36
37#------------------------------------------------------------------------
38#
39# Class Calc_Lines
40#
41#------------------------------------------------------------------------
42class CalcLines:
43    """ wrapper for libsubstkeyword and added functionality for
44    replacements.
45
46    Receive:  Individual and family handle, and display format [string]
47    return: [Text] ready for a box.
48    """
49    def __init__(self, dbase, repl, locale, name_displayer):
50        self.database = dbase
51        self.display_repl = repl
52        #self.default_string = default_str
53        self._locale = locale
54        self._nd = name_displayer
55
56    def calc_lines(self, _indi_handle, _fams_handle, workinglines):
57        """
58        In this pass we will:
59        1. make our text and do our replacements
60        2. remove any extra (unwanted) lines with the compres option
61        """
62
63        ####################
64        #1.1  Get our line information here
65        subst = SubstKeywords(self.database, self._locale, self._nd,
66                              _indi_handle, _fams_handle)
67        lines = subst.replace_and_clean(workinglines)
68
69        ####################
70        #1.2  do our replacements
71        lns = []
72        for line in lines:
73            for pair in self.display_repl:
74                if pair.count("/") == 1:
75                    repl = pair.split("/", 1)
76                    line = line.replace(repl[0], repl[1])
77            lns.append(line)
78
79        return lns
80
81
82#------------------------------------------------------------------------
83#
84# Class Canvas/Pages
85#
86#------------------------------------------------------------------------
87class Page:
88    """ This class is a printable page.
89        Offsets from the canvas, Page numbers
90        boxes and lines
91        """
92    def __init__(self, canvas):
93        #parts from canvas
94        #self.doc = doc
95        self.canvas = canvas
96
97        #parts about the page
98        self.page_x_offset = 0
99        self.page_y_offset = 0
100        self.x_page_num = 0
101        self.y_page_num = 0
102        self.boxes = []   #All object must derive from BoxBase
103        self.lines = []   #All must derive form Linebase
104        self.note = None
105
106    def is_blank(self):
107        """ Am I a blank page?  Notes and Titles are boxes too """
108        return self.boxes == [] and self.lines == []
109
110    def add_box(self, box):
111        """ The box must derive from class Box_Base(Object): """
112        self.boxes.append(box)
113        box.page = self
114
115    def add_line(self, line):
116        """ Add a line onto this page """
117        self.lines.append(line)
118
119    def draw_border(self, line_name):
120        doc = self.canvas.doc
121        if self.y_page_num == 0:
122            doc.draw_line(line_name, 0, 0,
123                               doc.get_usable_width(), 0)
124        if self.x_page_num == 0:
125            doc.draw_line(line_name, 0, 0, 0,
126                               doc.get_usable_height())
127        if self.y_page_num == self.canvas.y_pages-1:
128            doc.draw_line(line_name, 0,
129                               doc.get_usable_height(),
130                               doc.get_usable_width(),
131                               doc.get_usable_height())
132        if self.x_page_num == self.canvas.x_pages-1:
133            doc.draw_line(line_name, doc.get_usable_width(),
134                               0, doc.get_usable_width(),
135                               doc.get_usable_height())
136
137    def display(self):
138        """ Display all boxes and lines that are on this page """
139        for box in self.boxes:
140            box.display()
141        for line in self.lines:
142            line.display(self)
143
144
145class Canvas(Page):
146    """ The Canvas is two things.
147      The all in one canvas.  a canvas is a page of unlimited size
148      a group of pages.  each page is set is size and shows only a
149       part of what is on the entire canvas
150    """
151    def __init__(self, doc, report_opts):
152        Page.__init__(self, self)
153        self.doc = doc
154        self.report_opts = report_opts
155
156        #How many pages are there in the report.  one more than real.
157        self.x_pages = 1
158        self.y_pages = 1
159        self.__pages = {(0, 0): self}  #set page 0,0 to me.
160        self.__fonts = {}  #keep a list of fonts so we don't have to lookup.
161        self.title = None
162        self.note = None
163
164    def __new_page(self, x_page, y_page, x_offset, y_offset):
165        """ Make a new page.  This will only happen if we are
166            paginating (making new pages to hold parts of the canvas) """
167        if x_page >= self.x_pages:
168            self.x_pages = x_page + 1
169        new_page = Page(self)
170        new_page.x_page_num = x_page
171        new_page.y_page_num = y_page
172        new_page.page_x_offset = x_offset
173        new_page.page_y_offset = y_offset
174        self.__pages[x_page, y_page] = new_page
175        return new_page
176
177    def sort_boxes_on_y_cm(self):
178        """ sorts the list of boxes on the canvas by .y_cm (top down) """
179        self.boxes.sort( key=lambda box: box.y_cm)
180
181    def add_title(self, title):
182        """ The title must derive from class TitleBox(BoxBase): """
183        self.title = title
184        self.title.cm_y = self.report_opts.littleoffset
185
186    def add_note(self, note):
187        """ The note must derive from class NoteBox(BoxBase, NoteType) """
188        self.note = note
189        self.set_box_height_width(self.note)
190
191    def __get_font(self, box):
192        """ returns the font used by a box.  makes a list of all seen fonts
193        to be faster.  If a new is found, run through the process to get it """
194        if box.boxstr not in self.__fonts:
195            style_sheet = self.doc.get_style_sheet()
196            style_name = style_sheet.get_draw_style(box.boxstr)
197            style_name = style_name.get_paragraph_style()
198            self.__fonts[box.boxstr] = \
199                style_sheet.get_paragraph_style(style_name).get_font()
200
201        return self.__fonts[box.boxstr]
202
203    def get_report_height_width(self):
204        """ returns the (max width, max height) of the report
205        This does not take into account any shadows """
206        max_width = 0
207        max_height = 0
208        for box in self.boxes:
209            tmp = box.x_cm + box.width
210            if tmp > max_width:
211                max_width = tmp
212            tmp = box.y_cm + box.height
213            if tmp > max_height:
214                max_height = tmp
215        max_width += self.report_opts.box_shadow
216        max_width += self.report_opts.littleoffset
217        max_height += self.report_opts.box_shadow
218        max_height += self.report_opts.littleoffset
219        return (max_width, max_height)
220
221    def __scale_canvas(self, scale_amount):
222        """ scales everything up/down depending upon scale_amount """
223        self.report_opts.scale_everything(scale_amount)
224        self.title.scale(scale_amount)
225        if self.note is not None:
226            self.note.scale(scale_amount)
227        #scale down everyone!
228        for box in self.boxes:
229            box.scale(scale_amount)
230
231    def set_box_height_width(self, box):
232        """ Sets the .width and .height of a box.  """
233        if box.boxstr == "None":
234            box.height = box.width = 0
235            return
236
237        font = self.__get_font(box)
238        #####################
239        #Get the width
240        for line in box.text:
241            width = self.doc.string_width(font, line)
242            width = PT2CM(width)
243            if width > box.width:
244                box.width = width
245
246        #####################
247        #Get the height
248        height = len(box.text) * font.get_size() * 1.5
249        height += 1.0/2.0 * font.get_size() #funny number(s) based upon font.
250        box.height = PT2CM(height)
251
252    def page_count(self, incblank):
253        count = 0
254        if incblank:
255            return self.y_pages * self.x_pages
256
257        for y_p in range(self.y_pages):
258            for x_p in range(self.x_pages):
259                if (x_p, y_p) in self.__pages:
260                    count += 1
261        return count
262
263    def page_iter_gen(self, incblank):
264        """ generate the pages of the report.  do so in a left to right
265        up down approach.  incblank asks to include blank pages """
266        blank = Page(self)
267        for y_p in range(self.y_pages):
268            for x_p in range(self.x_pages):
269                if (x_p, y_p) in self.__pages:
270                    yield self.__pages[(x_p, y_p)]
271                else:
272                    if incblank:
273                        blank.x_page_num = x_p
274                        blank.y_page_num = y_p
275                        yield blank
276
277    def __add_box_to_page(self, x_page, y_page, x_offset, y_offset, box):
278        """ adds a box to a page.  If the page is not there, make it first """
279        if (x_page, y_page) not in self.__pages:
280            #Add the new page into the dictionary
281            self.__new_page(x_page, y_page, x_offset, y_offset)
282
283        #Add the box into the page
284        self.__pages[x_page, self.y_pages-1].add_box(box)
285
286    def scale_report(self, one_page, scale_to_width, scale_to_height):
287        """ We have a report in its full size (on the canvas
288        and pages to print on.  scale one or both as needed/desired.
289
290        - one_page, boolean.  Whether to make the page(or parts of) the size
291            of the report
292        - scale_to_width, boolean.  Scale the report width to the page size?
293        - scale_to_height, boolean.  Scale the report height to page size?
294        """
295
296        if scale_to_width or scale_to_height:
297            max_width, max_height = self.canvas.get_report_height_width()
298            #max_width  += self.report_opts.littleoffset
299            #max_height += self.report_opts.littleoffset
300
301        """
302        calc - Calculate the scale amount (if any).
303          <1 everything is smaller to fit on the page
304          1 == no scaling
305          >1 make everything bigger to fill out the page
306        """
307        scale = 1
308        scaled_report_to = None
309
310        #####################
311        #scale the report option - width
312        if scale_to_width:
313            #Check the width of the title
314            title_width = self.title.width
315            title_width += self.report_opts.littleoffset * 2
316
317            max_width = max(title_width, max_width)
318
319            #This will be our base amount and
320            #then we will decrease only as needed from here.
321
322            scale = self.doc.get_usable_width() / max_width
323            scaled_report_to = "width"
324
325        #####################
326        #scale the report option - height
327        if scale_to_height:
328            tmp = self.doc.get_usable_height() / max_height
329            if not scale_to_width or tmp < scale:
330                scale = tmp
331                scaled_report_to = "height"
332
333        #Now I have the scale amount
334        if scale != 1:  #scale everything on the canvas
335            self.__scale_canvas(scale)
336
337        #####################
338        #Scale the page option
339        if one_page:
340
341            #user wants PAGE to be the size of the report.
342            size = self.doc.paper.get_size()
343            size.name = 'custom'
344
345            max_width, max_height = \
346                self.canvas.get_report_height_width()
347
348            if scaled_report_to != "width":
349                #calculate the width of the report
350                #max_width  += self.report_opts.littleoffset
351                max_width += self.doc.paper.get_left_margin()
352                max_width += self.doc.paper.get_right_margin()
353
354                #calculate the width of the title
355                title_width = self.canvas.title.width
356                title_width += self.doc.paper.get_left_margin()
357                title_width += self.doc.paper.get_right_margin()
358                title_width += self.report_opts.littleoffset
359                max_width = max(title_width, max_width)
360
361                size.set_width(max_width)
362
363            if scaled_report_to != "height":
364                #calculate the height of the report
365                max_height += self.doc.paper.get_top_margin()
366                max_height += self.doc.paper.get_bottom_margin()
367                #max_height += self.report_opts.littleoffset
368                size.set_height(max_height)
369
370        return scale
371
372
373    def __paginate_x_offsets(self, colsperpage):
374        """ Go through the boxes and get the x page offsets """
375        #fix soon.  should not use .level
376        liloffset = self.report_opts.littleoffset
377        x_page_offsets = {0:0}  #change me to [] ???
378        for box in self.boxes:
379            x_index = box.level[0]
380            x_page = x_index // colsperpage
381            if x_page not in x_page_offsets and x_index % colsperpage == 0:
382                x_page_offsets[x_page] = box.x_cm - liloffset
383                if x_page >= self.x_pages:
384                    self.x_pages = x_page+1
385        return x_page_offsets
386
387    def __paginate_y_pages(self, colsperpage, x_page_offsets):
388        """ Go through the boxes and put each one in a page
389        note that the self.boxes needs to be sorted by .y_cm """
390        page_y_top = [0]
391        page_y_height = [self.doc.get_usable_height()]
392        liloffset = self.report_opts.littleoffset
393
394        for box in self.boxes:
395            #check to see if this box cross over to the next (y) page
396            height = box.y_cm + liloffset + box.height #+ box.shadow/2
397
398            if height > page_y_height[-1]:
399                #we went off the end
400                page_y_height.append(box.y_cm - liloffset + page_y_height[0])
401                page_y_top.append(box.y_cm - liloffset)
402                self.y_pages = len(page_y_height)
403
404            #Calculate my (x) page
405            #fix soon.  should not use .level
406            x_page = box.level[0] // colsperpage
407
408            self.__add_box_to_page(x_page, self.y_pages-1,
409                                   x_page_offsets[x_page],
410                                   page_y_top[self.y_pages-1],
411                                   box)
412            #if not self.__pages.has_key((x_page, self.y_pages-1)):
413            #    #Add the new page into the dictionary
414            #    self.__new_page(x_page, self.y_pages-1,
415            #                    )
416            #
417            ##Add the box into the page
418            #self.__pages[x_page, self.y_pages-1].add_box(box)
419        return page_y_top
420
421    def __paginate_note(self, x_page_offsets, page_y_top):
422        """ Put the note on first.  it can be overwritten by other
423        boxes but it CAN NOT overwrite a box. """
424        x_page, y_page = self.note.set_on_page(self)
425        if (x_page, y_page) not in self.__pages:
426            #Add the new page into the dictionary
427            self.__new_page(x_page, y_page,
428                            x_page_offsets[x_page], page_y_top[y_page])
429        #Add the box into the page
430        self.__pages[x_page, y_page].boxes.insert(0, self.note)
431        self.note.doc = self.doc
432        self.note.page = self
433
434    def __paginate_lines(self, x_page_offsets, page_y_top):
435        """ Step three go through the lines and put each in page(s) """
436        for box1 in self.boxes:
437            if not box1.line_to:
438                continue
439
440            line = box1.line_to
441
442            pages = [box1.page.y_page_num]
443
444            end = line.start + line.end
445
446            x_page = box1.page.x_page_num
447            start_y_page = end[0].page.y_page_num
448            end_y_page = end[0].page.y_page_num
449
450            for box in end:
451                y_page = box.page.y_page_num
452                if y_page not in pages:
453                    if (x_page, y_page) not in self.__pages:
454                        #Add the new page into the dictionary
455                        self.__new_page(x_page, y_page,
456                                        x_page_offsets[x_page],
457                                        page_y_top[y_page])
458                    self.__pages[x_page, y_page].add_line(box1.line_to)
459                    pages.append(y_page)
460
461                if y_page < start_y_page:
462                    start_y_page = y_page
463                if y_page > end_y_page:
464                    end_y_page = y_page
465
466            #if len(end) = 2 & end[0].y_page = 0 & end[1].y_page = 4
467            #the line will not print on y_pages 1,2,3.  Fix that here.
468            #x_page = start_x_page
469            for y_page in range(start_y_page, end_y_page+1):
470                if y_page not in pages:
471                    if (x_page, y_page) not in self.__pages:
472                        #Add the new page into the dictionary
473                        self.__new_page(x_page, y_page,
474                                        x_page_offsets[x_page],
475                                        page_y_top[y_page])
476                    self.__pages[x_page, y_page].add_line(box1.line_to)
477
478    def __paginate_title(self, x_page_offsets):
479        #step four work with the title
480        if self.title.boxstr == "None":
481            return
482        #x_page_offsets[page] tells me the widths I can use
483        if len(x_page_offsets) > 1:
484            if self.title.mark_text and not self.title.text:
485                self.title.width = self.doc.get_usable_width()
486                self.__pages[list(self.__pages.keys())[0]].add_box(self.title)
487                return
488            title_list = self.title.text.split(" ")
489            title_font = self.__get_font(self.title)
490            #space_width = PT2CM(self.doc.string_width(title_font," "))
491
492            list_title = [title_list.pop(0)]
493            while len(title_list):
494                tmp = list_title[-1] + " " + title_list[0]
495                if PT2CM(self.doc.string_width(title_font, tmp)) > \
496                   x_page_offsets[1]:
497                    list_title.append("")
498                if list_title[-1] != "":
499                    list_title[-1] += " "
500                list_title[-1] += title_list.pop(0)
501
502            start_page = int((len(x_page_offsets) - len(list_title)) / 2)
503            for tmp in range(start_page):
504                list_title.insert(0, "")
505                list_title.append("")
506            #one extra for security.  doesn't hurt.
507            list_title.append("")
508
509            x_page = 0
510            for title in list_title:
511                if title == "":
512                    x_page += 1
513                    continue
514                if (x_page, 0) not in self.__pages:
515                    #Add the new page into the dictionary
516                    self.__new_page(x_page, 0, x_page_offsets[1], 0)
517
518                title_part = TitleBox(self.doc, self.title.boxstr)
519                title_part.text = list_title[x_page]
520                title_part.width = x_page_offsets[1]
521
522                #Add the box into the page
523                self.__pages[x_page, 0].add_box(title_part)
524                x_page = x_page + 1
525        else:
526            self.title.width = self.doc.get_usable_width()
527            self.__pages[0, 0].add_box(self.title)
528
529    def __paginate(self, colsperpage):
530        """ take the boxes on the canvas and put them into separate pages.
531        The boxes need to be sorted by y_cm """
532        liloffset = self.report_opts.littleoffset
533        self.__pages = {}
534        x_page_offsets = self.__paginate_x_offsets(colsperpage)
535        page_y_top = self.__paginate_y_pages(colsperpage, x_page_offsets)
536
537        if self.note is not None:
538            self.__paginate_note(x_page_offsets, page_y_top)
539        self.__paginate_lines(x_page_offsets, page_y_top)
540        self.__paginate_title(x_page_offsets)
541
542
543    def paginate(self, colsperpage, one_page_report):
544        """ self.boxes must be sorted by box.y_cm for this to work.  """
545        if one_page_report:
546            #self.canvas.add_box(self.canvas.title)
547            title_part = TitleBox(self.doc, self.title.boxstr)
548            title_part.text = self.title.text
549            title_part.width = self.doc.get_usable_width()
550            self.add_box(title_part)
551
552            if self.note is not None:
553                self.note.set_on_page(self)
554                self.boxes.insert(0, self.note)
555                self.note.doc = self.doc
556                self.note.page = self
557        else:
558            self.__paginate(colsperpage)
559
560
561#------------------------------------------------------------------------
562#
563# Class Box_Base
564#
565#------------------------------------------------------------------------
566class BoxBase:
567    """ boxes are always in/on a Page
568    Needed to print are: boxstr, text, x_cm, y_cm, width, height
569    """
570    def __init__(self):
571        self.page = None
572
573        #'None' will cause an error.  Sub-classes will init
574        self.boxstr = "None"
575        self.text = ""
576        #level requires ...
577        # (#  - which column am I in  (zero based)
578        # ,#  - Am I a (0)direct descendant/ancestor or (>0)other
579        # , ) - anything else the report needs to run
580        self.__mark = None  #Database person object
581        self.level = (0,0)
582        self.x_cm = 0.0
583        self.y_cm = 0.0
584        self.width = 0.0
585        self.height = 0.0
586        self.line_to = None
587        #if text in TOC needs to be different from text, set mark_text
588        self.mark_text = None
589
590    def scale(self, scale_amount):
591        """ Scale the amounts """
592        self.x_cm *= scale_amount
593        self.y_cm *= scale_amount
594        self.width *= scale_amount
595        self.height *= scale_amount
596
597    def add_mark(self, database, person):
598        self.__mark = utils.get_person_mark(database, person)
599
600    def display(self):
601        """ display the box accounting for page x, y offsets
602        Ignore any box with 'None' is boxstr """
603        if self.boxstr == "None":
604            return
605
606        doc = self.page.canvas.doc
607        report_opts = self.page.canvas.report_opts
608        text = '\n'.join(self.text)
609        xbegin = self.x_cm - self.page.page_x_offset
610        ybegin = self.y_cm - self.page.page_y_offset
611
612        doc.draw_box(self.boxstr,
613                text,
614                xbegin, ybegin,
615                self.width, self.height, self.__mark)
616
617        #I am responsible for my own lines. Do them here.
618        if self.line_to:
619            #draw my line out here.
620            self.line_to.display(self.page)
621        if self.page.x_page_num > 0 and self.level[1] == 0 and \
622           xbegin < report_opts.littleoffset*2:
623            #I am a child on the first column
624            yme = ybegin + self.height/2
625            doc.draw_line(report_opts.line_str, 0, yme, xbegin, yme)
626
627class TitleNoDisplay(BoxBase):
628    """
629    Holds information about the Title that will print on a TOC
630    and NOT on the report
631    """
632    def __init__(self, doc, boxstr):
633        """ initialize the title box """
634        BoxBase.__init__(self)
635        self.doc = doc
636        self.boxstr = boxstr
637
638    def set_box_height_width(self):
639        self.width = self.height = 0
640
641    def display(self):
642        """ display the title box.  """
643        #Set up the Table of Contents here
644        if self.mark_text is None:
645            mark = IndexMark(self.text, INDEX_TYPE_TOC, 1)
646        else:
647            mark = IndexMark(self.mark_text, INDEX_TYPE_TOC, 1)
648        self.doc.center_text(self.boxstr, '', 0, -100, mark)
649
650class TitleBox(BoxBase):
651    """
652    Holds information about the Title that will print on a page
653    """
654    def __init__(self, doc, boxstr):
655        """ initialize the title box """
656        BoxBase.__init__(self)
657        self.doc = doc
658        self.boxstr = boxstr
659        if boxstr == "None":
660            return
661
662        style_sheet = self.doc.get_style_sheet()
663        style_name = style_sheet.get_draw_style(self.boxstr)
664        style_name = style_name.get_paragraph_style()
665        self.font = style_sheet.get_paragraph_style(style_name).get_font()
666
667    def set_box_height_width(self):
668        if self.boxstr == "None":
669            return
670        #fix me. width should be the printable area
671        self.width = PT2CM(self.doc.string_width(self.font, self.text))
672        self.height = PT2CM(self.font.get_size() * 2)
673
674    def _get_names(self, persons, name_displayer):
675        """  A helper function that receives a list of persons and
676        returns their names in a list """
677        return [name_displayer.display(person) for person in persons]
678
679    def display(self):
680        """ display the title box.  """
681        if self.page.y_page_num or self.boxstr == "None":
682            return
683
684        #Set up the Table of Contents here
685        mark = IndexMark(self.text, INDEX_TYPE_TOC, 1)
686
687        if self.text:
688            self.doc.center_text(self.boxstr, self.text,
689                             self.width/2, self.y_cm, mark)
690
691class PageNumberBox(BoxBase):
692    """
693    Calculates information about the page numbers that will print on a page
694    do not put in a value for PageNumberBox.text.  this will be calculated for
695    each page """
696
697    def __init__(self, doc, boxstr, locale):
698        """ initialize the page number box """
699        BoxBase.__init__(self)
700        self.doc = doc
701        self.boxstr = boxstr
702        self._ = locale.translation.sgettext
703
704    def __calc_position(self, page):
705        """ calculate where I am to print on the page(s) """
706        # translators: needed for Arabic, ignore otherwise
707        self.text = "(%d" + self._(',') + "%d)"
708
709        style_sheet = self.doc.get_style_sheet()
710        style_name = style_sheet.get_draw_style(self.boxstr)
711        style_name = style_name.get_paragraph_style()
712        font = style_sheet.get_paragraph_style(style_name).get_font()
713
714        #calculate how much space is needed
715        if page.canvas.x_pages > 10:
716            tmp = "00"
717        else:
718            tmp = "0"
719        if page.canvas.y_pages > 10:
720            tmp += "00"
721        else:
722            tmp += "0"
723
724        width = self.doc.string_width(font, '(,)'+tmp)
725        width = PT2CM(width)
726        self.width = width
727
728        height = font.get_size() * 1.4
729        height += 0.5/2.0 * font.get_size() #funny number(s) based upon font.
730        self.height = PT2CM(height)
731
732        self.x_cm = self.doc.get_usable_width() - self.width
733        self.y_cm = self.doc.get_usable_height() - self.height
734
735    def display(self, page):
736        """ If this is the first time I am ran, get my position
737        then display the page number """
738        if self.text == "":
739            self.__calc_position(page)
740
741        self.doc.draw_text(self.boxstr,
742                   self.text % (page.x_page_num+1, page.y_page_num+1),
743                   self.x_cm, self.y_cm)
744
745class NoteType:
746    """  Provide the different options (corners) to place the note """
747
748    TOPLEFT = 0
749    TOPRIGHT = 1
750    BOTTOMLEFT = 2
751    BOTTOMRIGHT = 3
752
753    _DEFAULT = BOTTOMRIGHT
754
755    _DATAMAP = [
756        (TOPLEFT,     _("Top Left"),     "Top Left"),
757        (TOPRIGHT,    _("Top Right"),    "Top Right"),
758        (BOTTOMLEFT,  _("Bottom Left"),  "Bottom Left"),
759        (BOTTOMRIGHT, _("Bottom Right"), "Bottom Right"),
760        ]
761
762    def __init__(self, value, exclude=None):
763        """ initialize GrampsType """
764        self.value = value
765        self.exclude = exclude
766        #GrampsType.__init__(self, value)
767
768    def note_locals(self, start=0):
769        """ generates an int of all the options """
770        for tuple  in self._DATAMAP:
771            if tuple[0] != self.exclude:
772                yield tuple[0], tuple[1]
773
774class NoteBox(BoxBase, NoteType):
775    """ Box that will hold the note to display on the page """
776
777    def __init__(self, doc, boxstr, box_corner, exclude=None):
778        """ initialize the NoteBox """
779        BoxBase.__init__(self)
780        NoteType.__init__(self, box_corner, exclude)
781        self.doc = doc
782        self.boxstr = boxstr
783
784    def set_on_page(self, canvas):
785        """ set the x_cm and y_cm given
786        self.doc, leloffset, and title_height """
787
788        liloffset = canvas.report_opts.littleoffset
789        #left or right side
790        if self.value == NoteType.BOTTOMLEFT or \
791                           self.value == NoteType.TOPLEFT:
792            self.x_cm = liloffset
793        else:
794            self.x_cm = self.doc.get_usable_width() - self.width - liloffset
795        #top or bottom
796        if self.value == NoteType.TOPRIGHT or \
797                           self.value == NoteType.TOPLEFT:
798            self.y_cm = canvas.title.height + liloffset*2
799        else:
800            self.y_cm = self.doc.get_usable_height() - self.height - liloffset
801
802        """ helper function for canvas.paginate().
803        return the (x, y) page I want to print on """
804        if self.value == NoteType.TOPLEFT:
805            return (0, 0)
806        elif self.value == NoteType.TOPRIGHT:
807            return (canvas.x_pages-1, 0)
808        elif self.value == NoteType.BOTTOMLEFT:
809            return (0, canvas.y_pages-1)
810        elif self.value == NoteType.BOTTOMRIGHT:
811            return (canvas.x_pages-1, canvas.y_pages-1)
812
813    def display(self):
814        """ position the box and display """
815        title = self.page.canvas.title
816        title_height = 0
817        if title is not None:
818            title_height = title.height
819        text = '\n'.join(self.text)
820        self.doc.draw_box(self.boxstr, text,
821           self.x_cm, self.y_cm,
822           self.width, self.height)
823
824
825#------------------------------------------------------------------------
826#
827# Class Line_base
828#
829#------------------------------------------------------------------------
830class LineBase:
831    """ A simple line class.
832    self.start is the box that we are drawing a line from
833    self.end are the boxes that we are drawing lines to.
834    """
835    def __init__(self, start):
836        #self.linestr = "None"
837        self.start = [start]
838        self.end = []
839
840    def add_from(self, person):
841        self.start.append(person)
842
843    def add_to(self, person):
844        """ add destination boxes to draw this line to """
845        self.end.append(person)
846
847    def display(self, page):
848        """ display the line.  left to right line.  one start, multiple end.
849        page will tell us what parts of the line we can print """
850        if self.end == [] and len(self.start) == 1:
851            return
852
853        # y_cm and x_cm start points - take into account page offsets
854        #yme = self.start.y_cm + self.start.height/2 - page.page_y_offset
855        #if type(self.start) != type([]):
856        #    self.start = [self.start]
857        start = self.start[0]
858        doc = start.page.canvas.doc
859        report_opts = start.page.canvas.report_opts
860        linestr = report_opts.line_str
861
862        xbegin = start.x_cm + start.width - page.page_x_offset
863        # out 3/4 of the way and x_cm end point(s)
864        x34 = xbegin + (report_opts.col_width * 3/4)
865        xend = xbegin + report_opts.col_width
866
867        if x34 > 0:  # > 0 tell us we are printing on this page.
868            usable_height = doc.get_usable_height()
869            #1 - Line from start box out
870            for box in self.start:
871                yme = box.y_cm + box.height/2 - page.page_y_offset
872                if box.page.y_page_num == page.y_page_num:
873                    # and 0 < yme < usable_height and \
874                    doc.draw_line(linestr, xbegin, yme, x34, yme)
875
876            #2 - vertical line
877            mid = []
878            for box in self.start + self.end:
879                tmp = box.y_cm + box.height/2
880                mid.append(tmp)
881            mid.sort()
882            mid = [mid[0]-page.page_y_offset, mid[-1]-page.page_y_offset]
883            if mid[0] < 0:
884                mid[0] = 0
885            if mid[1] > usable_height:
886                mid[1] = usable_height
887            #draw the connecting vertical line.
888            doc.draw_line(linestr, x34, mid[0], x34, mid[1])
889        else:
890            x34 = 0
891
892        #3 - horizontal line(s)
893        for box in self.end:
894            if box.page.y_page_num == page.y_page_num:
895                yme = box.y_cm + box.height/2 - box.page.page_y_offset
896                doc.draw_line(linestr, x34, yme, xend, yme)
897
898
899#------------------------------------------------------------------------
900#
901# Class report_options
902#
903#------------------------------------------------------------------------
904class ReportOptions:
905    """
906    A simple class to hold various report information
907    Calculates
908      the gap between persons,
909      the column width, for lines,
910      the left hand spacing for spouses (Descendant report only)
911    """
912
913    def __init__(self, doc, normal_font, normal_line):
914        """ initialize various report variables that are used """
915        self.box_pgap = PT2CM(1.25*normal_font.get_size()) #gap between persons
916        self.box_mgap = self.box_pgap /2 #gap between marriage information
917        self.box_shadow = PT2CM(normal_font.get_size()) * .6 #normal text
918        self.spouse_offset = PT2CM(doc.string_width(normal_font, "0"))
919
920        self.col_width = PT2CM(doc.string_width(normal_font, "(000,0)"))
921        self.littleoffset = PT2CM(1)
922        self.x_cm_cols = [self.littleoffset]
923
924        self.line_str = normal_line
925
926        #Things that will get added later
927        self.max_box_width = 0
928        self.max_box_height = 0
929
930        self.scale = 1
931
932    def scale_everything(self, amount):
933        """ Scale the amounts that are needed to generate a report """
934        self.scale = amount
935
936        self.col_width *= amount
937        self.littleoffset *= amount
938
939        self.max_box_width *= amount  #box_width
940        self.spouse_offset *= amount
941        self.box_shadow *= amount
942
943#=====================================
944#"And Jesus said unto them ... , "If ye have faith as a grain of mustard
945#seed, ye shall say unto this mountain, Remove hence to yonder place; and
946#it shall remove; and nothing shall be impossible to you."
947#Romans 1:17
948