Quick reports general functionality

svn: r8889
This commit is contained in:
Benny Malengier 2007-08-29 19:37:58 +00:00
parent 4a2e1058b0
commit 9ce50cee93
13 changed files with 391 additions and 176 deletions

View File

@ -1,3 +1,19 @@
2007-08-29 Benny Malengier <benny.malengier@gramps-project.org>
* src/ReportBase/_Constants.py: quick reports constants
* src/PluginUtils/__init__.py : add quick reports
* src/PluginUtils/_Plugins.py : add quick reports
* src/PluginUtils/_PluginMgr.py: allow registering of quick reports,
remove assignments of global var
* src/plugins/all_events.py : register as quick report
* src/plugins/siblings.py : register as quick report
* src/QuickReports.py : construction of quick report GUI
and callbacks
* src/Makefile.am : Add QuickReports.py
* po/POTFILES.in : Add QuickReports.py
* src/Editors/_EditPerson.py : use QuickReports.py for quick report popup
* src/DataViews/_PersonView.py: use QuickReports.py for quick report popup
* src/ManagedWindow.py : add init of attribute
2007-08-29 Benny Malengier <bm@cage.ugent.be>
* src/Editors/_EditNote.py : ManagedWindow error, bad naming in menu
* src/Editors/_EditMediaRef.py : Bad naming in menu

View File

@ -35,6 +35,7 @@ src/Navigation.py
src/PageView.py
src/PlaceUtils.py
src/QuestionDialog.py
src/QuickReports.py
src/RecentFiles.py
src/Relationship.py
src/Reorder.py

View File

@ -153,10 +153,7 @@ class PersonView(PageView.PersonNavView):
('CloseAllNodes', None, _("Collapse all nodes"), None, None,
self.close_all_nodes),
('QuickReport', None, _("Quick Report"), None, None, None),
('AllEvents', None, _("All Events"), None, None,
self.quick_report),
('Siblings', None, _("Siblings"), None, None,
self.siblings_report),
('Dummy', None, ' ', None, None, self.dummy_report),
])
self.edit_action.add_actions(
@ -414,9 +411,8 @@ class PersonView(PageView.PersonNavView):
<menuitem action="Edit"/>
<menuitem action="Remove"/>
<separator/>
<menu action="QuickReport">
<menuitem action="AllEvents"/>
<menuitem action="Siblings"/>
<menu name="QuickReport" action="QuickReport">
<menuitem action="Dummy"/>
</menu>
</popup>
</ui>'''
@ -833,8 +829,32 @@ class PersonView(PageView.PersonNavView):
except Errors.WindowActiveError:
pass
return True
elif event.type == gtk.gdk.BUTTON_PRESS and event.button == 3:
from ReportBase import CATEGORY_QR_PERSON
from QuickReports import create_quickreport_menu
menu = self.uistate.uimanager.get_widget('/Popup')
#add the quickreports, different for every handle
qr_menu = self.uistate.uimanager.\
get_widget('/Popup/QuickReport').get_submenu()
if qr_menu :
self.uistate.uimanager.\
get_widget('/Popup/QuickReport').remove_submenu()
reportactions = []
if menu and self.dbstate.active:
(ui, reportactions) = create_quickreport_menu(
CATEGORY_QR_PERSON,
self.dbstate,
self.dbstate.active.handle)
if len(reportactions) > 1 :
qr_menu = gtk.Menu()
for action in reportactions[1:] :
Utils.add_menuitem(qr_menu, action[2], None, action[5])
self.uistate.uimanager.get_widget('/Popup/QuickReport').\
set_submenu(qr_menu)
if menu:
menu.popup(None, None, None, event.button, event.time)
return True
@ -960,23 +980,11 @@ class PersonView(PageView.PersonNavView):
path = (path[0]+1,)
ofile.end_page()
ofile.close()
def dummy_report(self, obj):
''' For the xml UI definition of popup to work, the submenu
Quick Report must have an entry in the xml
As this submenu will be dynamically built, we offer a dummy action
'''
pass
def run_report(self, func):
from TextBufDoc import TextBufDoc
from Simple import make_basic_stylesheet
if self.dbstate.active:
d = TextBufDoc(make_basic_stylesheet(), None, None)
handle = self.dbstate.active.handle
person = self.dbstate.db.get_person_from_handle(handle)
d.open("")
func(self.db, d, person)
d.close()
def quick_report(self, obj):
import all_events
self.run_report(all_events.run)
def siblings_report(self, obj):
import siblings
self.run_report(siblings.run)

View File

@ -64,6 +64,7 @@ from DisplayTabs import \
PersonEventEmbedList,NameEmbedList,SourceEmbedList,AttrEmbedList,\
AddrEmbedList,NoteTab,GalleryTab,WebEmbedList,PersonRefEmbedList, \
LdsEmbedList,PersonBackRefList
from QuickReports import create_quickreport_menu
#-------------------------------------------------------------------------
#
@ -133,7 +134,6 @@ class EditPerson(EditPrimary):
self.contextbox = self.top.get_widget("eventboxtop")
self._build_ui_manager()
def _post_init(self):
"""
@ -277,56 +277,6 @@ class EditPerson(EditPrimary):
self.obj.set_gramps_id,
self.obj.get_gramps_id,
self.db.readonly)
def _build_ui_manager(self):
self.uimanager = gtk.UIManager()
self.all_action = gtk.ActionGroup("/PersonAll")
self.home_action = gtk.ActionGroup("/PersonHome")
self.report_action = gtk.ActionGroup("/PersonReport")
self.all_action.add_actions([
('ActivePerson', gtk.STOCK_APPLY, _("Make Active Person"),
None, None, self._make_active),
])
self.home_action.add_actions([
('HomePerson', gtk.STOCK_HOME, _("Make Home Person"),
None, None, self._make_home_person),
])
self.report_action.add_actions([
('QuickReport', None, _("Quick Report"), None, None, None),
('AllEvents', None, _("All Events"), None, None,
self.quick_report),
('Siblings', None, _("Siblings"), None, None,
self.siblings_report),
])
self.all_action.set_visible(True)
self.home_action.set_visible(True)
self.report_action.set_visible(True)
merge_id = self.uimanager.add_ui_from_string(self.ui_definition())
self.uimanager.insert_action_group(self.all_action, -1)
self.uimanager.insert_action_group(self.home_action, -1)
self.uimanager.insert_action_group(self.report_action, -1)
def ui_definition(self):
"""
Specifies the UIManager XML code that defines the popup
associated with the interface.
"""
return '''<ui>
<popup name="Popup">
<menuitem action="ActivePerson"/>
<menuitem action="HomePerson"/>
<separator/>
<menu action="QuickReport">
<menuitem action="AllEvents"/>
<menuitem action="Siblings"/>
</menu>
</popup>
</ui>'''
def _create_tabbed_pages(self):
"""
@ -527,17 +477,64 @@ class EditPerson(EditPrimary):
if self.obj.get_handle() == 0 :
return False
if self.obj.get_handle() == \
#build the possible popup menu
self._build_popup_ui()
if self.dbstate.db.get_default_person() and \
self.obj.get_handle() == \
self.dbstate.db.get_default_person().get_handle():
self.home_action.set_sensitive(False)
else :
self.home_action.set_sensitive(True)
menu = self.uimanager.get_widget('/Popup')
menu = self.popupmanager.get_widget('/Popup')
if menu:
menu.popup(None, None, None, event.button, event.time)
return True
return False
def _build_popup_ui(self):
from ReportBase import CATEGORY_QR_PERSON
self.popupmanager = gtk.UIManager()
self.all_action = gtk.ActionGroup("/PersonAll")
self.home_action = gtk.ActionGroup("/PersonHome")
self.all_action.add_actions([
('ActivePerson', gtk.STOCK_APPLY, _("Make Active Person"),
None, None, self._make_active),
])
self.home_action.add_actions([
('HomePerson', gtk.STOCK_HOME, _("Make Home Person"),
None, None, self._make_home_person),
])
#see which quick reports are available now:
(ui, reportactions) = create_quickreport_menu(CATEGORY_QR_PERSON,
self.dbstate,self.obj.get_handle())
self.report_action = gtk.ActionGroup("/PersonReport")
self.report_action.add_actions(reportactions)
self.all_action.set_visible(True)
self.home_action.set_visible(True)
self.report_action.set_visible(True)
self.popupmanager.insert_action_group(self.all_action, -1)
self.popupmanager.insert_action_group(self.home_action, -1)
self.popupmanager.insert_action_group(self.report_action, -1)
popupui = '''
<ui>
<popup name="Popup">
<menuitem action="ActivePerson"/>
<menuitem action="HomePerson"/>
<separator/>'''
self.popupmanager.add_ui_from_string(popupui + ui + '''
</popup>
</ui>''')
def _make_active(self, obj):
self.dbstate.change_active_person(self.obj)
@ -583,12 +580,12 @@ class EditPerson(EditPrimary):
def _check_for_unknown_gender(self):
if self.obj.get_gender() == RelLib.Person.UNKNOWN:
d = GenderDialog(self.window)
gender = d.run()
d.destroy()
if gender >= 0:
self.obj.set_gender(gender)
d = GenderDialog(self.window)
gender = d.run()
d.destroy()
if gender >= 0:
self.obj.set_gender(gender)
def _check_and_update_id(self):
original = self.db.get_person_from_handle(self.obj.get_handle())
@ -821,44 +818,24 @@ class EditPerson(EditPrimary):
Config.set(Config.PERSON_WIDTH, width)
Config.set(Config.PERSON_HEIGHT, height)
Config.sync()
def run_report(self, func):
from TextBufDoc import TextBufDoc
from Simple import make_basic_stylesheet
if self.dbstate.active:
d = TextBufDoc(make_basic_stylesheet(), None, None)
handle = self.dbstate.active.handle
person = self.dbstate.db.get_person_from_handle(handle)
d.open("")
func(self.db, d, person)
d.close()
def quick_report(self, obj):
import all_events
self.run_report(all_events.run)
def siblings_report(self, obj):
import siblings
self.run_report(siblings.run)
class GenderDialog(gtk.MessageDialog):
def __init__(self,parent=None):
gtk.MessageDialog.__init__(self,
parent,
flags=gtk.DIALOG_MODAL,
type=gtk.MESSAGE_QUESTION,
)
parent,
flags=gtk.DIALOG_MODAL,
type=gtk.MESSAGE_QUESTION,
)
self.set_icon(ICON)
self.set_title('')
self.set_markup('<span size="larger" weight="bold">%s</span>' %
_('Unknown gender specified'))
self.format_secondary_text(
_("The gender of the person is currently unknown. "
"Usually, this is a mistake. Please specify the gender."))
self.set_markup('<span size="larger" weight="bold">%s</span>' %
_('Unknown gender specified'))
self.format_secondary_text(
_("The gender of the person is currently unknown. "
"Usually, this is a mistake. Please specify the gender."))
self.add_button(_('Male'), RelLib.Person.MALE)
self.add_button(_('Female'), RelLib.Person.FEMALE)
self.add_button(_('Unknown'), RelLib.Person.UNKNOWN)
self.add_button(_('Male'), RelLib.Person.MALE)
self.add_button(_('Female'), RelLib.Person.FEMALE)
self.add_button(_('Unknown'), RelLib.Person.UNKNOWN)

View File

@ -65,6 +65,7 @@ gdir_PYTHON = \
Navigation.py\
PageView.py\
QuestionDialog.py\
QuickReports.py\
RecentFiles.py\
Relationship.py\
Reorder.py\

View File

@ -329,6 +329,7 @@ class ManagedWindow:
window_key = self.build_window_key(obj)
menu_label, submenu_label = self.build_menu_names(obj)
self._gladeobj = None
self.isWindow = None
if uistate.gwm.get_item_from_id(window_key):
uistate.gwm.get_item_from_id(window_key).present()

View File

@ -50,10 +50,11 @@ from ReportBase import MODE_GUI, MODE_CLI, MODE_BKI, book_categories
# Global lists
#
#-------------------------------------------------------------------------
report_list = []
tool_list = []
import_list = []
export_list = []
report_list = []
quick_report_list = []
tool_list = []
import_list = []
export_list = []
attempt_list = []
loaddir_list = []
textdoc_list = []
@ -61,7 +62,7 @@ bookdoc_list = []
drawdoc_list = []
failmsg_list = []
bkitems_list = []
cl_list = []
cl_list = []
cli_tool_list = []
success_list = []
@ -410,47 +411,89 @@ def register_draw_doc(name,classref,paper,style, ext,
print_report_label, clname))
mod2text[classref.__module__] = name
#-------------------------------------------------------------------------
#
# Quick Report registration
#
#-------------------------------------------------------------------------
def register_quick_report(
name,
category,
run_func,
translated_name,
status=_("Unknown"),
description=_unavailable,
author_name=_("Unknown"),
author_email=_("Unknown"),
unsupported=False,
):
"""
Registers quick report for all possible objects.
This function should be used to register a quick report
so it appears in the quick report context menu of the object it is
attached to.
The low-level functions (starting with '_') should not be used
on their own. Instead, this function will call them as needed.
"""
"""Register a report with the plugin system"""
global quick_report_list
del_index = -1
for i in range(0,len(quick_report_list)):
val = quick_report_list[i]
if val[3] == name:
del_index = i
if del_index != -1:
del quick_report_list[del_index]
quick_report_list.append((run_func, translated_name,
category, name, description, status,
author_name, author_email, unsupported))
mod2text[run_func.__module__] = description
#-------------------------------------------------------------------------
#
# Remove plugins whose reloading failed from the already-registered lists
#
#-------------------------------------------------------------------------
def purge_failed(failed_list,export_list,import_list,tool_list,cli_tool_list,
report_list,bkitems_list,cl_list,textdoc_list,bookdoc_list,
drawdoc_list):
def purge_failed():
global report_list, quick_report_list, tool_list, import_list, export_list,\
textdoc_list, bookdoc_list, drawdoc_list, bkitems_list, cl_list, \
cli_tool_list, failmsg_list
failed_module_names = [
os.path.splitext(os.path.basename(filename))[0]
for filename,junk in failed_list
for filename,junk in failmsg_list
]
export_list = [ item for item in export_list
if item[0].__module__ not in failed_module_names ]
import_list = [ item for item in import_list
if item[0].__module__ not in failed_module_names ]
tool_list = [ item for item in tool_list
if item[0].__module__ not in failed_module_names ]
cli_tool_list = [ item for item in cli_tool_list
if item[2].__module__ not in failed_module_names ]
report_list = [ item for item in report_list
if item[0].__module__ not in failed_module_names ]
bkitems_list = [ item for item in bkitems_list
if item[2].__module__ not in failed_module_names ]
cl_list = [ item for item in cl_list
if item[2].__module__ not in failed_module_names ]
textdoc_list = [ item for item in textdoc_list
if item[1].__module__ not in failed_module_names ]
bookdoc_list = [ item for item in bookdoc_list
if item[1].__module__ not in failed_module_names ]
drawdoc_list = [ item for item in drawdoc_list
if item[1].__module__ not in failed_module_names ]
#although these are global variables, we may not change the pointer of
# the list, as __init__.py already contains these, see comment there
export_list[:] = [ item for item in export_list
if item[0].__module__ not in failed_module_names ][:]
import_list[:] = [ item for item in import_list
if item[0].__module__ not in failed_module_names ][:]
tool_list[:] = [ item for item in tool_list
if item[0].__module__ not in failed_module_names ][:]
cli_tool_list[:] = [ item for item in cli_tool_list
if item[2].__module__ not in failed_module_names ][:]
report_list[:] = [ item for item in report_list
if item[0].__module__ not in failed_module_names ][:]
quick_report_list[:] = [ item for item in quick_report_list
if item[0].__module__ not in failed_module_names ][:]
bkitems_list[:] = [ item for item in bkitems_list
if item[2].__module__ not in failed_module_names ][:]
cl_list[:] = [ item for item in cl_list
if item[2].__module__ not in failed_module_names ][:]
textdoc_list[:] = [ item for item in textdoc_list
if item[1].__module__ not in failed_module_names ][:]
bookdoc_list[:] = [ item for item in bookdoc_list
if item[1].__module__ not in failed_module_names ][:]
drawdoc_list[:] = [ item for item in drawdoc_list
if item[1].__module__ not in failed_module_names ][:]
# For some funky reason this module's global variables
# are not seen inside this function. But they are seen
# from other modules, so we pass them back and forth.
# Sucks, but I don't know why this happens :-(
return (export_list,import_list,tool_list,cli_tool_list,
report_list,bkitems_list,cl_list,textdoc_list,bookdoc_list,
drawdoc_list)
#-------------------------------------------------------------------------
#

View File

@ -338,27 +338,7 @@ class Reload(_Tool.Tool):
# Remove previously good plugins that are now bad
# from the registered lists
(_PluginMgr.export_list,
_PluginMgr.import_list,
_PluginMgr.tool_list,
_PluginMgr.cli_tool_list,
_PluginMgr.report_list,
_PluginMgr.bkitems_list,
_PluginMgr.cl_list,
_PluginMgr.textdoc_list,
_PluginMgr.bookdoc_list,
_PluginMgr.drawdoc_list) = _PluginMgr.purge_failed(
_PluginMgr.failmsg_list,
_PluginMgr.export_list,
_PluginMgr.import_list,
_PluginMgr.tool_list,
_PluginMgr.cli_tool_list,
_PluginMgr.report_list,
_PluginMgr.bkitems_list,
_PluginMgr.cl_list,
_PluginMgr.textdoc_list,
_PluginMgr.bookdoc_list,
_PluginMgr.drawdoc_list)
_PluginMgr.purge_failed()
# attempt to load the plugins that have failed in the past
for (filename,message) in oldfailmsg:

View File

@ -20,6 +20,13 @@
# $Id: Report.py 6044 2006-03-03 00:10:52Z rshura $
#The following is bad, we import lists here, and obtain pointers to them
#If in _PluginMgr the list changes, that is ok, if however the list is
#assigned to another pointer eg export_list = then in this module we
#still retain the old pointer! ==> all actions may not change the pointer
#Better would be to do: import _PluginMgr as PluginMgr and then access
# the list as PluginUtils.PluginMgr, or use a function that returns the pointer
# of the list.
from _PluginMgr import \
register_export, register_import, \
register_tool, register_report, \
@ -27,8 +34,9 @@ from _PluginMgr import \
textdoc_list, drawdoc_list, bookdoc_list, \
bkitems_list, cl_list, cli_tool_list, \
load_plugins, import_list, export_list,\
report_list, tool_list, \
register_text_doc, register_draw_doc, register_book_doc
report_list, quick_report_list, tool_list, \
register_text_doc, register_draw_doc, register_book_doc,\
register_quick_report
import _Tool as Tool
import _Plugins as Plugins

137
src/QuickReports.py Normal file
View File

@ -0,0 +1,137 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2007 B. Malengier
#
# 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: QuickReports.py 8857 2007-08-23 11:58:36Z bmcage $
"""
This module provides the functions to build the quick report context menu's
"""
__author__ = "B. Malengier"
__revision__ = "$Revision: 8857 $"
#------------------------------------------------------------------------
#
# python modules
#
#------------------------------------------------------------------------
from gettext import gettext as _
from cStringIO import StringIO
#------------------------------------------------------------------------
#
# Set up logging
#
#------------------------------------------------------------------------
import logging
log = logging.getLogger(".QuickReports")
#-------------------------------------------------------------------------
#
# GNOME modules
#
#-------------------------------------------------------------------------
import gtk
#-------------------------------------------------------------------------
#
# GRAMPS modules
#
#-------------------------------------------------------------------------
from PluginUtils import Plugins
from ReportBase import CATEGORY_QR_PERSON, CATEGORY_QR_FAMILY,\
CATEGORY_QR_EVENT, CATEGORY_QR_SOURCE,\
CATEGORY_QR_PLACE, CATEGORY_QR_REPOSITORY
def create_quickreport_menu(category,dbstate,handle) :
#import present version of the
from PluginUtils import quick_report_list
''' This functions querries the registered quick reports with
quick_report_list of _PluginMgr.py
It collects the reports of the requested category, which must be one of
CATEGORY_QR_PERSON, CATEGORY_QR_FAMILY,
CATEGORY_QR_EVENT, CATEGORY_QR_SOURCE,
CATEGORY_QR_PLACE, CATEGORY_QR_REPOSITORY
It constructs the ui string of the quick report menu, and it's actions
The action callback function is constructed, using the dbstate and the
handle as input method.
A tuple is returned, containing the ui string of the quick report menu,
and its associated actions
'''
actions = []
ofile = StringIO()
ofile.write('<menu action="QuickReport">')
actions.append(('QuickReport', None, _("Quick Report"), None, None, None))
menu = gtk.Menu()
menu.show()
#select the reports to show
showlst = []
for item in quick_report_list:
if not item[8] and item[2] == category :
#add tuple function, translated name, name, status
showlst.append((item[0], item[1], item[3], item[5]))
showlst.sort(by_menu_name)
for report in showlst:
new_key = report[2].replace(' ', '-')
ofile.write('<menuitem action="%s"/>' % new_key)
actions.append((new_key, None, report[1], None, None,
make_quick_report_callback(report, category, dbstate, handle)))
ofile.write('</menu>')
return (ofile.getvalue(), actions)
def by_menu_name(first, second):
return cmp(first[1], second[1])
def make_quick_report_callback(lst, category, dbstate, handle):
return lambda x: run_report(dbstate, category, handle, lst[0])
def run_report(dbstate, category,handle,func):
from TextBufDoc import TextBufDoc
from Simple import make_basic_stylesheet
if dbstate.active and handle:
d = TextBufDoc(make_basic_stylesheet(), None, None)
if category == CATEGORY_QR_PERSON :
obj = dbstate.db.get_person_from_handle(handle)
elif category == CATEGORY_QR_FAMILY :
obj = dbstate.db.get_family_from_handle(handle)
elif category == CATEGORY_QR_EVENT :
obj = dbstate.db.get_event_from_handle(handle)
elif category == CATEGORY_QR_SOURCE :
obj = self.dbstate.db.get_source_from_handle(handle)
elif category == CATEGORY_QR_PLACE :
obj = dbstate.db.get_place_from_handle(handle)
elif category == CATEGORY_QR_REPOSITORY :
obj = dbstate.db.get_repository_from_handle(handle)
else :
obj = None
if obj :
d.open("")
func(dbstate.db, d, obj)
d.close()

View File

@ -61,3 +61,11 @@ book_categories = {
CATEGORY_TEXT : _("Text"),
CATEGORY_DRAW : _("Graphics"),
}
# Quick Report categories
CATEGORY_QR_PERSON = 0
CATEGORY_QR_FAMILY = 1
CATEGORY_QR_EVENT = 2
CATEGORY_QR_SOURCE = 3
CATEGORY_QR_PLACE = 4
CATEGORY_QR_REPOSITORY = 5

View File

@ -25,6 +25,8 @@ Display a person's events, both personal and family
from Simple import SimpleAccess, by_date, SimpleDoc
from gettext import gettext as _
from PluginUtils import register_quick_report
from ReportBase import CATEGORY_QR_PERSON
# define the formatting string once as a constant. Since this is reused
@ -62,3 +64,19 @@ def run(database, document, person):
sdoc.paragraph(__FMT % (sdb.event_type(event),
sdb.event_date(event),
sdb.event_place(event)))
#------------------------------------------------------------------------
#
#
#
#------------------------------------------------------------------------
register_quick_report(
name = 'all_events',
category = CATEGORY_QR_PERSON,
run_func = run,
translated_name = _("All Events"),
status = _("Stable"),
description= _("Display a person's events, both personal and family."),
author_name="Donald N. Allingham",
author_email="don@gramps-project.org"
)

View File

@ -25,6 +25,8 @@ Display a person's siblings in a report window
from Simple import SimpleAccess, SimpleDoc
from gettext import gettext as _
from PluginUtils import register_quick_report
from ReportBase import CATEGORY_QR_PERSON
# define the formatting string once as a constant. Since this is reused
@ -65,3 +67,18 @@ def run(database, document, person):
sdb.gender(child),
sdb.birth_date(child)))
#------------------------------------------------------------------------
#
#
#
#------------------------------------------------------------------------
register_quick_report(
name = 'siblings',
category = CATEGORY_QR_PERSON,
run_func = run,
translated_name = _("Siblings"),
status = _("Stable"),
description= _("Display a person's siblings."),
author_name="Donald N. Allingham",
author_email="don@gramps-project.org"
)