1# 2# Gramps - a GTK+/GNOME based genealogy program 3# 4# Copyright (C) 2004-2007 Donald N. Allingham 5# Copyright (C) 2008,2011 Gary Burton 6# Copyright (C) 2010 Jakim Friant 7# Copyright (C) 2011-2012 Paul Franklin 8# Copyright (C) 2012 Brian G. Matherly 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# Written by Alex Roitman 26 27""" 28Report option handling, including saving and parsing. 29""" 30 31#------------------------------------------------------------------------- 32# 33# Standard Python modules 34# 35#------------------------------------------------------------------------- 36import os 37import copy 38 39#------------------------------------------------------------------------- 40# 41# SAX interface 42# 43#------------------------------------------------------------------------- 44from xml.sax import make_parser, SAXParseException 45from xml.sax.saxutils import escape 46 47#------------------------------------------------------------------------ 48# 49# Set up logging 50# 51#------------------------------------------------------------------------ 52import logging 53LOG = logging.getLogger(".plug.report.options") 54 55#------------------------------------------------------------------------- 56# 57# gramps modules 58# 59# (do not import anything from 'gui' as this is in 'gen') 60# 61#------------------------------------------------------------------------- 62from ...const import HOME_DIR, REPORT_OPTIONS 63from ...config import config 64from ..docgen import PAPER_PORTRAIT 65from .. import _options 66from .. import MenuOptions 67from ...utils.cast import get_type_converter 68 69def escxml(word): 70 return escape(word, {'"' : '"'}) 71 72#------------------------------------------------------------------------- 73# 74# List of options for a single report 75# 76#------------------------------------------------------------------------- 77class OptionList(_options.OptionList): 78 """ 79 Implements a set of options to parse and store for a given report. 80 """ 81 82 def __init__(self): 83 _options.OptionList.__init__(self) 84 self.style_name = None 85 self.paper_metric = None 86 self.paper_name = None 87 self.orientation = None 88 self.custom_paper_size = [29.7, 21.0] 89 self.margins = [2.54, 2.54, 2.54, 2.54] 90 self.format_name = None 91 self.css_filename = None 92 self.output = None 93 94 def set_style_name(self, style_name): 95 """ 96 Set the style name for the OptionList. 97 98 :param style_name: name of the style to set. 99 :type style_name: str 100 """ 101 self.style_name = style_name 102 103 def get_style_name(self): 104 """ 105 Return the style name of the OptionList. 106 107 :returns: string representing the style name 108 :rtype: str 109 """ 110 return self.style_name 111 112 def set_paper_metric(self, paper_metric): 113 """ 114 Set the paper metric for the OptionList. 115 116 :param paper_metric: whether to use metric. 117 :type paper_name: boolean 118 """ 119 self.paper_metric = paper_metric 120 121 def get_paper_metric(self): 122 """ 123 Return the paper metric of the OptionList. 124 125 :returns: returns whether to use metric 126 :rtype: boolean 127 """ 128 return self.paper_metric 129 130 def set_paper_name(self, paper_name): 131 """ 132 Set the paper name for the OptionList. 133 134 :param paper_name: name of the paper to set. 135 :type paper_name: str 136 """ 137 self.paper_name = paper_name 138 139 def get_paper_name(self): 140 """ 141 Return the paper name of the OptionList. 142 143 :returns: returns the paper name 144 :rtype: str 145 """ 146 return self.paper_name 147 148 def set_orientation(self, orientation): 149 """ 150 Set the orientation for the OptionList. 151 152 :param orientation: orientation to set. Possible values are 153 PAPER_LANDSCAPE or PAPER_PORTRAIT 154 :type orientation: int 155 """ 156 self.orientation = orientation 157 158 def get_orientation(self): 159 """ 160 Return the orientation for the OptionList. 161 162 :returns: returns the selected orientation. Valid values are 163 PAPER_LANDSCAPE or PAPER_PORTRAIT 164 :rtype: int 165 """ 166 return self.orientation 167 168 def set_custom_paper_size(self, paper_size): 169 """ 170 Set the custom paper size for the OptionList. 171 172 :param paper_size: paper size to set in cm. 173 :type paper_size: [float, float] 174 """ 175 self.custom_paper_size = paper_size 176 177 def get_custom_paper_size(self): 178 """ 179 Return the custom paper size for the OptionList. 180 181 :returns: returns the custom paper size in cm 182 :rtype: [float, float] 183 """ 184 return self.custom_paper_size 185 186 def set_margins(self, margins): 187 """ 188 Set the margins for the OptionList. 189 190 :param margins: margins to set. Possible values are floats in cm 191 :type margins: [float, float, float, float] 192 """ 193 self.margins = copy.copy(margins) 194 195 def get_margins(self): 196 """ 197 Return the margins for the OptionList. 198 199 :returns margins: returns the margins, floats in cm 200 :rtype margins: [float, float, float, float] 201 """ 202 return copy.copy(self.margins) 203 204 def set_margin(self, pos, value): 205 """ 206 Set a margin for the OptionList. 207 208 :param pos: Position of margin [left, right, top, bottom] 209 :type pos: int 210 :param value: floating point in cm 211 :type value: float 212 """ 213 self.margins[pos] = value 214 215 def get_margin(self, pos): 216 """ 217 Return a margin for the OptionList. 218 219 :param pos: Position of margin [left, right, top, bottom] 220 :type pos: int 221 :returns: float cm of margin 222 :rtype: float 223 """ 224 return self.margins[pos] 225 226 def set_css_filename(self, css_filename): 227 """ 228 Set the template name for the OptionList. 229 230 :param template_name: name of the template to set. 231 :type template_name: str 232 """ 233 self.css_filename = css_filename 234 235 def get_css_filename(self): 236 """ 237 Return the template name of the OptionList. 238 239 :returns: template name 240 :rtype: str 241 """ 242 return self.css_filename 243 244 def set_format_name(self, format_name): 245 """ 246 Set the format name for the OptionList. 247 248 :param format_name: name of the format to set. 249 :type format_name: str 250 """ 251 self.format_name = format_name 252 253 def get_format_name(self): 254 """ 255 Return the format name of the OptionList. 256 257 :returns: returns the format name 258 :rtype: str 259 """ 260 return self.format_name 261 262 def set_output(self, output): 263 """ 264 Set the output for the OptionList. 265 266 :param output: name of the output to set. 267 :type output: str 268 """ 269 self.output = output 270 271 def get_output(self): 272 """ 273 Return the output of the OptionList. 274 275 :returns: returns the output name 276 :rtype: str 277 """ 278 return self.output 279 280#------------------------------------------------------------------------- 281# 282# Collection of option lists 283# 284#------------------------------------------------------------------------- 285class OptionListCollection(_options.OptionListCollection): 286 """ 287 Implements a collection of option lists. 288 """ 289 def __init__(self, filename): 290 _options.OptionListCollection.__init__(self, filename) 291 292 def init_common(self): 293 # Default values for common options 294 self.default_style_name = "default" 295 self.default_paper_metric = config.get('preferences.paper-metric') 296 self.default_paper_name = config.get('preferences.paper-preference') 297 self.default_orientation = PAPER_PORTRAIT 298 self.default_css_filename = "" 299 self.default_custom_paper_size = [29.7, 21.0] 300 self.default_margins = [2.54, 2.54, 2.54, 2.54] 301 self.default_format_name = 'print' 302 303 self.last_paper_metric = self.default_paper_metric 304 self.last_paper_name = self.default_paper_name 305 self.last_orientation = self.default_orientation 306 self.last_custom_paper_size = copy.copy(self.default_custom_paper_size) 307 self.last_margins = copy.copy(self.default_margins) 308 self.last_css_filename = self.default_css_filename 309 self.last_format_name = self.default_format_name 310 self.option_list_map = {} 311 312 def set_last_paper_metric(self, paper_metric): 313 """ 314 Set the last paper metric used for any report in this collection. 315 316 :param paper_metric: whether to use metric. 317 :type paper_name: boolean 318 """ 319 self.last_paper_metric = paper_metric 320 321 def get_last_paper_metric(self): 322 """ 323 Return the last paper metric used for any report in this collection. 324 325 :returns: returns whether or not to use metric 326 :rtype: boolean 327 """ 328 return self.last_paper_metric 329 330 def set_last_paper_name(self, paper_name): 331 """ 332 Set the last paper name used for any report in this collection. 333 334 :param paper_name: name of the paper to set. 335 :type paper_name: str 336 """ 337 self.last_paper_name = paper_name 338 339 def get_last_paper_name(self): 340 """ 341 Return the last paper name used for any report in this collection. 342 343 :returns: returns the name of the paper 344 :rtype: str 345 """ 346 return self.last_paper_name 347 348 def set_last_orientation(self, orientation): 349 """ 350 Set the last orientation used for any report in this collection. 351 352 :param orientation: orientation to set. 353 :type orientation: int 354 """ 355 self.last_orientation = orientation 356 357 def get_last_orientation(self): 358 """ 359 Return the last orientation used for any report in this collection. 360 361 :returns: last orientation used 362 :rtype: int 363 """ 364 return self.last_orientation 365 366 def set_last_custom_paper_size(self, custom_paper_size): 367 """ 368 Set the last custom paper size used for any report in this collection. 369 370 :param custom_paper_size: size to set in cm (width, height) 371 :type custom_paper_size: [float, float] 372 """ 373 self.last_custom_paper_size = copy.copy(custom_paper_size) 374 375 def get_last_custom_paper_size(self): 376 """ 377 Return the last custom paper size used for any report in this 378 collection. 379 380 :returns: list of last custom paper size used in cm (width, height) 381 :rtype: [float, float] 382 """ 383 return copy.copy(self.last_custom_paper_size) 384 385 def set_last_margins(self, margins): 386 """ 387 Set the last margins used for any report in this collection. 388 389 :param margins: margins to set in cm (left, right, top, bottom) 390 :type margins: [float, float, float, float] 391 """ 392 self.last_margins = copy.copy(margins) 393 394 def get_last_margins(self): 395 """ 396 Return the last margins used for any report in this 397 collection. 398 399 :returns: list of last margins used in cm (left, right, top, bottom) 400 :rtype: [float, float, float, float] 401 """ 402 return copy.copy(self.last_margins) 403 404 def set_last_margin(self, pos, value): 405 """ 406 Set the last margin used for any report in this collection. 407 408 :param pos: pos to set (0-4) (left, right, top, bottom) 409 :type pos: int 410 :param value: value to set the margin to in cm 411 :type value: float 412 """ 413 self.last_margins[pos] = value 414 415 def get_last_margin(self, pos): 416 """ 417 Return the last margins used for any report in this collection. 418 419 :param pos: position in margins list 420 :type pos: int 421 :returns: last margin used in pos 422 :rtype: float 423 """ 424 return self.last_margins[pos] 425 426 def set_last_css_filename(self, css_filename): 427 """ 428 Set the last css used for any report in this collection. 429 430 :param css_filename: name of the style to set. 431 """ 432 self.last_css_name = css_filename 433 434 def get_last_css_filename(self): 435 """ 436 Return the last template used for any report in this collection. 437 """ 438 return self.last_css_filename 439 440 def set_last_format_name(self, format_name): 441 """ 442 Set the last format used for any report in this collection. 443 444 :param format_name: name of the format to set. 445 """ 446 self.last_format_name = format_name 447 448 def get_last_format_name(self): 449 """ 450 Return the last format used for any report in this collection. 451 """ 452 return self.last_format_name 453 454 def write_common(self, file): 455 file.write('<last-common>\n') 456 if self.get_last_paper_metric() != self.default_paper_metric: 457 file.write(' <metric value="%d"/>\n' 458 % self.get_last_paper_metric()) 459 if self.get_last_custom_paper_size() != self.default_custom_paper_size: 460 size = self.get_last_custom_paper_size() 461 file.write(' <size value="%f %f"/>\n' % (size[0], size[1])) 462 if self.get_last_paper_name() != self.default_paper_name: 463 file.write(' <paper name="%s"/>\n' 464 % escxml(self.get_last_paper_name())) 465 if self.get_last_css_filename() != self.default_css_filename: 466 file.write(' <css name="%s"/>\n' 467 % escxml(self.get_last_css_filename())) 468 if self.get_last_format_name() != self.default_format_name: 469 file.write(' <format name="%s"/>\n' 470 % escxml(self.get_last_format_name())) 471 if self.get_last_orientation() != self.default_orientation: 472 file.write(' <orientation value="%d"/>\n' 473 % self.get_last_orientation()) 474 file.write('</last-common>\n') 475 476 def write_module_common(self, file, option_list): 477 if option_list.get_format_name(): 478 file.write(' <format name="%s"/>\n' 479 % escxml(option_list.get_format_name())) 480 if option_list.get_format_name() == 'html': 481 if option_list.get_css_filename(): 482 file.write(' <css name="%s"/>\n' 483 % escxml(option_list.get_css_filename())) 484 else: # not HTML format, therefore it's paper 485 if option_list.get_paper_name(): 486 file.write(' <paper name="%s"/>\n' 487 % escxml(option_list.get_paper_name())) 488 if option_list.get_orientation() is not None: # 0 is legal 489 file.write(' <orientation value="%d"/>\n' 490 % option_list.get_orientation()) 491 if option_list.get_paper_metric() is not None: # 0 is legal 492 file.write(' <metric value="%d"/>\n' 493 % option_list.get_paper_metric()) 494 if option_list.get_custom_paper_size(): 495 size = option_list.get_custom_paper_size() 496 file.write(' <size value="%f %f"/>\n' 497 % (size[0], size[1])) 498 if option_list.get_margins(): 499 margins = option_list.get_margins() 500 for pos in range(len(margins)): 501 file.write(' <margin number="%s" value="%f"/>\n' 502 % (pos, margins[pos])) 503 504 if option_list.get_style_name(): 505 file.write(' <style name="%s"/>\n' 506 % escxml(option_list.get_style_name())) 507 if option_list.get_output(): 508 file.write(' <output name="%s"/>\n' 509 % escxml(os.path.basename(option_list.get_output()))) 510 511 def parse(self): 512 """ 513 Loads the :class:`OptionList` from the associated file, if it exists. 514 """ 515 try: 516 if os.path.isfile(self.filename): 517 parser = make_parser() 518 parser.setContentHandler(OptionParser(self)) 519 with open(self.filename, encoding="utf-8") as the_file: 520 parser.parse(the_file) 521 except (IOError, OSError, SAXParseException): 522 pass 523 524#------------------------------------------------------------------------- 525# 526# OptionParser 527# 528#------------------------------------------------------------------------- 529class OptionParser(_options.OptionParser): 530 """ 531 SAX parsing class for the OptionListCollection XML file. 532 """ 533 534 def __init__(self, collection): 535 """ 536 Create a OptionParser class that populates the passed collection. 537 538 collection: OptionListCollection to be loaded from the file. 539 """ 540 _options.OptionParser.__init__(self, collection) 541 self.common = False 542 self.list_class = OptionList 543 544 def startElement(self, tag, attrs): 545 """ 546 Overridden class that handles the start of a XML element 547 """ 548 # First we try report-specific tags 549 if tag == "last-common": 550 self.common = True 551 elif tag == 'docgen-option': 552 if not self.common: 553 self.oname = attrs['name'] 554 self.an_o = [attrs['docgen'], attrs['value']] 555 elif tag == "style": 556 self.option_list.set_style_name(attrs['name']) 557 elif tag == "paper": 558 if self.common: 559 self.collection.set_last_paper_name(attrs['name']) 560 else: 561 self.option_list.set_paper_name(attrs['name']) 562 elif tag == "css": 563 if self.common: 564 self.collection.set_last_css_filename(attrs['name']) 565 else: 566 self.option_list.set_css_filename(attrs['name']) 567 elif tag == "format": 568 if self.common: 569 self.collection.set_last_format_name(attrs['name']) 570 else: 571 self.option_list.set_format_name(attrs['name']) 572 elif tag == "orientation": 573 if self.common: 574 self.collection.set_last_orientation(int(attrs['value'])) 575 else: 576 self.option_list.set_orientation(int(attrs['value'])) 577 elif tag == "metric": 578 if self.common: 579 self.collection.set_last_paper_metric(int(attrs['value'])) 580 else: 581 self.option_list.set_paper_metric(int(attrs['value'])) 582 elif tag == "size": 583 width, height = attrs['value'].split() 584 width = float(width) 585 height = float(height) 586 if self.common: 587 self.collection.set_last_custom_paper_size([width, height]) 588 else: 589 self.option_list.set_custom_paper_size([width, height]) 590 591 elif tag == "margin": 592 pos, value = int(attrs['number']), float(attrs['value']) 593 if self.common: 594 self.collection.set_last_margin(pos, value) 595 else: 596 self.option_list.set_margin(pos, value) 597 elif tag == 'output': 598 if not self.common: 599 self.option_list.set_output(attrs['name']) 600 else: 601 # Tag is not report-specific, so we let the base class handle it. 602 _options.OptionParser.startElement(self, tag, attrs) 603 604 def endElement(self, tag): 605 "Overridden class that handles the end of a XML element" 606 # First we try report-specific tags 607 if tag == "last-common": 608 self.common = False 609 elif tag == 'docgen-option': 610 self.odict[self.oname] = self.an_o 611 else: 612 # Tag is not report-specific, so we let the base class handle it. 613 _options.OptionParser.endElement(self, tag) 614 615#------------------------------------------------------------------------ 616# 617# Empty class to keep the BaseDoc-targeted format happy 618# Yes, this is a hack. Find some other way to pass around documents so that 619# we don't have to handle them for reports that don't use documents (web) 620# 621#------------------------------------------------------------------------ 622class EmptyDoc: 623 def init(self): 624 pass 625 626 def set_creator(self, creator): 627 pass 628 629 def set_rtl_doc(self, value): # crock! 630 pass 631 632 def open(self, filename): 633 pass 634 635 def close(self): 636 pass 637 638#------------------------------------------------------------------------- 639# 640# Class handling options for plugins 641# 642#------------------------------------------------------------------------- 643class OptionHandler(_options.OptionHandler): 644 """ 645 Implements handling of the options for the plugins. 646 """ 647 def __init__(self, module_name, options_dict): 648 _options.OptionHandler.__init__(self, module_name, options_dict, None) 649 650 def init_subclass(self): 651 self.collection_class = OptionListCollection 652 self.list_class = OptionList 653 self.filename = REPORT_OPTIONS 654 655 def init_common(self): 656 """ 657 Specific initialization for reports. 658 """ 659 # These are needed for running reports. 660 # We will not need to save/retrieve them, just keep around. 661 self.doc = EmptyDoc() # Nasty hack. Text reports replace this 662 self.output = None 663 664 # Retrieve our options from whole collection 665 self.style_name = self.option_list_collection.default_style_name 666 self.paper_metric = self.option_list_collection.get_last_paper_metric() 667 self.paper_name = self.option_list_collection.get_last_paper_name() 668 self.orientation = self.option_list_collection.get_last_orientation() 669 self.custom_paper_size = \ 670 self.option_list_collection.get_last_custom_paper_size() 671 self.css_filename = self.option_list_collection.get_last_css_filename() 672 self.margins = self.option_list_collection.get_last_margins() 673 self.format_name = self.option_list_collection.get_last_format_name() 674 675 def set_common_options(self): 676 if self.saved_option_list.get_style_name(): 677 self.style_name = self.saved_option_list.get_style_name() 678 if self.saved_option_list.get_orientation() is not None: # 0 is legal 679 self.orientation = self.saved_option_list.get_orientation() 680 if self.saved_option_list.get_custom_paper_size(): 681 self.custom_paper_size = \ 682 self.saved_option_list.get_custom_paper_size() 683 if self.saved_option_list.get_margins(): 684 self.margins = self.saved_option_list.get_margins() 685 if self.saved_option_list.get_css_filename(): 686 self.css_filename = self.saved_option_list.get_css_filename() 687 if self.saved_option_list.get_paper_metric() is not None: # 0 is legal 688 self.paper_metric = self.saved_option_list.get_paper_metric() 689 if self.saved_option_list.get_paper_name(): 690 self.paper_name = self.saved_option_list.get_paper_name() 691 if self.saved_option_list.get_format_name(): 692 self.format_name = self.saved_option_list.get_format_name() 693 if self.saved_option_list.get_output(): 694 self.output = self.saved_option_list.get_output() 695 696 def save_options(self): 697 """ 698 Saves options to file. 699 700 """ 701 702 # First we save options from options_dict 703 for option_name, option_data in self.options_dict.items(): 704 self.saved_option_list.set_option(option_name, 705 self.options_dict[option_name]) 706 707 # Handle common options 708 self.save_common_options() 709 710 # Finally, save the whole collection into file 711 self.option_list_collection.save() 712 713 def save_common_options(self): 714 # First we save common options 715 self.saved_option_list.set_style_name(self.style_name) 716 self.saved_option_list.set_output(self.output) 717 self.saved_option_list.set_orientation(self.orientation) 718 self.saved_option_list.set_custom_paper_size(self.custom_paper_size) 719 self.saved_option_list.set_margins(self.margins) 720 self.saved_option_list.set_paper_metric(self.paper_metric) 721 self.saved_option_list.set_paper_name(self.paper_name) 722 self.saved_option_list.set_css_filename(self.css_filename) 723 self.saved_option_list.set_format_name(self.format_name) 724 self.option_list_collection.set_option_list(self.module_name, 725 self.saved_option_list) 726 727 # Then save last-common options from the current selection 728 self.option_list_collection.set_last_orientation(self.orientation) 729 self.option_list_collection.set_last_custom_paper_size( 730 self.custom_paper_size) 731 self.option_list_collection.set_last_margins(self.margins) 732 self.option_list_collection.set_last_paper_metric(self.paper_metric) 733 self.option_list_collection.set_last_paper_name(self.paper_name) 734 self.option_list_collection.set_last_css_filename(self.css_filename) 735 self.option_list_collection.set_last_format_name(self.format_name) 736 737 def get_stylesheet_savefile(self): 738 """Where to save user defined styles for this report.""" 739 # Get the first part of name, if it contains a comma: 740 # (will just be module_name, if no comma) 741 filename = "%s.xml" % self.module_name.split(",")[0] 742 return os.path.join(HOME_DIR, filename) 743 744 def get_default_stylesheet_name(self): 745 """ get the default stylesheet name """ 746 return self.style_name 747 748 def set_default_stylesheet_name(self, style_name): 749 """ set the default stylesheet name """ 750 self.style_name = style_name 751 752 def get_format_name(self): 753 """ get the format name """ 754 return self.format_name 755 756 def set_format_name(self, format_name): 757 """ set the format name """ 758 self.format_name = format_name 759 760 def get_paper_metric(self): 761 """ get the paper metric """ 762 return self.paper_metric 763 764 def set_paper_metric(self, paper_metric): 765 """ set the paper metric """ 766 self.paper_metric = paper_metric 767 768 def get_paper_name(self): 769 """ get the paper name """ 770 return self.paper_name 771 772 def set_paper_name(self, paper_name): 773 """ set the paper name """ 774 self.paper_name = paper_name 775 776 def get_paper(self): 777 """ 778 This method is for temporary storage, not for saving/restoring. 779 """ 780 return self.paper 781 782 def set_paper(self, paper): 783 """ 784 This method is for temporary storage, not for saving/restoring. 785 """ 786 self.paper = paper 787 788 def get_css_filename(self): 789 """ get the CSS filename """ 790 return self.css_filename 791 792 def set_css_filename(self, css_filename): 793 """ set the CSS filename """ 794 self.css_filename = css_filename 795 796 def get_orientation(self): 797 """ get the paper's orientation """ 798 return self.orientation 799 800 def set_orientation(self, orientation): 801 """ set the paper's orientation """ 802 self.orientation = orientation 803 804 def get_custom_paper_size(self): 805 """ get the paper's custom paper size, if any """ 806 return copy.copy(self.custom_paper_size) 807 808 def set_custom_paper_size(self, custom_paper_size): 809 """ set the paper's custom paper size """ 810 self.custom_paper_size = copy.copy(custom_paper_size) 811 812 def get_margins(self): 813 """ get the paper's margin sizes """ 814 return copy.copy(self.margins) 815 816 def set_margins(self, margins): 817 """ set the paper's margin sizes """ 818 self.margins = copy.copy(margins) 819 820#------------------------------------------------------------------------ 821# 822# Base Options class 823# 824#------------------------------------------------------------------------ 825class ReportOptions(_options.Options): 826 827 """ 828 Defines options and provides handling interface. 829 830 This is a base Options class for the reports. All reports, options 831 classes should derive from it. 832 """ 833 def __init__(self, name, dbase): 834 """ 835 Initialize the class, performing usual house-keeping tasks. 836 Subclasses MUST call this in their :meth:`__init__` method. 837 """ 838 self.name = name 839 self.options_dict = {} 840 self.options_help = {} 841 self.handler = None 842 843 def load_previous_values(self): 844 self.handler = OptionHandler(self.name, self.options_dict) 845 846 def make_default_style(self, default_style): 847 """ 848 Defines default style for this report. 849 850 This method MUST be overridden by reports that use the 851 user-adjustable paragraph styles. 852 853 .. note:: Unique names MUST be used for all style names, otherwise the 854 styles will collide when making a book with duplicate style 855 names. A rule of safety is to prepend style name with the 856 acronym based on report name. 857 858 The following acronyms are already taken: 859 860 ==== ================================ 861 Code Report 862 ==== ================================ 863 AC Ancestor Chart 864 AC2 Ancestor Chart 2 (Wall Chart) 865 AHN Ahnentafel Report 866 AR Comprehensive Ancestors report 867 CBT Custom Book Text 868 DG Descendant Graph 869 DR Descendant Report 870 DAR Detailed Ancestral Report 871 DDR Detailed Descendant Report 872 FGR Family Group Report 873 FC Fan Chart 874 FTA FTM Style Ancestral report 875 FTD FTM Style Descendant report 876 IDS Individual Complete Report 877 IDX Alphabetical Index 878 IVS Individual Summary Report 879 PLC Place Report 880 SBT Simple Book Title 881 TLG Timeline Graph 882 TOC Table Of Contents 883 ==== ================================ 884 """ 885 pass 886 887 def get_document(self): 888 """ 889 Return document instance. 890 891 .. warning:: This method MUST NOT be overridden by subclasses. 892 """ 893 return self.handler.doc 894 895 def set_document(self, val): 896 """ 897 Set document to a given instance. 898 899 .. warning:: This method MUST NOT be overridden by subclasses. 900 """ 901 self.handler.doc = val 902 903 def get_output(self): 904 """ 905 Return document output destination. 906 907 .. warning:: This method MUST NOT be overridden by subclasses. 908 """ 909 return self.handler.output 910 911 def set_output(self, val): 912 """ 913 Set output destination to a given string. 914 915 .. warning:: This method MUST NOT be overridden by subclasses. 916 """ 917 self.handler.output = val 918 919#------------------------------------------------------------------------- 920# 921# MenuReportOptions 922# 923#------------------------------------------------------------------------- 924class MenuReportOptions(MenuOptions, ReportOptions): 925 """ 926 927 The MenuReportOptions class implements the :class:`ReportOptions` 928 functionality in a generic way so that the user does not need to 929 be concerned with the actual representation of the options. 930 931 The user should inherit the MenuReportOptions class and override the 932 add_menu_options function. The user can add options to the menu and the 933 MenuReportOptions class will worry about setting up the UI. 934 935 """ 936 def __init__(self, name, dbase): 937 ReportOptions.__init__(self, name, dbase) 938 MenuOptions.__init__(self) 939 940 def load_previous_values(self): 941 ReportOptions.load_previous_values(self) 942 # Pass the loaded values to the menu options so they will be displayed 943 # properly. 944 for optname in self.options_dict: 945 menu_option = self.menu.get_option_by_name(optname) 946 if menu_option: 947 menu_option.set_value(self.options_dict[optname]) 948 949 def get_subject(self): 950 """ 951 Return a string that describes the subject of the report. 952 953 This method MUST be overridden by subclasses. 954 """ 955 LOG.warning("get_subject not implemented for %s" % self.name) 956 return "" 957 958#------------------------------------------------------------------------- 959# 960# DocOptionHandler class 961# 962#------------------------------------------------------------------------- 963class DocOptionHandler(_options.OptionHandler): 964 """ 965 Implements handling of the docgen options for the plugins. 966 """ 967 968 def __init__(self, module_name, options_dict): 969 _options.OptionHandler.__init__(self, module_name, options_dict) 970 971 def init_subclass(self): 972 self.collection_class = DocOptionListCollection 973 self.list_class = OptionList 974 self.filename = REPORT_OPTIONS 975 976 def set_options(self): 977 """ 978 Set options to be used in this plugin according to the passed 979 options dictionary. 980 981 Dictionary values are all strings, since they were read from XML. 982 Here we need to convert them to the needed types. We use default 983 values to determine the type. 984 """ 985 # First we set options_dict values based on the saved options 986 options = self.saved_option_list.get_options() 987 docgen_names = self.option_list_collection.docgen_names 988 for option_name, option_data in options.items(): 989 if (option_name in self.options_dict 990 and isinstance(option_data, list) 991 and option_data 992 and option_data[0] in docgen_names): 993 try: 994 converter = get_type_converter( 995 self.options_dict[option_name]) 996 self.options_dict[option_name] = converter(option_data[1]) 997 except (TypeError, ValueError): 998 pass 999 1000#------------------------------------------------------------------------ 1001# 1002# DocOptions class 1003# 1004#------------------------------------------------------------------------ 1005class DocOptions(MenuOptions): 1006 """ 1007 Defines options and provides handling interface. 1008 """ 1009 1010 def __init__(self, name): 1011 """ 1012 Initialize the class, performing usual house-keeping tasks. 1013 Subclasses MUST call this in their :meth:`__init__` method. 1014 """ 1015 self.name = name 1016 MenuOptions.__init__(self) 1017 1018 def load_previous_values(self): 1019 self.handler = DocOptionHandler(self.name, self.options_dict) 1020 1021#------------------------------------------------------------------------- 1022# 1023# DocOptionListCollection class 1024# 1025#------------------------------------------------------------------------- 1026class DocOptionListCollection(_options.OptionListCollection): 1027 """ 1028 Implements a collection of option lists. 1029 """ 1030 1031 def __init__(self, filename): 1032 _options.OptionListCollection.__init__(self, filename) 1033 1034 def init_common(self): 1035 pass 1036 1037 def parse(self): 1038 """ 1039 Loads the :class:`OptionList` from the associated file, if it exists. 1040 """ 1041 try: 1042 if os.path.isfile(self.filename): 1043 parser = make_parser() 1044 parser.setContentHandler(DocOptionParser(self)) 1045 with open(self.filename, encoding="utf-8") as the_file: 1046 parser.parse(the_file) 1047 except (IOError, OSError, SAXParseException): 1048 pass 1049 1050#------------------------------------------------------------------------ 1051# 1052# DocOptionParser class 1053# 1054#------------------------------------------------------------------------ 1055class DocOptionParser(_options.OptionParser): 1056 """ 1057 SAX parsing class for the DocOptionListCollection XML file. 1058 """ 1059 1060 def __init__(self, collection): 1061 """ 1062 Create a DocOptionParser class that populates the passed collection. 1063 1064 collection: DocOptionListCollection to be loaded from the file. 1065 """ 1066 _options.OptionParser.__init__(self, collection) 1067 self.list_class = OptionList 1068 1069 def startElement(self, tag, attrs): 1070 "Overridden class that handles the start of a XML element" 1071 if tag == 'docgen-option': 1072 self.oname = attrs['name'] 1073 self.an_o = [attrs['docgen'], attrs['value']] 1074 else: 1075 _options.OptionParser.startElement(self, tag, attrs) 1076 1077 def endElement(self, tag): 1078 "Overridden class that handles the end of a XML element" 1079 if tag == 'docgen-option': 1080 self.odict[self.oname] = self.an_o 1081 else: 1082 _options.OptionParser.endElement(self, tag) 1083