Added support for new GVDoc interface which makes it easier to write reports that user Graphviz for layout.

svn: r9042
This commit is contained in:
Brian Matherly 2007-09-30 04:56:56 +00:00
parent 07a96a7333
commit 152a452b2e
6 changed files with 794 additions and 21 deletions

View File

@ -1,3 +1,10 @@
2007-09-29 Brian Matherly <brian@gramps-project.org>
* src/plugins/GVHourGlass.py: Added.
* src/ReportBase/_GraphvizReportDialog: Added
* src/ReportBase/_Constants.py: Add Graphviz type.
* src/ReportBase/_ReportDialog.py: Add Graphviz type.
* src/BaseDoc.py: Add GVDoc interface.
2007-09-29 Brian Matherly <brian@gramps-project.org>
* src/gramps.py: Don't import gramps_main until logging is setup.

View File

@ -1551,3 +1551,50 @@ class DrawDoc:
def draw_line(self, style, x1, y1, x2, y2):
raise NotImplementedError
#-------------------------------------------------------------------------------
#
# GVDoc
#
#-------------------------------------------------------------------------------
class GVDoc:
"""
Abstract Interface for Graphviz document generators. Output formats
for Graphviz reports must implment this interface to be used by the
report system.
"""
def add_node(self, id, label, shape="box", fillcolor="white", url=""):
"""
Add a node to this graph. Nodes can be different shapes like boxes and
circles.
@param id: A unique identification value for this node.
Example: "p55"
@type id: string
@param label: The text to be displayed in the node.
Example: "John Smith"
@type label: string
@param shape: The shape for the node.
Examples: "box", "ellipse", "circle"
@type shape: string
@param fillcolor: The fill color for the node.
Examples: "blue", "lightyellow"
@type fillcolor: string
@param url: A URL for the node.
@type url: string
@return: nothing
"""
raise NotImplementedError
def add_link(self, id1, id2):
"""
Add a link between two nodes.
@param id1: The unique identifier of the starting node.
Example: "p55"
@type id1: string
@param id2: The unique identifier of the ending node.
Example: "p55"
@type id2: string
@return: nothing
"""
raise NotImplementedError

View File

@ -41,20 +41,22 @@ MODE_BKI = 2 # Book Item interface using GUI
MODE_CLI = 4 # Command line interface (CLI)
# Report categories
CATEGORY_TEXT = 0
CATEGORY_DRAW = 1
CATEGORY_CODE = 2
CATEGORY_WEB = 3
CATEGORY_VIEW = 4
CATEGORY_BOOK = 5
CATEGORY_TEXT = 0
CATEGORY_DRAW = 1
CATEGORY_CODE = 2
CATEGORY_WEB = 3
CATEGORY_VIEW = 4
CATEGORY_BOOK = 5
CATEGORY_GRAPHVIZ = 6
standalone_categories = {
CATEGORY_TEXT : _("Text Reports"),
CATEGORY_DRAW : _("Graphical Reports"),
CATEGORY_CODE : _("Code Generators"),
CATEGORY_WEB : _("Web Page"),
CATEGORY_VIEW : _("View"),
CATEGORY_BOOK : _("Books"),
CATEGORY_TEXT : _("Text Reports"),
CATEGORY_DRAW : _("Graphical Reports"),
CATEGORY_CODE : _("Code Generators"),
CATEGORY_WEB : _("Web Page"),
CATEGORY_VIEW : _("View"),
CATEGORY_BOOK : _("Books"),
CATEGORY_GRAPHVIZ : _("Graphviz"),
}
book_categories = {

View File

@ -0,0 +1,524 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2007 Brian G. Matherly
#
# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# $Id: $
#------------------------------------------------------------------------
#
# python modules
#
#------------------------------------------------------------------------
import os
from cStringIO import StringIO
import tempfile
#-------------------------------------------------------------------------------
#
# GTK+ modules
#
#-------------------------------------------------------------------------------
import gtk
#-------------------------------------------------------------------------------
#
# GRAMPS modules
#
#-------------------------------------------------------------------------------
import Mime
import Utils
import BaseDoc
import Config
from _Constants import CATEGORY_GRAPHVIZ
from _ReportDialog import ReportDialog
#-------------------------------------------------------------------------------
#
# Private Contstants
#
#-------------------------------------------------------------------------------
_dot_found = 0
_gs_cmd = ""
if os.sys.platform == "win32":
_dot_found = Utils.search_for("dot.exe")
if Utils.search_for("gswin32c.exe") == 1:
_gs_cmd = "gswin32c.exe"
elif Utils.search_for("gswin32.exe") == 1:
_gs_cmd = "gswin32.exe"
else:
_dot_found = Utils.search_for("dot")
if Utils.search_for("gs") == 1:
_gs_cmd = "gs"
#-------------------------------------------------------------------------------
#
# GVDocBase
#
#-------------------------------------------------------------------------------
class GVDocBase(BaseDoc.BaseDoc,BaseDoc.GVDoc):
"""
Base document generator for all Graphiz codument 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,styles,paper_style,template):
BaseDoc.BaseDoc.__init__(self,styles,paper_style,template)
self.dot = StringIO()
self.paper = paper_style
paper_size = paper_style.get_size()
pheight = paper_size.get_height_inches()
pwidth = paper_size.get_width_inches()
# graph size
if self.paper.get_orientation() == BaseDoc.PAPER_LANDSCAPE:
rotate = 90
sizew = ( paper_size.get_height() -
self.paper.get_top_margin() -
self.paper.get_bottom_margin() ) / 2.54
sizeh = ( paper_size.get_width() -
self.paper.get_left_margin() -
self.paper.get_right_margin() ) / 2.54
else:
rotate = 0
sizew = ( paper_size.get_width() -
self.paper.get_left_margin() -
self.paper.get_right_margin() ) / 2.54
sizeh = ( paper_size.get_height() -
self.paper.get_top_margin() -
self.paper.get_bottom_margin() ) / 2.54
self.dot.write( 'digraph GRAMPS_graph { \n' )
self.dot.write( ' bgcolor=white; \n' )
self.dot.write( ' center=1; \n' )
self.dot.write( ' rankdir="TB"; \n' )
self.dot.write( ' mclimit=2.0; \n' )
self.dot.write( ' pagedir="BL"; \n' )
self.dot.write( ' page="%3.2f,%3.2f"; \n' % ( pwidth, pheight ) )
self.dot.write( ' size="%3.2f,%3.2f"; \n' % ( sizew, sizeh ) )
self.dot.write( ' rotate=%d; \n' % rotate )
self.dot.write( ' nodesep=0.25; \n' )
def open(self, filename):
self.filename = os.path.normpath(os.path.abspath(filename))
def close(self):
"""
This isn't useful by itself. Other classes need to override this and
actually generate a file.
"""
self.dot.write( '}' )
def add_node(self, id, label, shape="box", fillcolor="white", url=""):
"""
Add a node to this graph. Nodes can be different shapes like boxes and
circles.
Implementes BaseDoc.GVDoc.add_node().
"""
line = ' "%s" [style=filled label="%s", shape="%s", fillcolor="%s",' \
' URL="%s"];\n' % \
(id, label, shape, fillcolor, url)
self.dot.write(line)
def add_link(self, id1, id2):
"""
Add a link between two nodes.
Implementes BaseDoc.GVDoc.add_link().
"""
self.dot.write(' "%s" -> "%s";\n' % (id1, id2))
#-------------------------------------------------------------------------------
#
# GVDotDoc
#
#-------------------------------------------------------------------------------
class GVDotDoc(GVDocBase):
def close(self):
GVDoc.close(self)
# Make sure the extension is correct
if self.filename[-4:] != ".dot":
self.filename += ".dot"
file = open(self.filename,"w")
file.write(self.dot.getvalue())
file.close()
if self.print_req:
app = Mime.get_application("text/x-graphviz")
Utils.launch(app[0], self.filename)
#-------------------------------------------------------------------------------
#
# GVPsDoc
#
#-------------------------------------------------------------------------------
class GVPsDoc(GVDocBase):
def close(self):
GVDocBase.close(self)
# Make sure the extension is correct
if self.filename[-3:] != ".ps":
self.filename += ".ps"
# Create a temporary dot file
(handle,tmp_dot) = tempfile.mkstemp(".dot" )
dotfile = os.fdopen(handle,"w")
dotfile.write(self.dot.getvalue())
dotfile.close()
# Generate the PS file.
os.system( 'dot -Tps -o"%s" "%s"' % (self.filename,tmp_dot) )
# Delete the temporary dot file
os.remove(tmp_dot)
if self.print_req:
app = Mime.get_application("application/postscript")
Utils.launch(app[0], self.filename)
#-------------------------------------------------------------------------------
#
# GVSvgDoc
#
#-------------------------------------------------------------------------------
class GVSvgDoc(GVDocBase):
def close(self):
GVDocBase.close(self)
# Make sure the extension is correct
if self.filename[-4:] != ".svg":
self.filename += ".svg"
# Create a temporary dot file
(handle,tmp_dot) = tempfile.mkstemp(".dot" )
dotfile = os.fdopen(handle,"w")
dotfile.write(self.dot.getvalue())
dotfile.close()
# Generate the PS file.
os.system( 'dot -Tsvg -o"%s" "%s"' % (self.filename,tmp_dot) )
# Delete the temporary dot file
os.remove(tmp_dot)
if self.print_req:
app = Mime.get_application("image/svg")
Utils.launch(app[0], self.filename)
#-------------------------------------------------------------------------------
#
# GVSvgzDoc
#
#-------------------------------------------------------------------------------
class GVSvgzDoc(GVDocBase):
def close(self):
GVDocBase.close(self)
# Make sure the extension is correct
if self.filename[-5:] != ".svgz":
self.filename += ".svgz"
# Create a temporary dot file
(handle,tmp_dot) = tempfile.mkstemp(".dot" )
dotfile = os.fdopen(handle,"w")
dotfile.write(self.dot.getvalue())
dotfile.close()
# Generate the PS file.
os.system( 'dot -Tsvgz -o"%s" "%s"' % (self.filename,tmp_dot) )
# Delete the temporary dot file
os.remove(tmp_dot)
if self.print_req:
app = Mime.get_application("image/svgz")
Utils.launch(app[0], self.filename)
#-------------------------------------------------------------------------------
#
# GVPngDoc
#
#-------------------------------------------------------------------------------
class GVPngDoc(GVDocBase):
def close(self):
GVDocBase.close(self)
# Make sure the extension is correct
if self.filename[-4:] != ".png":
self.filename += ".png"
# Create a temporary dot file
(handle,tmp_dot) = tempfile.mkstemp(".dot" )
dotfile = os.fdopen(handle,"w")
dotfile.write(self.dot.getvalue())
dotfile.close()
# Generate the PS file.
os.system( 'dot -Tpng -o"%s" "%s"' % (self.filename,tmp_dot) )
# Delete the temporary dot file
os.remove(tmp_dot)
if self.print_req:
app = Mime.get_application("image/png")
Utils.launch(app[0], self.filename)
#-------------------------------------------------------------------------------
#
# GVJpegDoc
#
#-------------------------------------------------------------------------------
class GVJpegDoc(GVDocBase):
def close(self):
GVDocBase.close(self)
# Make sure the extension is correct
if self.filename[-5:] != ".jpeg":
self.filename += ".jpeg"
# Create a temporary dot file
(handle,tmp_dot) = tempfile.mkstemp(".dot" )
dotfile = os.fdopen(handle,"w")
dotfile.write(self.dot.getvalue())
dotfile.close()
# Generate the PS file.
os.system( 'dot -Tjpg -o"%s" "%s"' % (self.filename,tmp_dot) )
# Delete the temporary dot file
os.remove(tmp_dot)
if self.print_req:
app = Mime.get_application("image/jpeg")
Utils.launch(app[0], self.filename)
#-------------------------------------------------------------------------------
#
# GVGifDoc
#
#-------------------------------------------------------------------------------
class GVGifDoc(GVDocBase):
def close(self):
GVDocBase.close(self)
# Make sure the extension is correct
if self.filename[-4:] != ".gif":
self.filename += ".gif"
# Create a temporary dot file
(handle,tmp_dot) = tempfile.mkstemp(".dot" )
dotfile = os.fdopen(handle,"w")
dotfile.write(self.dot.getvalue())
dotfile.close()
# Generate the PS file.
os.system( 'dot -Tgif -o"%s" "%s"' % (self.filename,tmp_dot) )
# Delete the temporary dot file
os.remove(tmp_dot)
if self.print_req:
app = Mime.get_application("image/gif")
Utils.launch(app[0], self.filename)
#-------------------------------------------------------------------------------
#
# GVPdfDoc
#
#-------------------------------------------------------------------------------
class GVPdfDoc(GVDocBase):
def close(self):
GVDocBase.close(self)
# Make sure the extension is correct
if self.filename[-4:] != ".pdf":
self.filename += ".pdf"
# Create a temporary dot file
(handle,tmp_dot) = tempfile.mkstemp(".dot" )
dotfile = os.fdopen(handle,"w")
dotfile.write(self.dot.getvalue())
dotfile.close()
# Generate a temporary PS file.
os.system( 'dot -Tps -o"%s" "%s"' % (self.filename,tmp_dot) )
# Create a temporary Postscript file
(handle,tmp_ps) = tempfile.mkstemp(".ps" )
os.close( handle )
# Generate Postscript using dot
command = 'dot -Tps -o"%s" "%s"' % ( tmp_ps, tmp_dot )
os.system(command)
# Add .5 to remove rounding errors.
paper_size = self.paper.get_size()
width_pt = int( (paper_size.get_width_inches() * 72) + 0.5 )
height_pt = int( (paper_size.get_height_inches() * 72) + 0.5 )
# Convert to PDF using ghostscript
command = '%s -q -sDEVICE=pdfwrite -dNOPAUSE -dDEVICEWIDTHPOINTS=%d' \
' -dDEVICEHEIGHTPOINTS=%d -sOutputFile="%s" "%s" -c quit' \
% ( _gs_cmd, width_pt, height_pt, self.filename, tmp_ps )
os.system(command)
os.remove(tmp_ps)
os.remove(tmp_dot)
if self.print_req:
app = Mime.get_application("application/pdf")
Utils.launch(app[0], self.filename)
#-------------------------------------------------------------------------------
#
# Various Graphviz formats.
#
#-------------------------------------------------------------------------------
_formats = []
_formats += [{ 'type' : "dot",
'descr': _("Graphviz Dot File"),
'mime' : "text/x-graphviz",
'class': GVDotDoc }]
if _dot_found:
_formats += [{ 'type' : "ps",
'descr': _("Postscript"),
'mime' : "application/postscript",
'class': GVPsDoc }]
_formats += [{ 'type' : "svg",
'descr': _("Structured Vector Graphics (SVG)"),
'mime' : "image/svg",
'class': GVSvgDoc }]
_formats += [{ 'type' : "svgz",
'descr': _("Compressed Structured Vector Graphs (SVG)"),
'mime' : "image/svgz",
'class': GVSvgzDoc }]
_formats += [{ 'type' : "png",
'descr': _("PNG image"),
'mime' : "image/png",
'class': GVPngDoc }]
_formats += [{ 'type' : "jpg",
'descr': _("JPEG image"),
'mime' : "image/jpeg",
'class': GVJpegDoc }]
_formats += [{ 'type' : "gif",
'descr': _("GIF image"),
'mime' : "image/gif",
'class': GVGifDoc }]
if _dot_found and _gs_cmd != "":
_formats += [{ 'type' : "pdf",
'descr': _("PDF"),
'mime' : "application/pdf",
'class': GVPdfDoc }]
#-------------------------------------------------------------------------------
#
# GraphvizFormatComboBox
#
#-------------------------------------------------------------------------------
class GraphvizFormatComboBox(gtk.ComboBox):
"""
Format combo box class for Graphviz report.
"""
def set(self,active=None):
self.store = gtk.ListStore(str)
self.set_model(self.store)
cell = gtk.CellRendererText()
self.pack_start(cell,True)
self.add_attribute(cell,'text',0)
out_pref = Config.get(Config.OUTPUT_PREFERENCE)
index = 0
active_index = 0
for item in _formats:
name = item["descr"]
self.store.append(row=[name])
if item['type'] == active:
active_index = index
elif not active and name == out_pref:
active_index = index
index = index + 1
self.set_active(active_index)
def get_label(self):
return _formats[self.get_active()]["descr"]
def get_reference(self):
return _formats[self.get_active()]["class"]
def get_paper(self):
return 1
def get_styles(self):
return 0
def get_ext(self):
return '.%s' % _formats[self.get_active()]["type"]
def get_format_str(self):
return _formats[self.get_active()]["type"]
def get_printable(self):
_apptype = _formats[self.get_active()]["mime"]
print_label = None
try:
mprog = Mime.get_application(_apptype)
if Utils.search_for(mprog[0]):
print_label = _("Open in %(program_name)s") % { 'program_name':
mprog[1] }
else:
print_label = None
except:
print_label = None
return print_label
def get_clname(self):
return _formats[self.get_active()]["type"]
#-----------------------------------------------------------------------
#
# GraphvizReportDialog
#
#-----------------------------------------------------------------------
class GraphvizReportDialog(ReportDialog):
"""A class of ReportDialog customized for graphviz based reports."""
def __init__(self,dbstate,uistate,person,opt,name,translated_name):
"""Initialize a dialog to request that the user select options
for a graphiz report. See the ReportDialog class for
more information."""
self.category = CATEGORY_GRAPHVIZ
ReportDialog.__init__(self,dbstate,uistate,person,opt,
name,translated_name)
def make_doc_menu(self,active=None):
"""Build a menu of document types that are appropriate for
a graphiz report."""
self.format_menu = GraphvizFormatComboBox()
self.format_menu.set(active)

View File

@ -52,7 +52,8 @@ import const
from QuestionDialog import ErrorDialog, OptionDialog, RunDatabaseRepair
from _Constants import CATEGORY_TEXT, CATEGORY_DRAW, CATEGORY_BOOK, \
CATEGORY_VIEW, CATEGORY_CODE, CATEGORY_WEB, standalone_categories
CATEGORY_VIEW, CATEGORY_CODE, CATEGORY_WEB, CATEGORY_GRAPHVIZ, \
standalone_categories
from _BareReportDialog import BareReportDialog
from _FileEntry import FileEntry
from _PaperMenu import PaperComboBox, OrientationComboBox, paper_sizes
@ -672,6 +673,9 @@ def report(dbstate,uistate,person,report_class,options_class,
elif category == CATEGORY_DRAW:
from _DrawReportDialog import DrawReportDialog
dialog_class = DrawReportDialog
elif category == CATEGORY_GRAPHVIZ:
from _GraphvizReportDialog import GraphvizReportDialog
dialog_class = GraphvizReportDialog
elif category in (CATEGORY_BOOK,CATEGORY_CODE,CATEGORY_VIEW,CATEGORY_WEB):
try:
report_class(dbstate,uistate,person)
@ -701,15 +705,20 @@ def report(dbstate,uistate,person,report_class,options_class,
except Errors.ReportError, msg:
(m1,m2) = msg.messages()
ErrorDialog(m1,m2)
except Errors.DatabaseError,msg:
except Errors.DatabaseError,msg:
ErrorDialog(_("Report could not be created"),str(msg))
except AttributeError,msg:
if str(msg).startswith("'NoneType' object has no attribute"):
# "'NoneType' object has no attribute ..." usually means
# database corruption
RunDatabaseRepair(str(msg))
else:
raise
# The following except statement will catch all "NoneType" exceptions.
# This is useful for released code where the exception is most likely
# a corrupt database. But it is less useful for developing new reports
# where the execption is most likely a report bug.
# except AttributeError,msg:
# if str(msg).startswith("'NoneType' object has no attribute"):
# # "'NoneType' object has no attribute ..." usually means
# # database corruption
# RunDatabaseRepair(str(msg))
# else:
# raise
raise
except:
log.error("Failed to run report.", exc_info=True)
break

184
src/plugins/GVHourGlass.py Normal file
View File

@ -0,0 +1,184 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2007 Brian G. Matherly
#
# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# $Id: $
"""
Generate an hourglass graph using the GraphViz generator.
"""
#------------------------------------------------------------------------
#
# GRAMPS modules
#
#------------------------------------------------------------------------
from PluginUtils import register_report
from ReportBase import Report, ReportUtils, MenuOptions, NumberOption, \
CATEGORY_GRAPHVIZ, MODE_GUI, MODE_CLI
from BasicUtils import name_displayer
import DateHandler
#------------------------------------------------------------------------
#
# AncestorChart
#
#------------------------------------------------------------------------
class HourGlassReport(Report):
def __init__(self,database,person,options_class):
"""
Creates HourGlass object that produces the report.
"""
Report.__init__(self,database,person,options_class)
self.person = person
self.db = database
self.max_descend = options_class.handler.options_dict['maxdescend']
self.max_ascend = options_class.handler.options_dict['maxascend']
def write_report(self):
self.add_person(self.person)
self.traverse_up(self.person,1)
self.traverse_down(self.person,1)
def traverse_down(self,person,gen):
"""
Resursively find the descendants of the given person.
"""
if gen > self.max_descend:
return
for family_handle in person.get_family_handle_list():
family = self.db.get_family_from_handle(family_handle)
self.add_family(family)
self.doc.add_link( person.get_gramps_id(), family.get_gramps_id() )
for child_ref in family.get_child_ref_list():
child_handle = child_ref.get_reference_handle()
child = self.db.get_person_from_handle(child_handle)
self.add_person(child)
self.doc.add_link(family.get_gramps_id() ,child.get_gramps_id())
self.traverse_down(child,gen+1)
def traverse_up(self,person,gen):
"""
Resursively find the ancestors of the given person.
"""
if gen > self.max_ascend:
return
family_handle = person.get_main_parents_family_handle()
if family_handle:
family = self.db.get_family_from_handle(family_handle)
family_id = family.get_gramps_id()
self.add_family(family)
self.doc.add_link( family_id, person.get_gramps_id() )
father_handle = family.get_father_handle()
if father_handle:
father = self.db.get_person_from_handle(father_handle)
self.add_person(father)
self.doc.add_link( father.get_gramps_id(), family_id )
self.traverse_up(father,gen+1)
mother_handle = family.get_mother_handle()
if mother_handle:
mother = self.db.get_person_from_handle( mother_handle )
self.add_person( mother )
self.doc.add_link( mother.get_gramps_id(), family_id )
self.traverse_up( mother, gen+1 )
def add_person(self,person):
"""
Add a person to the Graph. The node id will be the person's gramps id.
"""
p_id = person.get_gramps_id()
name = name_displayer.display_formal(person)
birth_evt = ReportUtils.get_birth_or_fallback(self.db,person)
if birth_evt:
birth = DateHandler.get_date(birth_evt)
else:
birth = ""
death_evt = ReportUtils.get_death_or_fallback(self.db,person)
if death_evt:
death = DateHandler.get_date(death_evt)
else:
death = ""
label = "%s \\n(%s - %s)" % (name,birth,death)
gender = person.get_gender()
if gender == person.MALE:
color = 'lightblue'
elif gender == person.FEMALE:
color = 'lightpink'
else:
color = 'lightgray'
self.doc.add_node(p_id,label,"box",color)
def add_family(self,family):
"""
Add a family to the Graph. The node id will be the family's gramps id.
"""
family_id = family.get_gramps_id()
label = ""
marriage = ReportUtils.find_marriage(self.db,family)
if marriage:
label = DateHandler.get_date(marriage)
self.doc.add_node(family_id,label,"ellipse","lightyellow")
#------------------------------------------------------------------------
#
# HourGlassOptions
#
#------------------------------------------------------------------------
class HourGlassOptions(MenuOptions):
"""
Defines options for the HourGlass report.
"""
def __init__(self,name,person_id=None):
MenuOptions.__init__(self,name,person_id)
def add_menu_options(self,menu):
category_name = _("Report Options")
max_gen = NumberOption(_('Max Descendant Generations'),10,1,15)
max_gen.set_help(_("The number of generations of descendants to " \
"include in the report"))
menu.add_option(category_name,"maxdescend",max_gen)
max_gen = NumberOption(_('Max Ancestor Generations'),10,1,15)
max_gen.set_help(_("The number of generations of ancestors to " \
"include in the report"))
menu.add_option(category_name,"maxascend",max_gen)
#------------------------------------------------------------------------
#
#
#
#------------------------------------------------------------------------
register_report(
name = 'hourglass_graph',
category = CATEGORY_GRAPHVIZ,
report_class = HourGlassReport,
options_class = HourGlassOptions,
modes = MODE_GUI | MODE_CLI,
translated_name = _("Hourglass Graph"),
status = _("Stable"),
author_name = "Brian G. Matherly",
author_email = "brian@gramps-project.org",
description = _("Produces an hourglass graph")
)