gramps/src/PluginUtils/_Plugins.py

419 lines
15 KiB
Python
Raw Normal View History

2002-10-20 19:55:16 +05:30
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2006 Donald N. Allingham
2002-10-20 19:55:16 +05:30
#
# 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$
2002-10-20 19:55:16 +05:30
"""
The core of the GRAMPS plugin system. This module provides tasks to load
plugins from specfied directories, build menus for the different categories,
and provide dialog to select and execute plugins.
Plugins are divided into several categories. This are: reports, tools,
2004-05-07 09:28:26 +05:30
importers, exporters, and document generators.
2002-10-20 19:55:16 +05:30
"""
#-------------------------------------------------------------------------
#
# GTK libraries
#
#-------------------------------------------------------------------------
import gtk
#-------------------------------------------------------------------------
#
# Standard Python modules
#
#-------------------------------------------------------------------------
import os
import sys
import re
from gettext import gettext as _
2002-10-20 19:55:16 +05:30
#-------------------------------------------------------------------------
#
# GRAMPS modules
#
#-------------------------------------------------------------------------
import const
2006-03-03 05:40:52 +05:30
import Config
import Errors
from ReportBase import report, standalone_categories
import _Tool
import _PluginMgr
import _PluginStatus
2006-04-01 09:29:42 +05:30
import ManagedWindow
2005-12-06 12:08:09 +05:30
#-------------------------------------------------------------------------
#
# Constants
#
#-------------------------------------------------------------------------
REPORTS = 0
TOOLS = 1
UNSUPPORTED = _("Unsupported")
2002-10-20 19:55:16 +05:30
#-------------------------------------------------------------------------
#
# PluginDialog interface class
#
#-------------------------------------------------------------------------
2006-04-01 09:29:42 +05:30
class PluginDialog(ManagedWindow.ManagedWindow):
2002-10-20 19:55:16 +05:30
"""Displays the dialog box that allows the user to select the
report that is desired."""
def __init__(self,state, uistate, track, item_list,categories,msg,
label=None,button_label=None,tool_tip=None,content=REPORTS):
2002-10-20 19:55:16 +05:30
"""Display the dialog box, and build up the list of available
reports. This is used to build the selection tree on the left
hand side of the dailog box."""
self.active = state.active
self.imap = {}
2004-02-28 09:21:59 +05:30
self.msg = msg
2005-12-06 12:08:09 +05:30
self.content = content
ManagedWindow.ManagedWindow.__init__(self,uistate,[],self.__class__)
* src/AddrEdit.py: remove already_exist check in favor of exception * src/AttrEdit.py: remove already_exist check in favor of exception * src/DisplayState.py: remove already_exist variable * src/DisplayTabs.py: handle exception * src/EditEventRef.py: remove already_exist check in favor of exception * src/EditFamily.py: update to derive from EditPrimary * src/EditMedia.py: update to derive from EditPrimary * src/EditMediaRef.py: remove already_exist check in favor of exception * src/EditPerson.py: update to derive from EditPrimary * src/EditPlace.py: update to derive from EditPrimary * src/EditRepository.py:update to derive from EditPrimary * src/EditSource.py: update to derive from EditPrimary * src/EditSourceRef.py: remove already_exist check in favor of exception * src/Errors.py: new exception * src/EventEdit.py: update to derive from EditPrimary * src/EventView.py: catch exception of window already exists * src/FamilyList.py: catch exception of window already exists * src/FamilyView.py: catch exception of window already exists * src/GrampsWidgets.py: typos * src/NameEdit.py: remove already_exist check in favor of exception * src/PedView.py: catch exception of window already exists * src/PersonView.py: catch exception of window already exists * src/PlaceView.py: catch exception of window already exists * src/Plugins.py: catch exception of window already exists * src/UrlEdit.py: remove already_exist check in favor of exception * src/const.py.in: dynamically determine path * src/gramps.glade: name changes * src/gramps.py: set path svn: r6014
2006-03-01 10:38:11 +05:30
self.state = state
self.uistate = uistate
2002-10-20 19:55:16 +05:30
2006-03-05 04:23:46 +05:30
self.dialog = gtk.glade.XML(const.plugins_glade,"report","gramps")
2002-10-20 19:55:16 +05:30
self.dialog.signal_autoconnect({
"on_report_apply_clicked" : self.on_apply_clicked,
"destroy_passed_object" : self.close,
2002-10-20 19:55:16 +05:30
})
self.tree = self.dialog.get_widget("tree")
window = self.dialog.get_widget("report")
2003-03-06 11:42:51 +05:30
self.title = self.dialog.get_widget("title")
self.set_window(window, self.title, msg )
2003-03-06 11:42:51 +05:30
self.store = gtk.TreeStore(str)
self.selection = self.tree.get_selection()
self.selection.connect('changed', self.on_node_selected)
col = gtk.TreeViewColumn('',gtk.CellRendererText(),text=0)
self.tree.append_column(col)
self.tree.set_model(self.store)
2002-10-20 19:55:16 +05:30
self.description = self.dialog.get_widget("description")
if label:
self.description.set_text(label)
2002-10-20 19:55:16 +05:30
self.status = self.dialog.get_widget("report_status")
2003-03-05 11:31:31 +05:30
2003-02-08 22:58:41 +05:30
self.author_name = self.dialog.get_widget("author_name")
self.author_email = self.dialog.get_widget("author_email")
2005-12-06 12:08:09 +05:30
self.apply_button = self.dialog.get_widget("apply")
if button_label:
self.apply_button.set_label(button_label)
else:
self.apply_button.set_label(_("_Apply"))
self.apply_button.set_use_underline(True)
if tool_tip:
try:
tt = gtk.tooltips_data_get(self.apply_button)
if tt:
tt[0].set_tip(self.apply_button,tool_tip)
except AttributeError:
pass
2005-12-06 12:08:09 +05:30
self.item = None
self.build_plugin_tree(item_list,categories)
uistate.connect('plugins-reloaded',self.rebuild)
self.show()
2004-02-28 09:21:59 +05:30
def rebuild(self,tool_list,report_list):
# This method needs to be overridden in the subclass
assert False, "This method needs to be overridden in the subclass."
def build_menu_names(self,obj):
return (self.msg,None)
2002-10-20 19:55:16 +05:30
def on_apply_clicked(self,obj):
"""Execute the selected report"""
try:
(item_class,options_class,title,category,
name,require_active) = self.item
if self.content == REPORTS:
report(self.state,self.uistate,self.state.active,
item_class,options_class,title,name,category,require_active)
else:
_Tool.gui_tool(self.state,self.uistate,
item_class,options_class,title,name,category,
self.state.db.request_rebuild)
except TypeError:
pass # ignore pressing apply without a plugin being selected
def on_node_selected(self,obj):
2002-10-20 19:55:16 +05:30
"""Updates the informational display on the right hand side of
the dialog box with the description of the selected report"""
store,node = self.selection.get_selected()
if node:
path = store.get_path(node)
if not node or not self.imap.has_key(path):
return
data = self.imap[path]
2005-12-06 12:08:09 +05:30
(report_class,options_class,title,category,name,
doc,status,author,email,unsupported,require_active) = data
2002-10-20 19:55:16 +05:30
self.description.set_text(doc)
2005-12-06 12:08:09 +05:30
if unsupported:
status = UNSUPPORTED
2003-02-08 22:58:41 +05:30
self.status.set_text(status)
self.title.set_text('<span weight="bold" size="larger">%s</span>' \
% title)
self.title.set_use_markup(1)
2003-02-08 22:58:41 +05:30
self.author_name.set_text(author)
self.author_email.set_text(email)
self.item = (report_class,options_class,title,category,
name,require_active)
2002-10-20 19:55:16 +05:30
2005-12-06 12:08:09 +05:30
def build_plugin_tree(self,item_list,categories):
2002-10-20 19:55:16 +05:30
"""Populates a GtkTree with each menu item assocated with a entry
in the lists. The list must consist of a tuples with the following
format:
2005-12-06 12:08:09 +05:30
(item_class,options_class,title,category,name,
doc,status,author,email)
Items in the same category are grouped under the same submenu.
The categories must be dicts from integer to string.
"""
ilist = []
self.store.clear()
2002-10-20 19:55:16 +05:30
# build the tree items and group together based on the category name
item_hash = {}
2005-12-06 12:08:09 +05:30
for plugin in item_list:
if plugin[9]:
category = UNSUPPORTED
else:
category = categories[plugin[3]]
if item_hash.has_key(category):
item_hash[category].append(plugin)
2002-10-20 19:55:16 +05:30
else:
2005-12-06 12:08:09 +05:30
item_hash[category] = [plugin]
2002-10-20 19:55:16 +05:30
# add a submenu for each category, and populate it with the
# GtkTreeItems that are associated with it.
2005-12-06 12:08:09 +05:30
key_list = [ item for item in item_hash.keys() if item != UNSUPPORTED]
2002-10-20 19:55:16 +05:30
key_list.sort()
key_list.reverse()
2002-10-20 19:55:16 +05:30
prev = None
2005-12-06 12:08:09 +05:30
if item_hash.has_key(UNSUPPORTED):
key = UNSUPPORTED
2002-10-20 19:55:16 +05:30
data = item_hash[key]
node = self.store.insert_after(None,prev)
self.store.set(node,0,key)
2002-10-20 19:55:16 +05:30
next = None
2005-12-06 12:08:09 +05:30
data.sort(lambda x,y: cmp(x[2],y[2]))
2002-10-20 19:55:16 +05:30
for item in data:
next = self.store.insert_after(node,next)
ilist.append((next,item))
2005-12-06 12:08:09 +05:30
self.store.set(next,0,item[2])
for key in key_list:
data = item_hash[key]
node = self.store.insert_after(None,prev)
self.store.set(node,0,key)
next = None
data.sort(lambda x,y: cmp(x[2],y[2]))
for item in data:
next = self.store.insert_after(node,next)
ilist.append((next,item))
self.store.set(next,0,item[2])
for next,tab in ilist:
path = self.store.get_path(next)
self.imap[path] = tab
2002-10-20 19:55:16 +05:30
#-------------------------------------------------------------------------
#
# ReportPlugins interface class
#
#-------------------------------------------------------------------------
class ReportPlugins(PluginDialog):
"""Displays the dialog box that allows the user to select the
report that is desired."""
def __init__(self,dbstate,uistate,track):
2002-10-20 19:55:16 +05:30
"""Display the dialog box, and build up the list of available
reports. This is used to build the selection tree on the left
hand side of the dailog box."""
PluginDialog.__init__(
self,
dbstate,
uistate,
track,
_PluginMgr.report_list,
standalone_categories,
2005-12-06 12:08:09 +05:30
_("Report Selection"),
_("Select a report from those available on the left."),
2005-12-06 12:08:09 +05:30
_("_Generate"), _("Generate selected report"),
REPORTS)
def rebuild(self,tool_list,report_list):
self.build_plugin_tree(report_list,standalone_categories)
2002-10-20 19:55:16 +05:30
#-------------------------------------------------------------------------
#
# ToolPlugins interface class
#
#-------------------------------------------------------------------------
class ToolPlugins(PluginDialog):
"""Displays the dialog box that allows the user to select the tool
that is desired."""
__signals__ = {
'plugins-reloaded' : (list,list),
}
def __init__(self,dbstate,uistate,track):
2002-10-20 19:55:16 +05:30
"""Display the dialog box, and build up the list of available
reports. This is used to build the selection tree on the left
hand side of the dailog box."""
PluginDialog.__init__(
self,
dbstate,
uistate,
track,
_PluginMgr.tool_list,
_Tool.tool_categories,
_("Tool Selection"),
_("Select a tool from those available on the left."),
_("_Run"),
2005-12-06 12:08:09 +05:30
_("Run selected tool"),
TOOLS)
def rebuild(self,tool_list,report_list):
self.build_plugin_tree(tool_list,_Tool.tool_categories)
2002-10-20 19:55:16 +05:30
#-------------------------------------------------------------------------
#
2005-12-06 12:08:09 +05:30
# Reload plugins
#
#-------------------------------------------------------------------------
class Reload(_Tool.Tool):
def __init__(self, dbstate, uistate, options_class, name, callback=None):
2005-12-06 12:08:09 +05:30
"""
Treated as a callback, causes all plugins to get reloaded.
This is useful when writing and debugging a plugin.
"""
_Tool.Tool.__init__(self,dbstate,options_class,name)
2005-12-06 12:08:09 +05:30
pymod = re.compile(r"^(.*)\.py$")
oldfailmsg = _PluginMgr.failmsg_list[:]
_PluginMgr.failmsg_list = []
2005-12-06 12:08:09 +05:30
# attempt to reload all plugins that have succeeded in the past
for plugin in _PluginMgr.success_list:
filename = plugin[0]
2005-12-06 12:08:09 +05:30
filename = filename.replace('pyc','py')
filename = filename.replace('pyo','py')
try:
reload(plugin[1])
2005-12-06 12:08:09 +05:30
except:
_PluginMgr.failmsg_list.append((filename,sys.exc_info()))
2005-12-06 12:08:09 +05:30
# Remove previously good plugins that are now bad
# from the registered lists
_PluginMgr.purge_failed()
2005-12-06 12:08:09 +05:30
# attempt to load the plugins that have failed in the past
for (filename,message) in oldfailmsg:
name = os.path.split(filename)
match = pymod.match(name[1])
if not match:
continue
_PluginMgr.attempt_list.append(filename)
plugin = match.groups()[0]
try:
2005-12-06 12:08:09 +05:30
# For some strange reason second importing of a failed plugin
# results in success. Then reload reveals the actual error.
# Looks like a bug in Python.
a = __import__(plugin)
2005-12-06 12:08:09 +05:30
reload(a)
_PluginMgr.success_list.append((filename,a))
except:
_PluginMgr.failmsg_list.append((filename,sys.exc_info()))
2005-12-06 12:08:09 +05:30
# attempt to load any new files found
for directory in _PluginMgr.loaddir_list:
2005-12-06 12:08:09 +05:30
for filename in os.listdir(directory):
name = os.path.split(filename)
match = pymod.match(name[1])
if not match:
continue
if filename in _PluginMgr.attempt_list:
2005-12-06 12:08:09 +05:30
continue
_PluginMgr.attempt_list.append(filename)
2005-12-06 12:08:09 +05:30
plugin = match.groups()[0]
try:
a = __import__(plugin)
if a not in [plugin[1]
for plugin in _PluginMgr.success_list]:
_PluginMgr.success_list.append((filename,a))
2005-12-06 12:08:09 +05:30
except:
_PluginMgr.failmsg_list.append((filename,sys.exc_info()))
2005-12-06 12:08:09 +05:30
if Config.get(Config.POP_PLUGIN_STATUS) \
and len(_PluginMgr.failmsg_list):
try:
_PluginStatus.PluginStatus(dbstate,uistate)
except Errors.WindowActiveError:
old_win = uistate.gwm.get_item_from_id(
_PluginStatus.PluginStatus)
old_win.close()
_PluginStatus.PluginStatus(dbstate,uistate)
2005-12-06 12:08:09 +05:30
2006-12-20 12:53:10 +05:30
# Emit signal to re-generate tool and report menus
uistate.emit('plugins-reloaded',
(_PluginMgr.tool_list,_PluginMgr.report_list))
2005-12-06 12:08:09 +05:30
class ReloadOptions(_Tool.ToolOptions):
2005-12-06 12:08:09 +05:30
"""
Defines options and provides handling interface.
"""
def __init__(self,name,person_id=None):
_Tool.ToolOptions.__init__(self,name,person_id)
#-------------------------------------------------------------------------
#
# Register the plugin reloading tool
#
#-------------------------------------------------------------------------
2005-12-06 12:08:09 +05:30
if __debug__:
_PluginMgr.register_tool(
2005-12-06 12:08:09 +05:30
name = 'reload',
category = _Tool.TOOL_DEBUG,
2005-12-06 12:08:09 +05:30
tool_class = Reload,
options_class = ReloadOptions,
modes = _Tool.MODE_GUI,
2005-12-06 12:08:09 +05:30
translated_name = _("Reload plugins"),
description=_("Attempt to reload plugins. "
"Note: This tool itself is not reloaded!"),
)