diff --git a/gramps/cli/plug/__init__.py b/gramps/cli/plug/__init__.py index 1478924b1..00d115a6f 100644 --- a/gramps/cli/plug/__init__.py +++ b/gramps/cli/plug/__init__.py @@ -46,7 +46,8 @@ log = logging.getLogger(".") #------------------------------------------------------------------------- from gramps.gen.plug import BasePluginManager from gramps.gen.plug.docgen import (StyleSheet, StyleSheetList, PaperStyle, - PAPER_PORTRAIT, PAPER_LANDSCAPE, graphdoc) + PAPER_PORTRAIT, PAPER_LANDSCAPE, graphdoc, + treedoc) from gramps.gen.plug.menu import (FamilyOption, PersonOption, NoteOption, MediaOption, PersonListOption, NumberOption, BooleanOption, DestinationOption, StringOption, @@ -54,8 +55,8 @@ from gramps.gen.plug.menu import (FamilyOption, PersonOption, NoteOption, from gramps.gen.display.name import displayer as name_displayer from gramps.gen.errors import ReportError, FilterError from gramps.gen.plug.report import (CATEGORY_TEXT, CATEGORY_DRAW, CATEGORY_BOOK, - CATEGORY_GRAPHVIZ, CATEGORY_CODE, - ReportOptions, append_styles) + CATEGORY_GRAPHVIZ, CATEGORY_TREE, + CATEGORY_CODE, ReportOptions, append_styles) from gramps.gen.plug.report._paper import paper_sizes from gramps.gen.const import USER_HOME from gramps.gen.dbstate import DbState @@ -233,6 +234,15 @@ class CommandLineReport(object): if name not in self.option_class.options_dict: self.option_class.options_dict[name] = \ menu.get_option_by_name(name).get_value() + if category == CATEGORY_TREE: + # Need to include Genealogy Tree options + self.__toptions = treedoc.TreeOptions() + menu = self.option_class.menu + self.__toptions.add_menu_options(menu) + for name in menu.get_all_option_names(): + if name not in self.option_class.options_dict: + self.option_class.options_dict[ + name] = menu.get_option_by_name(name).get_value() self.option_class.load_previous_values() _validate_options(self.option_class, database) self.show = options_str_dict.pop('show', None) @@ -301,6 +311,10 @@ class CommandLineReport(object): for graph_format in graphdoc.FORMATS: self.options_help['off'][2].append( graph_format["type"] + "\t" + graph_format["descr"] ) + elif self.category == CATEGORY_TREE: + for tree_format in treedoc.FORMATS: + self.options_help['off'][2].append( + tree_format["type"] + "\t" + tree_format["descr"]) else: self.options_help['off'][2] = "NA" @@ -478,6 +492,15 @@ class CommandLineReport(object): # Pick the first one as the default. self.format = graphdoc.FORMATS[0]["class"] _chosen_format = graphdoc.FORMATS[0]["type"] + elif self.category == CATEGORY_TREE: + for tree_format in treedoc.FORMATS: + if tree_format['type'] == self.options_dict['off']: + if not self.format: # choose the first one, not the last + self.format = tree_format["class"] + if self.format is None: + # Pick the first one as the default. + self.format = tree_format.FORMATS[0]["class"] + _chosen_format = tree_format.FORMATS[0]["type"] else: self.format = None if _chosen_format and _format_str: @@ -640,7 +663,7 @@ def cl_report(database, name, category, report_class, options_class, clr.selected_style, PaperStyle(clr.paper,clr.orien,clr.marginl, clr.marginr,clr.margint,clr.marginb)) - elif category == CATEGORY_GRAPHVIZ: + elif category in [CATEGORY_GRAPHVIZ, CATEGORY_TREE]: clr.option_class.handler.doc = clr.format( clr.option_class, PaperStyle(clr.paper,clr.orien,clr.marginl, diff --git a/gramps/gen/plug/__init__.py b/gramps/gen/plug/__init__.py index 044fd3878..a82090387 100644 --- a/gramps/gen/plug/__init__.py +++ b/gramps/gen/plug/__init__.py @@ -27,7 +27,7 @@ The "plug" package for handling plugins in Gramps. from ._plugin import Plugin from ._pluginreg import (PluginData, PluginRegister, REPORT, TOOL, CATEGORY_TEXT, CATEGORY_DRAW, CATEGORY_CODE, - CATEGORY_WEB, CATEGORY_BOOK, CATEGORY_GRAPHVIZ, + CATEGORY_WEB, CATEGORY_BOOK, CATEGORY_GRAPHVIZ, CATEGORY_TREE, TOOL_DEBUG, TOOL_ANAL, TOOL_DBPROC, TOOL_DBFIX, TOOL_REVCTL, TOOL_UTILS, CATEGORY_QR_MISC, CATEGORY_QR_PERSON, CATEGORY_QR_FAMILY, CATEGORY_QR_EVENT, CATEGORY_QR_SOURCE, @@ -49,7 +49,7 @@ __all__ = [ "docbackend", "docgen", "menu", Plugin, PluginData, PluginRegister, BasePluginManager, ImportPlugin, ExportPlugin, DocGenPlugin, REPORT, TOOL, CATEGORY_TEXT, CATEGORY_DRAW, CATEGORY_CODE, - CATEGORY_WEB, CATEGORY_BOOK, CATEGORY_GRAPHVIZ, + CATEGORY_WEB, CATEGORY_BOOK, CATEGORY_GRAPHVIZ, CATEGORY_TREE, TOOL_DEBUG, TOOL_ANAL, TOOL_DBPROC, TOOL_DBFIX, TOOL_REVCTL, TOOL_UTILS, CATEGORY_QR_MISC, CATEGORY_QR_PERSON, CATEGORY_QR_FAMILY, CATEGORY_QR_EVENT, CATEGORY_QR_SOURCE, diff --git a/gramps/gen/plug/_pluginreg.py b/gramps/gen/plug/_pluginreg.py index 04167cafc..5fdeb2b48 100644 --- a/gramps/gen/plug/_pluginreg.py +++ b/gramps/gen/plug/_pluginreg.py @@ -94,8 +94,10 @@ CATEGORY_CODE = 2 CATEGORY_WEB = 3 CATEGORY_BOOK = 4 CATEGORY_GRAPHVIZ = 5 +CATEGORY_TREE = 6 REPORT_CAT = [ CATEGORY_TEXT, CATEGORY_DRAW, CATEGORY_CODE, - CATEGORY_WEB, CATEGORY_BOOK, CATEGORY_GRAPHVIZ] + CATEGORY_WEB, CATEGORY_BOOK, CATEGORY_GRAPHVIZ, + CATEGORY_TREE] #possible tool categories TOOL_DEBUG = -1 TOOL_ANAL = 0 @@ -1009,6 +1011,7 @@ def make_environment(**kwargs): 'CATEGORY_WEB': CATEGORY_WEB, 'CATEGORY_BOOK': CATEGORY_BOOK, 'CATEGORY_GRAPHVIZ': CATEGORY_GRAPHVIZ, + 'CATEGORY_TREE': CATEGORY_TREE, 'TOOL_DEBUG': TOOL_DEBUG, 'TOOL_ANAL': TOOL_ANAL, 'TOOL_DBPROC': TOOL_DBPROC, diff --git a/gramps/gen/plug/docgen/__init__.py b/gramps/gen/plug/docgen/__init__.py index d2dc8452c..a6fd37ca1 100644 --- a/gramps/gen/plug/docgen/__init__.py +++ b/gramps/gen/plug/docgen/__init__.py @@ -37,3 +37,4 @@ from .textdoc import TextDoc, IndexMark,INDEX_TYPE_ALP, INDEX_TYPE_TOC,\ URL_PATTERN, LOCAL_HYPERLINK, LOCAL_TARGET from .drawdoc import DrawDoc from .graphdoc import GVDoc +from .treedoc import TreeDoc diff --git a/gramps/gen/plug/docgen/treedoc.py b/gramps/gen/plug/docgen/treedoc.py new file mode 100644 index 000000000..d9b503750 --- /dev/null +++ b/gramps/gen/plug/docgen/treedoc.py @@ -0,0 +1,632 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2017-2018 Nick Hall +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +""" LaTeX Genealogy Tree adapter for Trees """ +#------------------------------------------------------------------------- +# +# Standard Python modules +# +#------------------------------------------------------------------------- +from abc import ABCMeta, abstractmethod +import os +import shutil +from io import StringIO +import tempfile +import logging + +#------------------------------------------------------------------------- +# +# Gramps modules +# +#------------------------------------------------------------------------- +from ...utils.file import search_for +from ...lib import Person, EventType, EventRoleType, Date +from ...display.place import displayer as _pd +from ...utils.file import media_path_full +from . import BaseDoc, PAPER_PORTRAIT +from ..menu import NumberOption, TextOption, EnumeratedListOption +from ...constfunc import win +from ...config import config +from ...const import GRAMPS_LOCALE as glocale +_ = glocale.translation.gettext + +#------------------------------------------------------------------------- +# +# set up logging +# +#------------------------------------------------------------------------- +LOG = logging.getLogger(".treedoc") + +#------------------------------------------------------------------------- +# +# Private Constants +# +#------------------------------------------------------------------------- +_DETAIL = [{'name': _("Full"), 'value': "full"}, + {'name': _("Medium"), 'value': "medium"}, + {'name': _("Short"), 'value': "short"}] + +_MARRIAGE = [{'name': _("Default"), 'value': ""}, + {'name': _("Above"), 'value': "marriage above"}, + {'name': _("Below"), 'value': "marriage below"}, + {'name': _("Not shown"), 'value': "no marriage"}] + +_COLOR = [{'name': _("None"), 'value': "none"}, + {'name': _("Default"), 'value': "default"}, + {'name': _("Preferences"), 'value': "preferences"}] + +_TIMEFLOW = [{'name': _("Down (↓)"), 'value': ""}, + {'name': _("Up (↑)"), 'value': "up"}, + {'name': _("Right (→)"), 'value': "right"}, + {'name': _("Left (←)"), 'value': "left"}] + +_EDGES = [{'name': _("Perpendicular"), 'value': ""}, + {'name': _("Rounded"), 'value': "rounded", }, + {'name': _("Swing"), 'value': "swing", }, + {'name': _("Mesh"), 'value': 'mesh'}] + +_NOTELOC = [{'name': _("Top"), 'value': "t"}, + {'name': _("Bottom"), 'value': "b"}] + +_NOTESIZE = [{'name': _("Tiny"), 'value': "tiny"}, + {'name': _("Script"), 'value': "scriptsize"}, + {'name': _("Footnote"), 'value': "footnotesize"}, + {'name': _("Small"), 'value': "small"}, + {'name': _("Normal"), 'value': "normalsize"}, + {'name': _("Large"), 'value': "large"}, + {'name': _("Very large"), 'value': "Large"}, + {'name': _("Extra large"), 'value': "LARGE"}, + {'name': _("Huge"), 'value': "huge"}, + {'name': _("Extra huge"), 'value': "Huge"}] + +if win(): + _LATEX_FOUND = search_for("lualatex.exe") +else: + _LATEX_FOUND = search_for("lualatex") + + +#------------------------------------------------------------------------------ +# +# TreeOptions +# +#------------------------------------------------------------------------------ +class TreeOptions: + """ + Defines all of the controls necessary + to configure the genealogy tree reports. + """ + def add_menu_options(self, menu): + """ + Add all graph related options to the menu. + + :param menu: The menu the options should be added to. + :type menu: :class:`.Menu` + :return: nothing + """ + ################################ + category = _("Node Options") + ################################ + + detail = EnumeratedListOption(_("Node detail"), "full") + for item in _DETAIL: + detail.add_item(item["value"], item["name"]) + detail.set_help(_("Detail of information to be shown in a node.")) + menu.add_option(category, "detail", detail) + + marriage = EnumeratedListOption(_("Marriage"), "") + for item in _MARRIAGE: + marriage.add_item(item["value"], item["name"]) + marriage.set_help(_("Position of marriage information.")) + menu.add_option(category, "marriage", marriage) + + nodesize = NumberOption(_("Node size"), 25, 5, 100, 5) + nodesize.set_help(_("One dimension of a node, in mm. If the timeflow " + "is up or down then this is the width, otherwise " + "it is the height.")) + menu.add_option(category, "nodesize", nodesize) + + levelsize = NumberOption(_("Level size"), 35, 5, 100, 5) + levelsize.set_help(_("One dimension of a node, in mm. If the timeflow " + "is up or down then this is the height, otherwise " + "it is the width.")) + menu.add_option(category, "levelsize", levelsize) + + nodecolor = EnumeratedListOption(_("Color"), "none") + for item in _COLOR: + nodecolor.add_item(item["value"], item["name"]) + nodecolor.set_help(_("Node color.")) + menu.add_option(category, "nodecolor", nodecolor) + + ################################ + category = _("Tree Options") + ################################ + + timeflow = EnumeratedListOption(_("Timeflow"), "") + for item in _TIMEFLOW: + timeflow.add_item(item["value"], item["name"]) + timeflow.set_help(_("Direction that the graph will grow over time.")) + menu.add_option(category, "timeflow", timeflow) + + edges = EnumeratedListOption(_("Edge style"), "") + for item in _EDGES: + edges.add_item(item["value"], item["name"]) + edges.set_help(_("Style of the edges between nodes.")) + menu.add_option(category, "edges", edges) + + leveldist = NumberOption(_("Level distance"), 5, 1, 20, 1) + leveldist.set_help(_("The minimum amount of free space, in mm, " + "between levels. For vertical graphs, this " + "corresponds to spacing between rows. For " + "horizontal graphs, this corresponds to spacing " + "between columns.")) + menu.add_option(category, "leveldist", leveldist) + + ################################ + category = _("Note") + ################################ + + note = TextOption(_("Note to add to the tree"), [""]) + note.set_help(_("This text will be added to the tree.")) + menu.add_option(category, "note", note) + + noteloc = EnumeratedListOption(_("Note location"), 't') + for item in _NOTELOC: + noteloc.add_item(item["value"], item["name"]) + noteloc.set_help(_("Whether note will appear on top " + "or bottom of the page.")) + menu.add_option(category, "noteloc", noteloc) + + notesize = EnumeratedListOption(_("Note size"), 'normalsize') + for item in _NOTESIZE: + notesize.add_item(item["value"], item["name"]) + notesize.set_help(_("The size of note text.")) + menu.add_option(category, "notesize", notesize) + + +#------------------------------------------------------------------------------ +# +# TreeDoc +# +#------------------------------------------------------------------------------ +class TreeDoc(metaclass=ABCMeta): + """ + Abstract Interface for genealogy tree document generators. Output formats + for genealogy tree reports must implement this interface to be used by the + report system. + """ + @abstractmethod + def start_tree(self, option_list): + """ + Write the start of a tree. + """ + + @abstractmethod + def end_tree(self): + """ + Write the end of a tree. + """ + + @abstractmethod + def start_subgraph(self, level, subgraph_type, family, option_list=None): + """ + Write the start of a subgraph. + """ + + @abstractmethod + def end_subgraph(self, level): + """ + Write the end of a subgraph. + """ + + @abstractmethod + def write_node(self, db, level, node_type, person, marriage_flag, + option_list=None): + """ + Write the contents of a node. + """ + + +#------------------------------------------------------------------------------ +# +# TreeDocBase +# +#------------------------------------------------------------------------------ +class TreeDocBase(BaseDoc, TreeDoc): + """ + Base document generator for all Graphviz document generators. Classes that + inherit from this class will only need to implement the close function. + The close function will generate the actual file of the appropriate type. + """ + def __init__(self, options, paper_style): + BaseDoc.__init__(self, None, paper_style) + + self._filename = None + self._tex = StringIO() + self._paper = paper_style + + get_option = options.menu.get_option_by_name + + self.detail = get_option('detail').get_value() + self.marriage = get_option('marriage').get_value() + self.nodesize = get_option('nodesize').get_value() + self.levelsize = get_option('levelsize').get_value() + self.nodecolor = get_option('nodecolor').get_value() + + self.timeflow = get_option('timeflow').get_value() + self.edges = get_option('edges').get_value() + self.leveldist = get_option('leveldist').get_value() + + self.note = get_option('note').get_value() + self.noteloc = get_option('noteloc').get_value() + self.notesize = get_option('notesize').get_value() + + def write_start(self): + """ + Write the start of the document. + """ + paper_size = self._paper.get_size() + name = paper_size.get_name().lower() + if name == 'custom size': + width = str(paper_size.get_width()) + height = str(paper_size.get_width()) + paper = 'papersize={%scm,%scm}' % (width, height) + elif name in ('a', 'b', 'c', 'd', 'e'): + paper = 'ansi' + name + 'paper' + else: + paper = name + 'paper' + + if self._paper.get_orientation() == PAPER_PORTRAIT: + orientation = 'portrait' + else: + orientation = 'landscape' + + lmargin = self._paper.get_left_margin() + rmargin = self._paper.get_right_margin() + tmargin = self._paper.get_top_margin() + bmargin = self._paper.get_bottom_margin() + if lmargin == rmargin == tmargin == bmargin: + margin = 'margin=%scm'% lmargin + else: + if lmargin == rmargin: + margin = 'hmargin=%scm' % lmargin + else: + margin = 'hmargin={%scm,%scm}' % (lmargin, rmargin) + if tmargin == bmargin: + margin += ',vmargin=%scm' % tmargin + else: + margin += ',vmargin={%scm,%scm}' % (tmargin, bmargin) + + self.write(0, '\\documentclass[%s]{article}\n' % orientation) + + self.write(0, '\\IfFileExists{libertine.sty}{\n') + self.write(0, ' \\usepackage{libertine}\n') + self.write(0, '}{}\n') + + self.write(0, '\\usepackage[%s,%s]{geometry}\n' % (paper, margin)) + self.write(0, '\\usepackage[all]{genealogytree}\n') + self.write(0, '\\usepackage{color}\n') + self.write(0, '\\begin{document}\n') + + if self.nodecolor == 'preferences': + male_bg = config.get('preferences.color-gender-male-death')[1:] + female_bg = config.get('preferences.color-gender-female-death')[1:] + neuter_bg = config.get('preferences.color-gender-unknown-death')[1:] + self.write(0, '\\definecolor{male-bg}{HTML}{%s}\n' % male_bg) + self.write(0, '\\definecolor{female-bg}{HTML}{%s}\n' % female_bg) + self.write(0, '\\definecolor{neuter-bg}{HTML}{%s}\n' % neuter_bg) + + if ''.join(self.note) != '' and self.noteloc == 't': + for line in self.note: + self.write(0, '{\\%s %s}\\par\n' % (self.notesize, line)) + self.write(0, '\\bigskip\n') + + self.write(0, '\\begin{tikzpicture}\n') + + def start_tree(self, option_list): + self.write(0, '\\genealogytree[\n') + self.write(0, 'processing=database,\n') + if self.marriage: + info = self.detail + ' ' + self.marriage + else: + info = self.detail + self.write(0, 'database format=%s,\n' % info) + if self.timeflow: + self.write(0, 'timeflow=%s,\n' % self.timeflow) + if self.edges: + self.write(0, 'edges=%s,\n' % self.edges) + if self.leveldist != 5: + self.write(0, 'level distance=%smm,\n' % self.leveldist) + if self.nodesize != 25: + self.write(0, 'node size=%smm,\n' % self.nodesize) + if self.levelsize != 35: + self.write(0, 'level size=%smm,\n' % self.levelsize) + if self.nodecolor == 'none': + self.write(0, 'tcbset={male/.style={},\n') + self.write(0, ' female/.style={},\n') + self.write(0, ' neuter/.style={}},\n') + if self.nodecolor == 'preferences': + self.write(0, 'tcbset={male/.style={colback=male-bg},\n') + self.write(0, ' female/.style={colback=female-bg},\n') + self.write(0, ' neuter/.style={colback=neuter-bg}},\n') + + for option in option_list: + self.write(0, '%s,\n' % option) + + self.write(0, ']{\n') + + def end_tree(self): + self.write(0, '}\n') + + def write_end(self): + """ + Write the end of the document. + """ + self.write(0, '\\end{tikzpicture}\n') + + if ''.join(self.note) != '' and self.noteloc == 'b': + self.write(0, '\\bigskip\n') + for line in self.note: + self.write(0, '\\par{\\%s %s}\n' % (self.notesize, line)) + + self.write(0, '\\end{document}\n') + + def start_subgraph(self, level, subgraph_type, family, option_list=None): + options = ['id=%s' % family.gramps_id] + if option_list: + options.extend(option_list) + if subgraph_type == 'sandclock': + self.write(level, 'sandclock{\n') + else: + self.write(level, '%s[%s]{\n' % (subgraph_type, ','.join(options))) + + def end_subgraph(self, level): + self.write(level, '}\n') + + def write_node(self, db, level, node_type, person, marriage_flag, + option_list=None): + options = ['id=%s' % person.gramps_id] + if option_list: + options.extend(option_list) + self.write(level, '%s[%s]{\n' % (node_type, ','.join(options))) + if person.gender == Person.MALE: + self.write(level+1, 'male,\n') + elif person.gender == Person.FEMALE: + self.write(level+1, 'female,\n') + elif person.gender == Person.UNKNOWN: + self.write(level+1, 'neuter,\n') + name = person.get_primary_name() + nick = name.get_nick_name() + surn = name.get_surname() + name_parts = [self.format_given_names(name), + '\\nick{{{}}}'.format(nick) if nick else '', + '\\surn{{{}}}'.format(surn) if surn else ''] + self.write(level+1, 'name = {{{}}},\n'.format( + ' '.join([e for e in name_parts if e]))) + for eventref in person.get_event_ref_list(): + if eventref.role == EventRoleType.PRIMARY: + event = db.get_event_from_handle(eventref.ref) + self.write_event(db, level+1, event) + if marriage_flag: + for handle in person.get_family_handle_list(): + family = db.get_family_from_handle(handle) + for eventref in family.get_event_ref_list(): + if eventref.role == EventRoleType.FAMILY: + event = db.get_event_from_handle(eventref.ref) + self.write_event(db, level+1, event) + for attr in person.get_attribute_list(): + if str(attr.get_type()) == 'Occupation': + self.write(level+1, 'profession = {%s},\n' % attr.get_value()) + if str(attr.get_type()) == 'Comment': + self.write(level+1, 'comment = {%s},\n' % attr.get_value()) + for mediaref in person.get_media_list(): + media = db.get_object_from_handle(mediaref.ref) + path = media_path_full(db, media.get_path()) + if os.path.isfile(path): + self.write(level+1, 'image = {%s},\n' % path) + break # first image only + self.write(level, '}\n') + + def write_event(self, db, level, event): + """ + Write an event. + """ + modifier = None + if event.type == EventType.BIRTH: + event_type = 'birth' + if 'died' in event.description.lower(): + modifier = 'died' + if 'stillborn' in event.description.lower(): + modifier = 'stillborn' + # modifier = 'out of wedlock' + elif event.type == EventType.BAPTISM: + event_type = 'baptism' + elif event.type == EventType.ENGAGEMENT: + event_type = 'engagement' + elif event.type == EventType.MARRIAGE: + event_type = 'marriage' + elif event.type == EventType.DIVORCE: + event_type = 'divorce' + elif event.type == EventType.DEATH: + event_type = 'death' + elif event.type == EventType.BURIAL: + event_type = 'burial' + if 'killed' in event.description.lower(): + modifier = 'killed' + elif event.type == EventType.CREMATION: + event_type = 'burial' + modifier = 'cremated' + else: + return + + date = event.get_date_object() + + if date.get_calendar() == Date.CAL_GREGORIAN: + calendar = 'AD' # GR + elif date.get_calendar() == Date.CAL_JULIAN: + calendar = 'JU' + else: + calendar = '' + + if date.get_modifier() == Date.MOD_ABOUT: + calendar = 'ca' + calendar + + date_str = self.format_iso(date.get_ymd(), calendar) + if date.get_modifier() == Date.MOD_BEFORE: + date_str = '/' + date_str + elif date.get_modifier() == Date.MOD_AFTER: + date_str = date_str + '/' + elif date.is_compound(): + stop_date = self.format_iso(date.get_stop_ymd(), calendar) + date_str = date_str + '/' + stop_date + + place = _pd.display_event(db, event) + + if modifier: + event_type += '+' + self.write(level, '%s = {%s}{%s}{%s},\n' % + (event_type, date_str, place, modifier)) + elif place == '': + event_type += '-' + self.write(level, '%s = {%s},\n' % (event_type, date_str)) + else: + self.write(level, '%s = {%s}{%s},\n' % + (event_type, date_str, place)) + + def format_given_names(self, name): + """ + Format given names. + """ + first = name.get_first_name() + call = name.get_call_name() + if call: + if call in first: + where = first.index(call) + return '{before}\\pref{{{call}}}{after}'.format( + before=first[:where], + call=call, + after=first[where+len(call):]) + else: + # ignore erroneous call name + return first + else: + return first + + def format_iso(self, date_tuple, calendar): + """ + Format an iso date. + """ + year, month, day = date_tuple + if year == 0: + iso_date = '' + elif month == 0: + iso_date = str(year) + elif day == 0: + iso_date = '%s-%s' % (year, month) + else: + iso_date = '%s-%s-%s' % (year, month, day) + if calendar and calendar != 'AD': + iso_date = '(%s)%s' % (calendar, iso_date) + return iso_date + + def write(self, level, text): + """ + Write indented text. + """ + self._tex.write(' '*level + text) + + def open(self, filename): + """ Implement TreeDocBase.open() """ + self._filename = os.path.normpath(os.path.abspath(filename)) + self.write_start() + + def close(self): + """ + This isn't useful by itself. Other classes need to override this and + actually generate a file. + """ + self.write_end() + + +#------------------------------------------------------------------------------ +# +# TreeTexDoc +# +#------------------------------------------------------------------------------ +class TreeTexDoc(TreeDocBase): + """ + TreeTexDoc implementation that generates a .tex file. + """ + + def close(self): + """ Implements TreeDocBase.close() """ + TreeDocBase.close(self) + + # Make sure the extension is correct + if self._filename[-4:] != ".tex": + self._filename += ".tex" + + with open(self._filename, "w") as texfile: + texfile.write(self._tex.getvalue()) + + +#------------------------------------------------------------------------------ +# +# TreePdfDoc +# +#------------------------------------------------------------------------------ +class TreePdfDoc(TreeDocBase): + """ + TreePdfDoc implementation that generates a .pdf file. + """ + + def close(self): + """ Implements TreeDocBase.close() """ + TreeDocBase.close(self) + + # Make sure the extension is correct + if self._filename[-4:] != ".pdf": + self._filename += ".pdf" + + with tempfile.TemporaryDirectory() as tmpdir: + with open(os.path.join(tmpdir, 'temp.tex'), "w") as texfile: + texfile.write(self._tex.getvalue()) + os.system('lualatex -output-directory %s temp.tex >/dev/null' + % tmpdir) + shutil.copy(os.path.join(tmpdir, 'temp.pdf'), self._filename) + + +#------------------------------------------------------------------------------ +# +# Various Genealogy Tree formats. +# +#------------------------------------------------------------------------------ +FORMATS = [] + +if _LATEX_FOUND: + FORMATS += [{'type' : "pdf", + 'ext' : "pdf", + 'descr': _("PDF"), + 'mime' : "application/pdf", + 'class': TreePdfDoc}] + +FORMATS += [{'type' : "tex", + 'ext' : "tex", + 'descr': _("LaTeX File"), + 'mime' : "application/x-latex", + 'class': TreeTexDoc}] diff --git a/gramps/gen/plug/report/_constants.py b/gramps/gen/plug/report/_constants.py index 7a7016463..97ca81650 100644 --- a/gramps/gen/plug/report/_constants.py +++ b/gramps/gen/plug/report/_constants.py @@ -39,7 +39,7 @@ import os # Report categories from .. import (CATEGORY_TEXT, CATEGORY_DRAW, CATEGORY_CODE, CATEGORY_WEB, - CATEGORY_BOOK, CATEGORY_GRAPHVIZ) + CATEGORY_BOOK, CATEGORY_GRAPHVIZ, CATEGORY_TREE) standalone_categories = { CATEGORY_TEXT : ("RepText", _("Text Reports")), @@ -48,6 +48,7 @@ standalone_categories = { CATEGORY_WEB : ("RepWeb", _("Web Pages")), CATEGORY_BOOK : ("RepBook", _("Books")), CATEGORY_GRAPHVIZ : ("Graphs", _("Graphs")), + CATEGORY_TREE : ("Trees", _("Trees")), } book_categories = { CATEGORY_TEXT : _("Text"), diff --git a/gramps/gui/plug/report/_graphreportdialog.py b/gramps/gui/plug/report/_graphreportdialog.py new file mode 100644 index 000000000..8a439fe00 --- /dev/null +++ b/gramps/gui/plug/report/_graphreportdialog.py @@ -0,0 +1,288 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2007-2008 Brian G. Matherly +# Copyright (C) 2007-2009 Stephane Charette +# Copyright (C) 2009 Gary Burton +# Contribution 2009 by Bob Ham +# Copyright (C) 2010 Jakim Friant +# Copyright (C) 2012-2013 Paul Franklin +# Copyright (C) 2017 Nick Hall +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +"""base class for generating dialogs for graph-based reports """ + +#------------------------------------------------------------------------ +# +# python modules +# +#------------------------------------------------------------------------ +from abc import ABCMeta, abstractmethod +import os +from gramps.gen.const import GRAMPS_LOCALE as glocale +_ = glocale.translation.gettext + +#------------------------------------------------------------------------------- +# +# GTK+ modules +# +#------------------------------------------------------------------------------- +from gi.repository import Gtk +from gi.repository import GObject + +#------------------------------------------------------------------------------- +# +# GRAMPS modules +# +#------------------------------------------------------------------------------- +from gramps.gen.config import config +from gramps.gen.plug.report import CATEGORY_GRAPHVIZ +from ._reportdialog import ReportDialog +from ._papermenu import PaperFrame +import gramps.gen.plug.docgen.graphdoc as graphdoc +from gramps.gen.plug.menu import Menu + +#------------------------------------------------------------------------------- +# +# BaseFormatComboBox +# +#------------------------------------------------------------------------------- +class BaseFormatComboBox(Gtk.ComboBox): + """ + Combo box base class for graph-based report format choices. + """ + FORMATS=[] + + def set(self, active=None): + self.store = Gtk.ListStore(GObject.TYPE_STRING) + self.set_model(self.store) + cell = Gtk.CellRendererText() + self.pack_start(cell, True) + self.add_attribute(cell, 'text', 0) + + index = 0 + active_index = 0 + for item in self.FORMATS: + name = item["descr"] + self.store.append(row=[name]) + if item['type'] == active: + active_index = index + index += 1 + self.set_active(active_index) + + def get_label(self): + return self.FORMATS[self.get_active()]["descr"] + + def get_reference(self): + return self.FORMATS[self.get_active()]["class"] + + def get_paper(self): + return 1 + + def get_styles(self): + return 0 + + def get_ext(self): + return '.%s' % self.FORMATS[self.get_active()]['ext'] + + def get_oformat_str(self): # the report's output-format type + return self.FORMATS[self.get_active()]["type"] + + def is_file_output(self): + return True + + def get_clname(self): + return self.FORMATS[self.get_active()]["type"] + +#----------------------------------------------------------------------- +# +# GraphReportDialog +# +#----------------------------------------------------------------------- +class GraphReportDialog(ReportDialog, metaclass=ABCMeta): + """A base class of ReportDialog customized for graph-based reports.""" + def __init__(self, dbstate, uistate, opt, name, translated_name): + """Initialize a dialog to request that the user select options + for a graphviz report. See the ReportDialog class for + more information.""" + self.category = self.get_category() + self._goptions = self.get_options() + self.dbname = dbstate.db.get_dbname() + ReportDialog.__init__(self, dbstate, uistate, opt, + name, translated_name) + + def init_options(self, option_class): + try: + if issubclass(option_class, object): # Old-style class + self.options = option_class(self.raw_name, + self.dbstate.get_database()) + except TypeError: + self.options = option_class + + menu = Menu() + self._goptions.add_menu_options(menu) + + for category in menu.get_categories(): + for name in menu.get_option_names(category): + option = menu.get_option(category, name) + self.options.add_menu_option(category, name, option) + + self.options.load_previous_values() + + def init_interface(self): + ReportDialog.init_interface(self) + self.doc_type_changed(self.format_menu) + self.notebook.set_current_page(1) # don't start on "Paper Options" + + def setup_format_frame(self): + """Set up the format frame of the dialog.""" + self.make_doc_menu() + self.format_menu.set(self.options.handler.get_format_name()) + self.format_menu.connect('changed', self.doc_type_changed) + label = Gtk.Label(label="%s:" % _("Output Format")) + label.set_halign(Gtk.Align.START) + self.grid.attach(label, 1, self.row, 1, 1) + self.format_menu.set_hexpand(True) + self.grid.attach(self.format_menu, 2, self.row, 2, 1) + self.row += 1 + + self.open_with_app = Gtk.CheckButton(_("Open with default viewer")) + self.open_with_app.set_active( + config.get('interface.open-with-default-viewer')) + self.grid.attach(self.open_with_app, 2, self.row, 2, 1) + self.row += 1 + + ext = self.format_menu.get_ext() + if ext is None: + ext = "" + else: + spath = self.get_default_directory() + if self.options.get_output(): + base = os.path.basename(self.options.get_output()) + else: + if self.dbname is None: + default_name = self.raw_name + else: + default_name = self.dbname + "_" + self.raw_name + base = "%s%s" % (default_name, ext) # "ext" already has a dot + spath = os.path.normpath(os.path.join(spath, base)) + self.target_fileentry.set_filename(spath) + + def setup_report_options_frame(self): + self.paper_label = Gtk.Label(label='%s' % _("Paper Options")) + self.paper_label.set_use_markup(True) + handler = self.options.handler + self.paper_frame = PaperFrame( + handler.get_paper_metric(), + handler.get_paper_name(), + handler.get_orientation(), + handler.get_margins(), + handler.get_custom_paper_size(), + ) + self.notebook.insert_page(self.paper_frame, self.paper_label, 0) + self.paper_frame.show_all() + + ReportDialog.setup_report_options_frame(self) + + def doc_type_changed(self, obj): + """ + This routine is called when the user selects a new file + format for the report. It adjusts the various dialog sections + to reflect the appropriate values for the currently selected + file format. For example, a HTML document doesn't need any + paper size/orientation options, but it does need a template + file. Those changes are made here. + """ + self.open_with_app.set_sensitive(True) + + fname = self.target_fileentry.get_full_path(0) + (spath, ext) = os.path.splitext(fname) + + ext_val = obj.get_ext() + if ext_val: + fname = spath + ext_val + else: + fname = spath + self.target_fileentry.set_filename(fname) + + def make_document(self): + """Create a document of the type requested by the user. + """ + pstyle = self.paper_frame.get_paper_style() + + self.doc = self.format(self.options, pstyle) + + self.options.set_document(self.doc) + + def on_ok_clicked(self, obj): + """The user is satisfied with the dialog choices. Validate + the output file name before doing anything else. If there is + a file name, gather the options and create the report.""" + + # Is there a filename? This should also test file permissions, etc. + if not self.parse_target_frame(): + self.window.run() + + # Preparation + self.parse_format_frame() + self.parse_user_options() + + self.options.handler.set_paper_metric( + self.paper_frame.get_paper_metric()) + self.options.handler.set_paper_name(self.paper_frame.get_paper_name()) + self.options.handler.set_orientation(self.paper_frame.get_orientation()) + self.options.handler.set_margins(self.paper_frame.get_paper_margins()) + self.options.handler.set_custom_paper_size( + self.paper_frame.get_custom_paper_size()) + + # Create the output document. + self.make_document() + + # Save options + self.options.handler.save_options() + config.set('interface.open-with-default-viewer', + self.open_with_app.get_active()) + + def parse_format_frame(self): + """Parse the format frame of the dialog. Save the user + selected output format for later use.""" + self.format = self.format_menu.get_reference() + format_name = self.format_menu.get_clname() + self.options.handler.set_format_name(format_name) + + def setup_style_frame(self): + """Required by ReportDialog""" + pass + + @abstractmethod + def make_doc_menu(self): + """ + Build a menu of document types that are appropriate for + this graph report. + """ + + @abstractmethod + def get_category(self): + """ + Return the report category. + """ + + @abstractmethod + def get_options(self): + """ + Return the graph options. + """ diff --git a/gramps/gui/plug/report/_graphvizreportdialog.py b/gramps/gui/plug/report/_graphvizreportdialog.py index 9fbe901c6..cf8d66ef9 100644 --- a/gramps/gui/plug/report/_graphvizreportdialog.py +++ b/gramps/gui/plug/report/_graphvizreportdialog.py @@ -1,12 +1,7 @@ # # Gramps - a GTK+/GNOME based genealogy program # -# Copyright (C) 2007-2008 Brian G. Matherly -# Copyright (C) 2007-2009 Stephane Charette -# Copyright (C) 2009 Gary Burton -# Contribution 2009 by Bob Ham -# Copyright (C) 2010 Jakim Friant -# Copyright (C) 2012-2013 Paul Franklin +# Copyright (C) 2017 Nick Hall # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -23,174 +18,42 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # -#------------------------------------------------------------------------ -# -# python modules -# -#------------------------------------------------------------------------ -import os -from gramps.gen.const import GRAMPS_LOCALE as glocale -_ = glocale.translation.gettext +"""class for generating dialogs for graphviz-based reports """ #------------------------------------------------------------------------------- # -# GTK+ modules +# Gramps modules # #------------------------------------------------------------------------------- -from gi.repository import Gtk -from gi.repository import GObject - -#------------------------------------------------------------------------------- -# -# GRAMPS modules -# -#------------------------------------------------------------------------------- -from gramps.gen.config import config +from ._graphreportdialog import GraphReportDialog, BaseFormatComboBox from gramps.gen.plug.report import CATEGORY_GRAPHVIZ -from ._reportdialog import ReportDialog -from ._papermenu import PaperFrame import gramps.gen.plug.docgen.graphdoc as graphdoc -from gramps.gen.plug.menu import Menu -#------------------------------------------------------------------------------- -# -# GraphvizFormatComboBox -# -#------------------------------------------------------------------------------- -class GraphvizFormatComboBox(Gtk.ComboBox): - """ - Format combo box class for Graphviz report. - """ - def set(self, active=None): - self.store = Gtk.ListStore(GObject.TYPE_STRING) - self.set_model(self.store) - cell = Gtk.CellRendererText() - self.pack_start(cell, True) - self.add_attribute(cell, 'text', 0) - - index = 0 - active_index = 0 - for item in graphdoc.FORMATS: - name = item["descr"] - self.store.append(row=[name]) - if item['type'] == active: - active_index = index - index += 1 - self.set_active(active_index) - - def get_label(self): - return graphdoc.FORMATS[self.get_active()]["descr"] - - def get_reference(self): - return graphdoc.FORMATS[self.get_active()]["class"] - - def get_paper(self): - return 1 - - def get_styles(self): - return 0 - - def get_ext(self): - return '.%s' % graphdoc.FORMATS[self.get_active()]['ext'] - - def get_oformat_str(self): # the report's output-format type - return graphdoc.FORMATS[self.get_active()]["type"] - - def is_file_output(self): - return True - - def get_clname(self): - return graphdoc.FORMATS[self.get_active()]["type"] - #----------------------------------------------------------------------- # # GraphvizReportDialog # #----------------------------------------------------------------------- -class GraphvizReportDialog(ReportDialog): - """A class of ReportDialog customized for graphviz based reports.""" - def __init__(self, dbstate, uistate, opt, name, translated_name): - """Initialize a dialog to request that the user select options - for a graphviz report. See the ReportDialog class for - more information.""" - self.category = CATEGORY_GRAPHVIZ - self.__gvoptions = graphdoc.GVOptions() - self.dbname = dbstate.db.get_dbname() - ReportDialog.__init__(self, dbstate, uistate, opt, - name, translated_name) - - def init_options(self, option_class): - try: - if issubclass(option_class, object): # Old-style class - self.options = option_class(self.raw_name, - self.dbstate.get_database()) - except TypeError: - self.options = option_class - - menu = Menu() - self.__gvoptions.add_menu_options(menu) - - for category in menu.get_categories(): - for name in menu.get_option_names(category): - option = menu.get_option(category, name) - self.options.add_menu_option(category, name, option) - - self.options.load_previous_values() +class GraphvizReportDialog(GraphReportDialog): - def init_interface(self): - ReportDialog.init_interface(self) - self.doc_type_changed(self.format_menu) - self.notebook.set_current_page(1) # don't start on "Paper Options" - - def setup_format_frame(self): - """Set up the format frame of the dialog.""" + def make_doc_menu(self): + """ + Build a menu of document types that are appropriate for + this graph report. + """ self.format_menu = GraphvizFormatComboBox() - self.format_menu.set(self.options.handler.get_format_name()) - self.format_menu.connect('changed', self.doc_type_changed) - label = Gtk.Label(label="%s:" % _("Output Format")) - label.set_halign(Gtk.Align.START) - self.grid.attach(label, 1, self.row, 1, 1) - self.format_menu.set_hexpand(True) - self.grid.attach(self.format_menu, 2, self.row, 2, 1) - self.row += 1 - self.open_with_app = Gtk.CheckButton(_("Open with default viewer")) - self.open_with_app.set_active( - config.get('interface.open-with-default-viewer')) - self.grid.attach(self.open_with_app, 2, self.row, 2, 1) - self.row += 1 + def get_category(self): + """ + Return the report category. + """ + return CATEGORY_GRAPHVIZ - ext = self.format_menu.get_ext() - if ext is None: - ext = "" - else: - spath = self.get_default_directory() - if self.options.get_output(): - base = os.path.basename(self.options.get_output()) - else: - if self.dbname is None: - default_name = self.raw_name - else: - default_name = self.dbname + "_" + self.raw_name - base = "%s%s" % (default_name, ext) # "ext" already has a dot - spath = os.path.normpath(os.path.join(spath, base)) - self.target_fileentry.set_filename(spath) - - def setup_report_options_frame(self): - self.paper_label = Gtk.Label(label='%s' % _("Paper Options")) - self.paper_label.set_use_markup(True) - handler = self.options.handler - self.paper_frame = PaperFrame( - handler.get_paper_metric(), - handler.get_paper_name(), - handler.get_orientation(), - handler.get_margins(), - handler.get_custom_paper_size(), - ) - self.notebook.insert_page(self.paper_frame, self.paper_label, 0) - self.paper_frame.show_all() - - ReportDialog.setup_report_options_frame(self) + def get_options(self): + """ + Return the graph options. + """ + return graphdoc.GVOptions() def doc_type_changed(self, obj): """ @@ -201,81 +64,31 @@ class GraphvizReportDialog(ReportDialog): paper size/orientation options, but it does need a template file. Those changes are made here. """ - self.open_with_app.set_sensitive(True) - - fname = self.target_fileentry.get_full_path(0) - (spath, ext) = os.path.splitext(fname) + GraphReportDialog.doc_type_changed(self, obj) - ext_val = obj.get_ext() - if ext_val: - fname = spath + ext_val - else: - fname = spath - self.target_fileentry.set_filename(fname) - - output_format_str = obj.get_oformat_str() + output_format_str = obj.get_clname() if output_format_str in ['gvpdf', 'gspdf', 'ps']: # Always use 72 DPI for PostScript and PDF files. - self.__gvoptions.dpi.set_value(72) - self.__gvoptions.dpi.set_available(False) + self._goptions.dpi.set_value(72) + self._goptions.dpi.set_available(False) else: - self.__gvoptions.dpi.set_available(True) + self._goptions.dpi.set_available(True) if output_format_str in ['gspdf', 'dot']: # Multiple pages only valid for dot and PDF via GhostsScript. - self.__gvoptions.h_pages.set_available(True) - self.__gvoptions.v_pages.set_available(True) + self._goptions.h_pages.set_available(True) + self._goptions.v_pages.set_available(True) else: - self.__gvoptions.h_pages.set_value(1) - self.__gvoptions.v_pages.set_value(1) - self.__gvoptions.h_pages.set_available(False) - self.__gvoptions.v_pages.set_available(False) + self._goptions.h_pages.set_value(1) + self._goptions.v_pages.set_value(1) + self._goptions.h_pages.set_available(False) + self._goptions.v_pages.set_available(False) - def make_document(self): - """Create a document of the type requested by the user. - """ - pstyle = self.paper_frame.get_paper_style() - - self.doc = self.format(self.options, pstyle) - - self.options.set_document(self.doc) - - def on_ok_clicked(self, obj): - """The user is satisfied with the dialog choices. Validate - the output file name before doing anything else. If there is - a file name, gather the options and create the report.""" - # Is there a filename? This should also test file permissions, etc. - if not self.parse_target_frame(): - self.window.run() - - # Preparation - self.parse_format_frame() - self.parse_user_options() - - self.options.handler.set_paper_metric( - self.paper_frame.get_paper_metric()) - self.options.handler.set_paper_name(self.paper_frame.get_paper_name()) - self.options.handler.set_orientation(self.paper_frame.get_orientation()) - self.options.handler.set_margins(self.paper_frame.get_paper_margins()) - self.options.handler.set_custom_paper_size( - self.paper_frame.get_custom_paper_size()) - - # Create the output document. - self.make_document() - - # Save options - self.options.handler.save_options() - config.set('interface.open-with-default-viewer', - self.open_with_app.get_active()) - - def parse_format_frame(self): - """Parse the format frame of the dialog. Save the user - selected output format for later use.""" - self.format = self.format_menu.get_reference() - format_name = self.format_menu.get_clname() - self.options.handler.set_format_name(format_name) - - def setup_style_frame(self): - """Required by ReportDialog""" - pass +#------------------------------------------------------------------------------- +# +# GraphvizFormatComboBox +# +#------------------------------------------------------------------------------- +class GraphvizFormatComboBox(BaseFormatComboBox): + FORMATS = graphdoc.FORMATS diff --git a/gramps/gui/plug/report/_reportdialog.py b/gramps/gui/plug/report/_reportdialog.py index 916b9c0fa..2dd69937f 100644 --- a/gramps/gui/plug/report/_reportdialog.py +++ b/gramps/gui/plug/report/_reportdialog.py @@ -54,8 +54,9 @@ from .. import add_gui_options, make_gui_option from ...user import User from ...dialog import ErrorDialog, OptionDialog from gramps.gen.plug.report import (CATEGORY_TEXT, CATEGORY_DRAW, CATEGORY_BOOK, - CATEGORY_CODE, CATEGORY_WEB, CATEGORY_GRAPHVIZ, - standalone_categories) + CATEGORY_CODE, CATEGORY_WEB, + CATEGORY_GRAPHVIZ, CATEGORY_TREE, + standalone_categories) from gramps.gen.plug.docgen import StyleSheet, StyleSheetList from ...managedwindow import ManagedWindow from ._stylecombobox import StyleComboBox @@ -673,6 +674,9 @@ def report(dbstate, uistate, person, report_class, options_class, elif category == CATEGORY_GRAPHVIZ: from ._graphvizreportdialog import GraphvizReportDialog dialog_class = GraphvizReportDialog + elif category == CATEGORY_TREE: + from ._treereportdialog import TreeReportDialog + dialog_class = TreeReportDialog elif category == CATEGORY_WEB: from ._webreportdialog import WebReportDialog dialog_class = WebReportDialog diff --git a/gramps/gui/plug/report/_treereportdialog.py b/gramps/gui/plug/report/_treereportdialog.py new file mode 100644 index 000000000..94f631219 --- /dev/null +++ b/gramps/gui/plug/report/_treereportdialog.py @@ -0,0 +1,64 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2017 Nick Hall +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +"""class for generating dialogs for graphviz-based reports """ + +#------------------------------------------------------------------------------- +# +# Gramps modules +# +#------------------------------------------------------------------------------- +from ._graphreportdialog import GraphReportDialog, BaseFormatComboBox +from gramps.gen.plug.report import CATEGORY_TREE +import gramps.gen.plug.docgen.treedoc as treedoc + +#----------------------------------------------------------------------- +# +# TreeReportDialog +# +#----------------------------------------------------------------------- +class TreeReportDialog(GraphReportDialog): + + def make_doc_menu(self): + """ + Build a menu of document types that are appropriate for + this graph report. + """ + self.format_menu = TreeFormatComboBox() + + def get_category(self): + """ + Return the report category. + """ + return CATEGORY_TREE + + def get_options(self): + """ + Return the graph options. + """ + return treedoc.TreeOptions() + +#------------------------------------------------------------------------------- +# +# TreeFormatComboBox +# +#------------------------------------------------------------------------------- +class TreeFormatComboBox(BaseFormatComboBox): + FORMATS = treedoc.FORMATS diff --git a/po/POTFILES.in b/po/POTFILES.in index a3a50f6df..befc3c583 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -318,6 +318,7 @@ gramps/gen/plug/_pluginreg.py gramps/gen/plug/docbackend/docbackend.py gramps/gen/plug/docgen/graphdoc.py gramps/gen/plug/docgen/paperstyle.py +gramps/gen/plug/docgen/treedoc.py gramps/gen/plug/menu/_enumeratedlist.py gramps/gen/plug/report/_book.py gramps/gen/plug/report/_constants.py