From e127925948637812adfde977a6973b52f6968b1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Bili=C5=84ski?= Date: Sat, 7 Jun 2008 17:28:34 +0000 Subject: [PATCH] Added first version of 'Plugins' window. It's accessible through 'Edit/Plugins' item in roster menu. It seems that you can successfully (de)activate plug-ins through GUI now. Added 'homepage' attribute to Plugin class. Added (commented out) calls of pycallgraph in src/gajim.py for later use. [xbright] Changed 'python' to 'python2.5' because code uses modules not available in previous versions of Python. --- data/glade/plugins_window.glade | 602 ++++++++++++++++++++++++++++++++ data/glade/roster_window.glade | 15 + launch.sh | 2 +- plugins/length_notifier.py | 12 +- src/gajim.py | 7 + src/plugins/gui.py | 166 +++++++++ src/plugins/plugin.py | 17 +- src/plugins/pluginmanager.py | 32 +- src/roster_window.py | 8 + 9 files changed, 844 insertions(+), 17 deletions(-) create mode 100644 data/glade/plugins_window.glade create mode 100644 src/plugins/gui.py diff --git a/data/glade/plugins_window.glade b/data/glade/plugins_window.glade new file mode 100644 index 000000000..d8996b53e --- /dev/null +++ b/data/glade/plugins_window.glade @@ -0,0 +1,602 @@ + + + + + + 650 + 500 + 6 + Plugins + 650 + 500 + + + + True + 6 + + + True + True + + + True + True + 250 + True + + + True + True + 6 + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + True + True + + + + + False + True + + + + + True + 6 + 6 + + + True + 6 + + + True + 0 + <empty> + True + True + True + + + + + True + + + + + + 1 + + + + + False + + + + + True + 6 + + + True + Version: + + + False + + + + + True + 0 + <empty> + True + + + 1 + + + + + False + 1 + + + + + True + 6 + + + True + 0 + Authors: + + + False + + + + + True + 0 + 0 + <empty> + PANGO_WRAP_WORD_CHAR + True + PANGO_ELLIPSIZE_END + + + 1 + + + + + False + 2 + + + + + True + + + True + Homepage: + + + False + + + + + True + True + True + homepage url + GTK_RELIEF_NONE + False + 0 + 0 + + + + False + 1 + + + + + False + 3 + + + + + True + + + True + + + True + Descrition: + + + False + + + + + True + + + + + + 1 + + + + + False + + + + + True + False + True + 6 + False + GTK_WRAP_WORD + 6 + 6 + 1 + Plug-in decription should be displayed here. This text will be erased during PluginsWindow initialization. + + + 1 + + + + + 4 + + + + + True + 5 + GTK_BUTTONBOX_END + + + True + True + True + 0 + + + True + + + True + gtk-cancel + + + + + True + Uninstall + + + 1 + + + + + + + + + True + True + True + 0 + + + True + + + True + gtk-preferences + + + + + True + Configure + + + 1 + + + + + + + 1 + + + + + False + 5 + + + + + True + True + + + + + + + True + Installed + + + tab + False + + + + + True + True + + + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + + + True + True + True + + + + + False + True + + + + + True + + + True + + + True + <empty> + True + + + False + + + + + True + + + + + + 1 + + + + + False + + + + + True + + + True + Version: + + + False + + + + + True + <empty> + True + + + False + 1 + + + + + False + 1 + + + + + True + + + True + Authors: + + + False + + + + + True + <empty> + True + + + False + 1 + + + + + False + 2 + + + + + True + + + True + Homepage: + + + False + + + + + True + True + True + button + GTK_RELIEF_NONE + False + 0 + + + + False + 1 + + + + + False + 3 + + + + + True + + + True + + + True + Descrition: + + + False + + + + + True + + + + + + 1 + + + + + False + + + + + True + True + + + 1 + + + + + 4 + + + + + True + GTK_BUTTONBOX_END + + + True + True + True + button + 0 + + + + + True + True + True + button + 0 + + + 1 + + + + + False + 5 + + + + + True + True + + + + + 1 + + + + + Available + + + tab + 1 + False + + + + + + + + + tab + + + + + + + True + 15 + GTK_BUTTONBOX_END + + + True + True + True + gtk-close + True + 0 + + + + + + False + 1 + + + + + + diff --git a/data/glade/roster_window.glade b/data/glade/roster_window.glade index c480f6879..86ca8f2d1 100644 --- a/data/glade/roster_window.glade +++ b/data/glade/roster_window.glade @@ -193,6 +193,21 @@ + + + True + P_lugins + True + + + + True + gtk-disconnect + 1 + + + + diff --git a/launch.sh b/launch.sh index cfaf9cd68..d816e833c 100755 --- a/launch.sh +++ b/launch.sh @@ -15,4 +15,4 @@ if [ "x${OS}" == "xDarwin" ]; then fi cd ${BASE}/src -exec -a gajim python -t gajim.py $@ +exec -a gajim python2.5 -t gajim.py $@ diff --git a/plugins/length_notifier.py b/plugins/length_notifier.py index af85ded5f..7c9f2422a 100644 --- a/plugins/length_notifier.py +++ b/plugins/length_notifier.py @@ -32,12 +32,12 @@ from plugins import GajimPlugin from plugins.helpers import log, log_calls class LengthNotifierPlugin(GajimPlugin): - name = 'Message Length Notifier' - short_name = 'length_notifier' - version = '0.1' - description = '''Highlights message entry field in chat window when given -length of message is exceeded.''' - authors = ['Mateusz Biliński '] + name = u'Message Length Notifier' + short_name = u'length_notifier' + version = u'0.1' + description = u'''Highlights message entry field in chat window when given length of message is exceeded.''' + authors = [u'Mateusz Biliński '] + homepage = u'http://blog.bilinski.it' @log_calls('LengthNotifierPlugin') def __init__(self): diff --git a/src/gajim.py b/src/gajim.py index 19c98d5cd..69d64e0c4 100755 --- a/src/gajim.py +++ b/src/gajim.py @@ -26,6 +26,8 @@ ## import os +import pycallgraph + if os.name == 'nt': import warnings @@ -602,6 +604,8 @@ def on_exit(): if sys.platform == 'darwin': import osx osx.shutdown() + + #pycallgraph.make_dot_graph('common.xmpp-only.dot', format='dot') import atexit atexit.register(on_exit) @@ -3231,6 +3235,9 @@ class Interface: gajim.ipython_window = window def __init__(self): + #filter_func = pycallgraph.GlobbingFilter(include=['common.xmpp.*']) + #pycallgraph.start_trace(filter_func=filter_func) + gajim.interface = self # This is the manager and factory of message windows set by the module self.msg_win_mgr = None diff --git a/src/plugins/gui.py b/src/plugins/gui.py new file mode 100644 index 000000000..c4628958b --- /dev/null +++ b/src/plugins/gui.py @@ -0,0 +1,166 @@ +# -*- coding: utf-8 -*- + +## This file is part of Gajim. +## +## Gajim 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; version 3 only. +## +## Gajim 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 Gajim. If not, see . +## + +''' +GUI classes related to plug-in management. + +:author: Mateusz Biliński +:since: 06/06/2008 +:copyright: Copyright (2008) Mateusz Biliński +:license: GPL +''' + +__all__ = ['PluginsWindow'] + +import pango +import gtk, gobject + +import gtkgui_helpers +from common import gajim + +from plugins.helpers import log_calls, log + +class PluginsWindow(object): + '''Class for Plugins window''' + + @log_calls('PluginsWindow') + def __init__(self): + '''Initialize Plugins window''' + self.xml = gtkgui_helpers.get_glade('plugins_window.glade') + self.window = self.xml.get_widget('plugins_window') + self.window.set_transient_for(gajim.interface.roster.window) + + widgets_to_extract = ('plugins_notebook', + 'plugin_name_label', + 'plugin_version_label', + 'plugin_authors_label', + 'plugin_homepage_linkbutton', + 'plugin_description_textview', + 'uninstall_plugin_button', + 'configure_plugin_button', + 'installed_plugins_treeview') + + for widget_name in widgets_to_extract: + setattr(self, widget_name, self.xml.get_widget(widget_name)) + + attr_list = pango.AttrList() + attr_list.insert(pango.AttrWeight(pango.WEIGHT_BOLD, 0, -1)) + self.plugin_name_label.set_attributes(attr_list) + + self.installed_plugins_model = gtk.ListStore(gobject.TYPE_PYOBJECT, + gobject.TYPE_STRING, + gobject.TYPE_BOOLEAN) + self.installed_plugins_treeview.set_model(self.installed_plugins_model) + + renderer = gtk.CellRendererText() + col = gtk.TreeViewColumn(_('Plugin'), renderer, text=1) + self.installed_plugins_treeview.append_column(col) + + renderer = gtk.CellRendererToggle() + renderer.set_property('activatable', True) + renderer.connect('toggled', self.installed_plugins_toggled_cb) + col = gtk.TreeViewColumn(_('Active'), renderer, active=2) + self.installed_plugins_treeview.append_column(col) + + # connect signal for selection change + selection = self.installed_plugins_treeview.get_selection() + selection.connect('changed', + self.installed_plugins_treeview_selection_changed) + selection.set_mode(gtk.SELECTION_SINGLE) + + self._clear_installed_plugin_info() + + self.fill_installed_plugins_model() + + self.xml.signal_autoconnect(self) + + self.plugins_notebook.set_current_page(0) + + self.window.show_all() + gtkgui_helpers.possibly_move_window_in_current_desktop(self.window) + + @log_calls('PluginsWindow') + def installed_plugins_treeview_selection_changed(self, treeview_selection): + model, iter = treeview_selection.get_selected() + if iter: + plugin = model.get_value(iter, 0) + plugin_name = model.get_value(iter, 1) + is_active = model.get_value(iter, 2) + + self._display_installed_plugin_info(plugin) + else: + self._clear_installed_plugin_info() + + def _display_installed_plugin_info(self, plugin): + self.plugin_name_label.set_text(plugin.name) + self.plugin_version_label.set_text(plugin.version) + self.plugin_authors_label.set_text(", ".join(plugin.authors)) + self.plugin_homepage_linkbutton.set_uri(plugin.homepage) + self.plugin_homepage_linkbutton.set_label(plugin.homepage) + self.plugin_homepage_linkbutton.set_property('sensitive', True) + + desc_textbuffer = self.plugin_description_textview.get_buffer() + desc_textbuffer.set_text(plugin.description) + self.plugin_description_textview.set_property('sensitive', True) + self.uninstall_plugin_button.set_property('sensitive', True) + self.configure_plugin_button.set_property('sensitive', True) + + def _clear_installed_plugin_info(self): + self.plugin_name_label.set_text('') + self.plugin_version_label.set_text('') + self.plugin_authors_label.set_text('') + self.plugin_homepage_linkbutton.set_uri('') + self.plugin_homepage_linkbutton.set_label('') + self.plugin_homepage_linkbutton.set_property('sensitive', False) + + desc_textbuffer = self.plugin_description_textview.get_buffer() + desc_textbuffer.set_text('') + self.plugin_description_textview.set_property('sensitive', False) + self.uninstall_plugin_button.set_property('sensitive', False) + self.configure_plugin_button.set_property('sensitive', False) + + @log_calls('PluginsWindow') + def fill_installed_plugins_model(self): + pm = gajim.plugin_manager + self.installed_plugins_model.clear() + self.installed_plugins_model.set_sort_column_id(0, gtk.SORT_ASCENDING) + + for plugin_class in pm.plugins: + self.installed_plugins_model.append([plugin_class, + plugin_class.name, + plugin_class._active]) + + @log_calls('PluginsWindow') + def installed_plugins_toggled_cb(self, cell, path): + is_active = self.installed_plugins_model[path][2] + plugin_class = self.installed_plugins_model[path][0] + + if is_active: + gajim.plugin_manager.deactivate_plugin(plugin_class._instance) + else: + gajim.plugin_manager.activate_plugin(plugin_class) + + self.installed_plugins_model[path][2] = not is_active + + @log_calls('PluginsWindow') + def on_plugins_window_destroy(self, widget): + '''Close window''' + del gajim.interface.instances['plugins'] + + @log_calls('PluginsWindow') + def on_close_button_clicked(self, widget): + self.window.destroy() \ No newline at end of file diff --git a/src/plugins/plugin.py b/src/plugins/plugin.py index c8bb95af9..fd49c6a44 100644 --- a/src/plugins/plugin.py +++ b/src/plugins/plugin.py @@ -30,7 +30,7 @@ class GajimPlugin(object): ''' Base class for implementing Gajim plugins. ''' - name = '' + name = u'' ''' Name of plugin. @@ -38,7 +38,7 @@ class GajimPlugin(object): :type: unicode ''' - short_name = '' + short_name = u'' ''' Short name of plugin. @@ -49,7 +49,7 @@ class GajimPlugin(object): :todo: decide whether we really need this one, because class name (with module name) can act as such short name ''' - version = '' + version = u'' ''' Version of plugin. @@ -61,7 +61,7 @@ class GajimPlugin(object): same plugin class but with different version and we want only the newest one to be active - is such policy good? ''' - description = '' + description = u'' ''' Plugin description. @@ -78,6 +78,15 @@ class GajimPlugin(object): :todo: should we decide on any particular format of author strings? Especially: should we force format of giving author's e-mail? ''' + homepage = u'' + ''' + URL to plug-in's homepage. + + :type: unicode + + :todo: should we check whether provided string is valid URI? (Maybe + using 'property') + ''' gui_extension_points = {} ''' Extension points that plugin wants to connect with. diff --git a/src/plugins/pluginmanager.py b/src/plugins/pluginmanager.py index b9e113793..359ec3805 100644 --- a/src/plugins/pluginmanager.py +++ b/src/plugins/pluginmanager.py @@ -94,7 +94,7 @@ class PluginManager(object): ''' for path in gajim.PLUGINS_DIRS: - self.plugins.extend(PluginManager.scan_dir_for_plugins(path)) + self._add_plugins(PluginManager.scan_dir_for_plugins(path)) log.debug('plugins: %s'%(self.plugins)) @@ -102,6 +102,22 @@ class PluginManager(object): log.debug('active: %s'%(self.active_plugins)) + + @log_calls('PluginManager') + def _add_plugin(self, plugin_class): + ''' + :todo: what about adding plug-ins that are already added? Module reload + and adding class from reloaded module or ignoring adding plug-in? + ''' + plugin_class._active = False + plugin_class._instance = None + self.plugins.append(plugin_class) + + @log_calls('PluginManager') + def _add_plugins(self, plugin_classes): + for plugin_class in plugin_classes: + self._add_plugin(plugin_class) + @log_calls('PluginManager') def gui_extension_point(self, gui_extpoint_name, *args): ''' @@ -124,7 +140,6 @@ class PluginManager(object): call 'self._deactivate_plugin()' or sth similar. Looking closer - we only rewrite tuples here. Real check should be made in method that invokes gui_extpoints handlers. - ''' self._add_gui_extension_point_call_to_list(gui_extpoint_name, *args) @@ -141,13 +156,13 @@ class PluginManager(object): handlers[0](*args) @log_calls('PluginManager') - def activate_plugin(self, plugin): + def activate_plugin(self, plugin_class): ''' :param plugin: plugin to be activated :type plugin: class object of `GajimPlugin` subclass ''' - plugin_object = plugin() + plugin_object = plugin_class() success = True @@ -156,6 +171,8 @@ class PluginManager(object): if success: self.active_plugins.append(plugin_object) + plugin_class._instance = plugin_object + plugin_class._active = True return success @@ -165,8 +182,9 @@ class PluginManager(object): # for each handled GUI extension point) for gui_extpoint_name, gui_extpoint_handlers in \ plugin_object.gui_extension_points.iteritems(): - for gui_extension_point_args in self.gui_extension_points[gui_extpoint_name]: - gui_extpoint_handlers[1](*gui_extension_point_args) + if gui_extpoint_name in self.gui_extension_points: + for gui_extension_point_args in self.gui_extension_points[gui_extpoint_name]: + gui_extpoint_handlers[1](*gui_extension_point_args) # remove GUI extension points handlers (provided by plug-in) from # handlers list @@ -176,6 +194,8 @@ class PluginManager(object): # removing plug-in from active plug-ins list self.active_plugins.remove(plugin_object) + plugin_object.__class__._active = False + del plugin_object def deactivate_all_plugins(self): for plugin_object in self.active_plugins: diff --git a/src/roster_window.py b/src/roster_window.py index 9af3d59b6..76b86c81a 100644 --- a/src/roster_window.py +++ b/src/roster_window.py @@ -44,6 +44,8 @@ import message_control import adhoc_commands import notify import features_window +import plugins +import plugins.gui from common import gajim from common import helpers @@ -3129,6 +3131,12 @@ class RosterWindow: gajim.interface.instances['preferences'].window.present() else: gajim.interface.instances['preferences'] = config.PreferencesWindow() + + def on_plugins_menuitem_activate(self, widget): + if gajim.interface.instances.has_key('plugins'): + gajim.interface.instances['plugins'].window.present() + else: + gajim.interface.instances['plugins'] = plugins.gui.PluginsWindow() def on_publish_tune_checkbutton_toggled(self, widget, account): if widget.get_active():