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