diff --git a/src/PluginUtils/_PluginMgr.py b/src/PluginUtils/_PluginMgr.py index 00a68224c..c36407613 100644 --- a/src/PluginUtils/_PluginMgr.py +++ b/src/PluginUtils/_PluginMgr.py @@ -95,7 +95,7 @@ def load_plugins(direct): responsible for registering itself in the correct manner. No attempt is done in this routine to register the tasks. Returns True on error. """ - global success_list,attempt_list,loaddir_list,failmsg_list + global success_list, attempt_list, loaddir_list, failmsg_list # if the directory does not exist, do nothing if not os.path.isdir(direct): @@ -132,6 +132,71 @@ def load_plugins(direct): return len(failmsg_list) != 0 # return True if there are errors +#------------------------------------------------------------------------- +# +# reload_plugins +# +#------------------------------------------------------------------------- +def reload_plugins(): + """ Reload previously loaded plugins """ + global success_list, attempt_list, loaddir_list, failmsg_list + + pymod = re.compile(r"^(.*)\.py$") + + oldfailmsg = failmsg_list[:] + failmsg_list = [] + + # attempt to reload all plugins that have succeeded in the past + for plugin in success_list: + filename = plugin[0] + filename = filename.replace('pyc','py') + filename = filename.replace('pyo','py') + try: + reload(plugin[1]) + except: + failmsg_list.append((filename, sys.exc_info())) + + # Remove previously good plugins that are now bad + # from the registered lists + purge_failed() + + # 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 + attempt_list.append(filename) + plugin = match.groups()[0] + try: + # 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) + reload(a) + success_list.append((filename, a)) + except: + failmsg_list.append((filename, sys.exc_info())) + + # attempt to load any new files found + for directory in loaddir_list: + for filename in os.listdir(directory): + name = os.path.split(filename) + match = pymod.match(name[1]) + if not match: + continue + if filename in attempt_list: + continue + attempt_list.append(filename) + plugin = match.groups()[0] + try: + a = __import__(plugin) + if a not in [plugin[1] + for plugin in success_list]: + success_list.append((filename, a)) + except: + failmsg_list.append((filename, sys.exc_info())) + #------------------------------------------------------------------------- # # Plugin registering diff --git a/src/PluginUtils/_PluginWindows.py b/src/PluginUtils/_PluginWindows.py index c3c8df732..49e9bf4be 100644 --- a/src/PluginUtils/_PluginWindows.py +++ b/src/PluginUtils/_PluginWindows.py @@ -55,16 +55,17 @@ import _Tool as Tool class PluginStatus(ManagedWindow.ManagedWindow): """Displays a dialog showing the status of loaded plugins""" - def __init__(self, state, uistate, track=[]): - + def __init__(self, uistate, track=[]): + self.__uistate = uistate self.title = _("Plugin Status") - ManagedWindow.ManagedWindow.__init__(self,uistate,track,self.__class__) + ManagedWindow.ManagedWindow.__init__(self, uistate, track, + self.__class__) - self.set_window(gtk.Dialog("",uistate.window, + self.set_window(gtk.Dialog("", uistate.window, gtk.DIALOG_DESTROY_WITH_PARENT, (gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE)), None, self.title) - self.window.set_size_request(600,400) + self.window.set_size_request(600, 400) self.window.connect('response', self.close) scrolled_window = gtk.ScrolledWindow() @@ -88,8 +89,19 @@ class PluginStatus(ManagedWindow.ManagedWindow): scrolled_window.add(self.list) self.window.vbox.add(scrolled_window) + + if __debug__: + # Only show the "Reload" button when in debug mode + # (without -O on the command line) + self.__reload_btn = gtk.Button("Reload") + self.window.action_area.add(self.__reload_btn) + self.__reload_btn.connect('clicked', self.__reload) + self.window.show_all() + self.__populate_list() + def __populate_list(self): + """ Build the list of plugins """ for i in PluginMgr.failmsg_list: err = i[1][0] @@ -110,6 +122,7 @@ class PluginStatus(ManagedWindow.ManagedWindow): i[0], descr, None]) def button_press(self, obj, event): + """ Callback function from the user clicking on a line """ if event.type == gtk.gdk._2BUTTON_PRESS and event.button == 1: model, node = self.selection.get_selected() data = model.get_value(node, 3) @@ -118,8 +131,16 @@ class PluginStatus(ManagedWindow.ManagedWindow): PluginTrace(self.uistate, self.track, data, name) def build_menu_names(self, obj): - return ( _('Summary'),self.title) - + return (self.title, "") + + def __reload(self, obj): + """ Callback function from the "Reload" button """ + PluginMgr.reload_plugins() + self.__uistate.emit('plugins-reloaded', + (PluginMgr.tool_list, PluginMgr.report_list)) + self.model.clear() + self.__populate_list() + #------------------------------------------------------------------------- # # Details for an individual plugin that failed @@ -133,15 +154,15 @@ class PluginTrace(ManagedWindow.ManagedWindow): title = "%s: %s" % (_("Plugin Status"), name) ManagedWindow.ManagedWindow.__init__(self, uistate, track, self) - self.set_window(gtk.Dialog("",uistate.window, + self.set_window(gtk.Dialog("", uistate.window, gtk.DIALOG_DESTROY_WITH_PARENT, (gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE)), None, title) - self.window.set_size_request(600,400) + self.window.set_size_request(600, 400) self.window.connect('response', self.close) scrolled_window = gtk.ScrolledWindow() - scrolled_window.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC) + scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.text = gtk.TextView() scrolled_window.add(self.text) self.text.get_buffer().set_text( @@ -189,18 +210,18 @@ class ToolManagedWindowBase(ManagedWindow.ManagedWindow): self.style_button = None window = gtk.Dialog('Tool') - self.set_window(window,None,self.get_title()) + self.set_window(window, None, self.get_title()) #self.window.set_has_separator(False) #self.window.connect('response', self.close) self.cancel = self.window.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_CANCEL) - self.cancel.connect('clicked',self.close) + self.cancel.connect('clicked', self.close) self.ok = self.window.add_button(gtk.STOCK_APPLY, gtk.RESPONSE_OK) - self.ok.connect('clicked',self.on_ok_clicked) + self.ok.connect('clicked', self.on_ok_clicked) - self.window.set_default_size(600,-1) + self.window.set_default_size(600, -1) # Set up and run the dialog. These calls are not in top down # order when looking at the dialog box as there is some @@ -208,7 +229,7 @@ class ToolManagedWindowBase(ManagedWindow.ManagedWindow): self.setup_title() self.setup_header() - self.tbl = gtk.Table(4,4,False) + self.tbl = gtk.Table(4, 4, False) self.tbl.set_col_spacings(12) self.tbl.set_row_spacings(6) self.tbl.set_border_width(6) @@ -241,7 +262,7 @@ class ToolManagedWindowBase(ManagedWindow.ManagedWindow): # Callback functions from the dialog # #------------------------------------------------------------------------ - def on_cancel(self,*obj): + def on_cancel(self, *obj): pass # cancel just closes def on_ok_clicked(self, obj): @@ -266,7 +287,8 @@ class ToolManagedWindowBase(ManagedWindow.ManagedWindow): iter = view.get_iter_at_location(*buffer_location) for (tag, person_handle) in self.tags: if iter.has_tag(tag): - view.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(self.link_cursor) + _window = view.get_window(gtk.TEXT_WINDOW_TEXT) + _window.set_cursor(self.link_cursor) return False # handle event further, if necessary view.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(self.standard_cursor) return False # handle event further, if necessary @@ -299,7 +321,7 @@ class ToolManagedWindowBase(ManagedWindow.ManagedWindow): self.results_write(text) start = buffer.get_iter_at_offset(offset) end = buffer.get_end_iter() - self.tags.append((LinkTag(person_handle, buffer),person_handle)) + self.tags.append((LinkTag(person_handle, buffer), person_handle)) buffer.apply_tag(self.tags[-1][0], start, end) def results_write(self, text): @@ -366,7 +388,7 @@ class ToolManagedWindowBase(ManagedWindow.ManagedWindow): label.set_use_markup(True) self.window.vbox.pack_start(label, True, True, self.border_pad) - def add_frame_option(self,frame_name,label_text,widget,tooltip=None): + def add_frame_option(self, frame_name, label_text, widget, tooltip=None): """Similar to add_option this method takes a frame_name, a text string and a Gtk Widget. When the interface is built, all widgets with the same frame_name are grouped into a @@ -377,12 +399,12 @@ class ToolManagedWindowBase(ManagedWindow.ManagedWindow): the add_user_options task.""" if self.frames.has_key(frame_name): - self.frames[frame_name].append((label_text,widget)) + self.frames[frame_name].append((label_text, widget)) else: - self.frames[frame_name] = [(label_text,widget)] + self.frames[frame_name] = [(label_text, widget)] self.frame_names.append(frame_name) if tooltip: - self.add_tooltip(widget,tooltip) + self.add_tooltip(widget, tooltip) def set_current_frame(self, name): if name == None: @@ -396,7 +418,7 @@ class ToolManagedWindowBase(ManagedWindow.ManagedWindow): self.notebook.set_current_page(page) return - def add_results_frame(self,frame_name="Results"): + def add_results_frame(self, frame_name="Results"): if frame_name not in self.frames: window = gtk.ScrolledWindow() window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) @@ -423,18 +445,18 @@ class ToolManagedWindowBase(ManagedWindow.ManagedWindow): the add_user_options task.""" for key in self.frame_names: flist = self.frames[key] - table = gtk.Table(3,len(flist)) + table = gtk.Table(3, len(flist)) table.set_col_spacings(12) table.set_row_spacings(6) table.set_border_width(6) l = gtk.Label("%s" % key) l.set_use_markup(True) - self.notebook.append_page(table,l) + self.notebook.append_page(table, l) row = 0 - for (text,widget) in flist: + for (text, widget) in flist: if text: text_widget = gtk.Label('%s:' % text) - text_widget.set_alignment(0.0,0.5) + text_widget.set_alignment(0.0, 0.5) table.attach(text_widget, 1, 2, row, row+1, gtk.SHRINK|gtk.FILL, gtk.SHRINK) table.attach(widget, 2, 3, row, row+1, @@ -467,7 +489,7 @@ class ToolManagedWindowBatch(Tool.BatchTool, ToolManagedWindowBase): # This constructor will ask a question, set self.fail: self.dbstate = dbstate self.uistate = uistate - Tool.BatchTool.__init__(self,dbstate, options_class, name) + Tool.BatchTool.__init__(self, dbstate, options_class, name) if not self.fail: ToolManagedWindowBase.__init__(self, dbstate, uistate, options_class, name, callback) @@ -476,6 +498,6 @@ class ToolManagedWindow(Tool.Tool, ToolManagedWindowBase): def __init__(self, dbstate, uistate, options_class, name, callback=None): self.dbstate = dbstate self.uistate = uistate - Tool.Tool.__init__(self,dbstate, options_class, name) + Tool.Tool.__init__(self, dbstate, options_class, name) ToolManagedWindowBase.__init__(self, dbstate, uistate, options_class, name, callback) diff --git a/src/PluginUtils/_Plugins.py b/src/PluginUtils/_Plugins.py index 092fda5f5..fda833398 100644 --- a/src/PluginUtils/_Plugins.py +++ b/src/PluginUtils/_Plugins.py @@ -41,9 +41,6 @@ from gtk import glade # Standard Python modules # #------------------------------------------------------------------------- -import os -import sys -import re from gettext import gettext as _ #------------------------------------------------------------------------- @@ -52,12 +49,9 @@ from gettext import gettext as _ # #------------------------------------------------------------------------- import const -import Config -import Errors from ReportBase import report, standalone_categories import _Tool import _PluginMgr -import _PluginWindows import ManagedWindow #------------------------------------------------------------------------- @@ -74,13 +68,12 @@ UNSUPPORTED = _("Unsupported") # PluginDialog interface class # #------------------------------------------------------------------------- - class PluginDialog(ManagedWindow.ManagedWindow): """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): + def __init__(self, state, uistate, track, item_list, categories, msg, + label=None, button_label=None, tool_tip=None, content=REPORTS): """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.""" @@ -90,12 +83,12 @@ class PluginDialog(ManagedWindow.ManagedWindow): self.msg = msg self.content = content - ManagedWindow.ManagedWindow.__init__(self,uistate,[],self.__class__) + ManagedWindow.ManagedWindow.__init__(self, uistate, [], self.__class__) self.state = state self.uistate = uistate - self.dialog = glade.XML(const.PLUGINS_GLADE,"report","gramps") + self.dialog = glade.XML(const.PLUGINS_GLADE, "report", "gramps") self.dialog.signal_autoconnect({ "on_report_apply_clicked" : self.on_apply_clicked, "destroy_passed_object" : self.close, @@ -110,7 +103,7 @@ class PluginDialog(ManagedWindow.ManagedWindow): 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) + col = gtk.TreeViewColumn('', gtk.CellRendererText(), text=0) self.tree.append_column(col) self.tree.set_model(self.store) @@ -130,34 +123,35 @@ class PluginDialog(ManagedWindow.ManagedWindow): 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) + tttips = gtk.tooltips_data_get(self.apply_button) + if tttips: + tttips[0].set_tip(self.apply_button, tool_tip) except AttributeError: pass self.item = None - self.build_plugin_tree(item_list,categories) - uistate.connect('plugins-reloaded',self.rebuild) + self.build_plugin_tree(item_list, categories) + uistate.connect('plugins-reloaded', self.rebuild) self.show() - def rebuild(self,tool_list,report_list): + 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) + return (self.msg, None) def on_apply_clicked(self, obj): """Execute the selected report""" try: - (item_class, options_class,title,category, - name,require_active) = self.item + (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) + 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, + _Tool.gui_tool(self.state, self.uistate, item_class, options_class,title, name,category, self.state.db.request_rebuild) except TypeError: @@ -174,7 +168,7 @@ class PluginDialog(ManagedWindow.ManagedWindow): return data = self.imap[path] - (report_class, options_class,title,category, name, + (report_class, options_class, title, category, name, doc,status,author,email,unsupported,require_active) = data self.description.set_text(doc) if unsupported: @@ -185,10 +179,10 @@ class PluginDialog(ManagedWindow.ManagedWindow): self.title.set_use_markup(1) self.author_name.set_text(author) self.author_email.set_text(email) - self.item = (report_class, options_class,title,category, - name,require_active) + self.item = (report_class, options_class, title, category, + name, require_active) - def build_plugin_tree(self,item_list,categories): + def build_plugin_tree(self, item_list, categories): """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: @@ -225,25 +219,25 @@ class PluginDialog(ManagedWindow.ManagedWindow): if item_hash.has_key(UNSUPPORTED): key = UNSUPPORTED data = item_hash[key] - node = self.store.insert_after(None,prev) - self.store.set(node,0,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])) + 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]) + ilist.append((next, item)) + 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) + 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])) + 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: + 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 @@ -256,7 +250,7 @@ class ReportPlugins(PluginDialog): """Displays the dialog box that allows the user to select the report that is desired.""" - def __init__(self,dbstate,uistate,track): + def __init__(self, dbstate, uistate, track): """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.""" @@ -273,8 +267,8 @@ class ReportPlugins(PluginDialog): _("_Generate"), _("Generate selected report"), REPORTS) - def rebuild(self,tool_list,report_list): - self.build_plugin_tree(report_list,standalone_categories) + def rebuild(self, tool_list, report_list): + self.build_plugin_tree(report_list, standalone_categories) #------------------------------------------------------------------------- # @@ -288,7 +282,7 @@ class ToolPlugins(PluginDialog): __signals__ = { 'plugins-reloaded' : (list,list), } - def __init__(self,dbstate,uistate,track): + def __init__(self, dbstate, uistate, track): """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.""" @@ -306,117 +300,5 @@ class ToolPlugins(PluginDialog): _("Run selected tool"), TOOLS) - def rebuild(self,tool_list,report_list): - self.build_plugin_tree(tool_list,_Tool.tool_categories) - -#------------------------------------------------------------------------- -# -# Reload plugins -# -#------------------------------------------------------------------------- -class Reload(_Tool.Tool): - def __init__(self, dbstate, uistate, options_class, name, callback=None): - """ - 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) - - pymod = re.compile(r"^(.*)\.py$") - - oldfailmsg = _PluginMgr.failmsg_list[:] - _PluginMgr.failmsg_list = [] - - # attempt to reload all plugins that have succeeded in the past - for plugin in _PluginMgr.success_list: - filename = plugin[0] - filename = filename.replace('pyc','py') - filename = filename.replace('pyo','py') - try: - reload(plugin[1]) - except: - _PluginMgr.failmsg_list.append((filename,sys.exc_info())) - - # Remove previously good plugins that are now bad - # from the registered lists - _PluginMgr.purge_failed() - - # 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: - # 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) - reload(a) - _PluginMgr.success_list.append((filename,a)) - except: - _PluginMgr.failmsg_list.append((filename,sys.exc_info())) - - # attempt to load any new files found - for directory in _PluginMgr.loaddir_list: - 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: - continue - _PluginMgr.attempt_list.append(filename) - 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)) - except: - _PluginMgr.failmsg_list.append((filename,sys.exc_info())) - - if Config.get(Config.POP_PLUGIN_STATUS) \ - and len(_PluginMgr.failmsg_list): - try: - _PluginWindows.PluginStatus(dbstate,uistate) - except Errors.WindowActiveError: - old_win = uistate.gwm.get_item_from_id( - _PluginWindows.PluginStatus) - old_win.close() - _PluginWindows.PluginStatus(dbstate,uistate) - - # Emit signal to re-generate tool and report menus - uistate.emit('plugins-reloaded', - (_PluginMgr.tool_list,_PluginMgr.report_list)) - -class ReloadOptions(_Tool.ToolOptions): - """ - 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 -# -#------------------------------------------------------------------------- - -if __debug__: - _PluginMgr.register_tool( - name = 'reload', - category = _Tool.TOOL_DEBUG, - tool_class = Reload, - options_class = ReloadOptions, - modes = _Tool.MODE_GUI, - translated_name = _("Reload Plugins"), - status=(_("Stable")), - author_name = "Donald N. Allingham", - author_email = "don@gramps-project.org", - description=_("Attempt to reload plugins. " - "Note: This tool itself is not reloaded!"), - ) + def rebuild(self, tool_list, report_list): + self.build_plugin_tree(tool_list, _Tool.tool_categories) diff --git a/src/ViewManager.py b/src/ViewManager.py index a7e0d1bce..bcbea3996 100644 --- a/src/ViewManager.py +++ b/src/ViewManager.py @@ -402,7 +402,7 @@ class ViewManager: ('About', gtk.STOCK_ABOUT, _('_About'), None, None, display_about_box), ('PluginStatus', None, _('_Plugin Status'), None, None, - self.plugin_status), + self.__plugin_status), ('FAQ', None, _('_FAQ'), None, None, faq_activate), ('KeyBindings', None, _('_Key Bindings'), None, None, key_bindings), ('UserManual', gtk.STOCK_HELP, _('_User Manual'), 'F1', None, @@ -607,14 +607,8 @@ class ViewManager: error |= load_plugins(const.USER_PLUGINS) # get to ssee if we need to open the plugin status window - if Config.get(Config.POP_PLUGIN_STATUS) and error: - try: - PluginWindows.PluginStatus(self.state, self.uistate, []) - except Errors.WindowActiveError: - old_win = self.uistate.gwm.get_item_from_id( - PluginWindows.PluginStatus) - old_win.close() - PluginWindows.PluginStatus(self.state, self.uistate, []) + if error and Config.get(Config.POP_PLUGIN_STATUS): + self.__plugin_status() self.uistate.push_message(self.state, _('Ready')) @@ -734,17 +728,17 @@ class ViewManager: import TipOfDay TipOfDay.TipOfDay(self.uistate) - def plugin_status(self, obj): + def __plugin_status(self, obj=None): """ Display plugin status dialog """ try: - PluginWindows.PluginStatus(self.state, self.uistate, []) + PluginWindows.PluginStatus(self.uistate, []) except Errors.WindowActiveError: old_win = self.uistate.gwm.get_item_from_id( PluginWindows.PluginStatus) old_win.close() - PluginWindows.PluginStatus(self.state, self.uistate, []) + PluginWindows.PluginStatus(self.uistate, []) def sidebar_toggle(self, obj): """