diff --git a/geanypy/geany/Makefile.am b/geanypy/geany/Makefile.am index 880c5f662..62bac20df 100644 --- a/geanypy/geany/Makefile.am +++ b/geanypy/geany/Makefile.am @@ -1,7 +1,5 @@ geanypy_sources = __init__.py \ console.py \ - manager.py \ - loader.py \ plugin.py \ signalmanager.py geanypy_objects = $(geanypy_sources:.py=.pyc) diff --git a/geanypy/geany/__init__.py b/geanypy/geany/__init__.py index 493dfa4d8..264d6eee1 100644 --- a/geanypy/geany/__init__.py +++ b/geanypy/geany/__init__.py @@ -15,9 +15,7 @@ import encoding import filetypes import highlighting -import loader import main -import manager import msgwindow import navqueue import prefs diff --git a/geanypy/geany/loader.py b/geanypy/geany/loader.py deleted file mode 100644 index 14b9b73d6..000000000 --- a/geanypy/geany/loader.py +++ /dev/null @@ -1,172 +0,0 @@ -import os -import imp -from collections import namedtuple -import geany - -PluginInfo = namedtuple('PluginInfo', 'filename, name, version, description, author, cls') - - -class PluginLoader(object): - - plugins = {} - - def __init__(self, plugin_dirs): - - self.plugin_dirs = plugin_dirs - - self.available_plugins = [] - for plugin in self.iter_plugin_info(): - self.available_plugins.append(plugin) - - self.restore_loaded_plugins() - - - def update_loaded_plugins_file(self): - for path in self.plugin_dirs: - if os.path.isdir(path): - try: - state_file = os.path.join(path, '.loaded_plugins') - with open(state_file, 'w') as f: - for plugfn in self.plugins: - f.write("%s\n" % plugfn) - except IOError as err: - if err.errno == 13: #perms - pass - else: - raise - - - def restore_loaded_plugins(self): - loaded_plugins = [] - for path in reversed(self.plugin_dirs): - state_file = os.path.join(path, ".loaded_plugins") - if os.path.exists(state_file): - for line in open(state_file): - line = line.strip() - if line not in loaded_plugins: - loaded_plugins.append(line) - for filename in loaded_plugins: - self.load_plugin(filename) - - - def load_all_plugins(self): - - for plugin_info in self.iter_plugin_info(): - if plugin_filename.endswith('test.py'): # hack for testing - continue - plug = self.load_plugin(plugin_info.filename) - if plug: - print("Loaded plugin: %s" % plugin_info.filename) - print(" Name: %s v%s" % (plug.name, plug.version)) - print(" Desc: %s" % plug.description) - print(" Author: %s" % plug.author) - - - def unload_all_plugins(self): - - for plugin in self.plugins: - self.unload_plugin(plugin) - - - def reload_all_plugins(self): - - self.unload_all_plugins() - self.load_all_plugins() - - - def iter_plugin_info(self): - - for d in self.plugin_dirs: - if os.path.isdir(d): - for current_file in os.listdir(d): - #check inside folders inside the plugins dir so we can load .py files here as plugins - current_path=os.path.abspath(os.path.join(d, current_file)) - if os.path.isdir(current_path): - for plugin_folder_file in os.listdir(current_path): - if plugin_folder_file.endswith('.py'): - #loop around results if its fails to load will never reach yield - for p in self.load_plugin_info(current_path,plugin_folder_file): - yield p - - #not a sub directory so if it ends with .py lets just attempt to load it as a plugin - if current_file.endswith('.py'): - #loop around results if its fails to load will never reach yield - for p in self.load_plugin_info(d,current_file): - yield p - - def load_plugin_info(self,d,f): - filename = os.path.abspath(os.path.join(d, f)) - if filename.endswith("test.py"): - pass - text = open(filename).read() - module_name = os.path.basename(filename)[:-3] - try: - module = imp.load_source(module_name, filename) - except ImportError as exc: - print "Error: failed to import settings module ({})".format(exc) - module=None - if module: - for k, v in module.__dict__.iteritems(): - if k == geany.Plugin.__name__: - continue - try: - if issubclass(v, geany.Plugin): - inf = PluginInfo( - filename, - getattr(v, '__plugin_name__'), - getattr(v, '__plugin_version__', ''), - getattr(v, '__plugin_description__', ''), - getattr(v, '__plugin_author__', ''), - v) - yield inf - - except TypeError: - continue - - - def load_plugin(self, filename): - - for avail in self.available_plugins: - if avail.filename == filename: - inst = avail.cls() - self.plugins[filename] = inst - self.update_loaded_plugins_file() - geany.ui_utils.set_statusbar('GeanyPy: plugin activated: %s' % - inst.name, True) - return inst - - - def unload_plugin(self, filename): - - try: - plugin = self.plugins[filename] - name = plugin.name - plugin.cleanup() - del self.plugins[filename] - self.update_loaded_plugins_file() - geany.ui_utils.set_statusbar('GeanyPy: plugin deactivated: %s' % - name, True) - except KeyError: - print("Unable to unload plugin '%s': it's not loaded" % filename) - - - def reload_plugin(self, filename): - - if filename in self.plugins: - self.unload_plugin(filename) - self.load_plugin(filename) - - - def plugin_has_help(self, filename): - - for plugin_info in self.iter_plugin_info(): - if plugin_info.filename == filename: - return hasattr(plugin_info.cls, 'show_help') - - - def plugin_has_configure(self, filename): - - try: - return hasattr(self.plugins[filename], 'show_configure') - except KeyError: - return None diff --git a/geanypy/geany/manager.py b/geanypy/geany/manager.py deleted file mode 100644 index 9c7e9a478..000000000 --- a/geanypy/geany/manager.py +++ /dev/null @@ -1,179 +0,0 @@ -import gtk -import gobject -import glib -from htmlentitydefs import name2codepoint -from loader import PluginLoader - - -class PluginManager(gtk.Dialog): - - def __init__(self, plugin_dirs=[]): - gtk.Dialog.__init__(self, title="Plugin Manager") - self.loader = PluginLoader(plugin_dirs) - - self.set_default_size(400, 450) - self.set_has_separator(True) - icon = self.render_icon(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU) - self.set_icon(icon) - - self.connect("response", lambda w,d: self.hide()) - - vbox = gtk.VBox(False, 12) - vbox.set_border_width(12) - - lbl = gtk.Label("Choose plugins to load or unload:") - lbl.set_alignment(0.0, 0.5) - vbox.pack_start(lbl, False, False, 0) - - sw = gtk.ScrolledWindow() - sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) - sw.set_shadow_type(gtk.SHADOW_ETCHED_IN) - vbox.pack_start(sw, True, True, 0) - - self.treeview = gtk.TreeView() - sw.add(self.treeview) - - vbox.show_all() - - self.get_content_area().add(vbox) - - action_area = self.get_action_area() - action_area.set_spacing(0) - action_area.set_homogeneous(False) - - btn = gtk.Button(stock=gtk.STOCK_CLOSE) - btn.set_border_width(6) - btn.connect("clicked", lambda x: self.response(gtk.RESPONSE_CLOSE)) - action_area.pack_start(btn, False, True, 0) - btn.show() - - self.btn_help = gtk.Button(stock=gtk.STOCK_HELP) - self.btn_help.set_border_width(6) - self.btn_help.set_no_show_all(True) - action_area.pack_start(self.btn_help, False, True, 0) - action_area.set_child_secondary(self.btn_help, True) - - self.btn_prefs = gtk.Button(stock=gtk.STOCK_PREFERENCES) - self.btn_prefs.set_border_width(6) - self.btn_prefs.set_no_show_all(True) - action_area.pack_start(self.btn_prefs, False, True, 0) - action_area.set_child_secondary(self.btn_prefs, True) - - action_area.show() - - self.load_plugins_list() - - - def on_help_button_clicked(self, button, treeview, model): - path = treeview.get_cursor()[0] - iter = model.get_iter(path) - filename = model.get_value(iter, 2) - for plugin in self.loader.available_plugins: - if plugin.filename == filename: - plugin.cls.show_help() - break - else: - print("Plugin does not support help function") - - - def on_preferences_button_clicked(self, button, treeview, model): - path = treeview.get_cursor()[0] - iter = model.get_iter(path) - filename = model.get_value(iter, 2) - try: - self.loader.plugins[filename].show_configure() - except KeyError: - print("Plugin is not loaded, can't run configure function") - - - def activate_plugin(self, filename): - self.loader.load_plugin(filename) - - - def deactivate_plugin(self, filename): - self.loader.unload_plugin(filename) - - - def load_plugins_list(self): - liststore = gtk.ListStore(gobject.TYPE_BOOLEAN, str, str) - - self.btn_help.connect("clicked", - self.on_help_button_clicked, self.treeview, liststore) - - self.btn_prefs.connect("clicked", - self.on_preferences_button_clicked, self.treeview, liststore) - - self.treeview.set_model(liststore) - self.treeview.set_headers_visible(False) - self.treeview.set_grid_lines(True) - - check_renderer = gtk.CellRendererToggle() - check_renderer.set_radio(False) - check_renderer.connect('toggled', self.on_plugin_load_toggled, liststore) - text_renderer = gtk.CellRendererText() - - check_column = gtk.TreeViewColumn(None, check_renderer, active=0) - text_column = gtk.TreeViewColumn(None, text_renderer, markup=1) - - self.treeview.append_column(check_column) - self.treeview.append_column(text_column) - - self.treeview.connect('row-activated', - self.on_row_activated, check_renderer, liststore) - self.treeview.connect('cursor-changed', - self.on_selected_plugin_changed, liststore) - - self.load_sorted_plugins_info(liststore) - - - def load_sorted_plugins_info(self, list_store): - - plugin_info_list = list(self.loader.iter_plugin_info()) - #plugin_info_list.sort(key=lambda pi: pi[1]) - - for plugin_info in plugin_info_list: - - lbl = str('%s %s\n%s\n' + - 'Author: %s\n' + - 'Filename: %s') % ( - glib.markup_escape_text(plugin_info.name), - glib.markup_escape_text(plugin_info.version), - glib.markup_escape_text(plugin_info.description), - glib.markup_escape_text(plugin_info.author), - glib.markup_escape_text(plugin_info.filename)) - - loaded = plugin_info.filename in self.loader.plugins - - list_store.append([loaded, lbl, plugin_info.filename]) - - - def on_selected_plugin_changed(self, treeview, model): - - path = treeview.get_cursor()[0] - iter = model.get_iter(path) - filename = model.get_value(iter, 2) - active = model.get_value(iter, 0) - - if self.loader.plugin_has_configure(filename): - self.btn_prefs.set_visible(True) - else: - self.btn_prefs.set_visible(False) - - if self.loader.plugin_has_help(filename): - self.btn_help.set_visible(True) - else: - self.btn_help.set_visible(False) - - - def on_plugin_load_toggled(self, cell, path, model): - active = not cell.get_active() - iter = model.get_iter(path) - model.set_value(iter, 0, active) - if active: - self.activate_plugin(model.get_value(iter, 2)) - else: - self.deactivate_plugin(model.get_value(iter, 2)) - - - def on_row_activated(self, tvw, path, view_col, cell, model): - self.on_plugin_load_toggled(cell, path, model) diff --git a/geanypy/geany/plugin.py b/geanypy/geany/plugin.py index f8adef6be..212fa7ca7 100644 --- a/geanypy/geany/plugin.py +++ b/geanypy/geany/plugin.py @@ -36,8 +36,9 @@ def cleanup(self): files with a `.py` extension will be loaded. """ - -class Plugin(object): +from geany.pluginbase import PluginBase + +class Plugin(PluginBase): """ Base class for all plugins. All plugins must inherit from this in order to be properly detected. @@ -49,14 +50,14 @@ class Plugin(object): #__plugin_version__ = None #__plugin_author__ = None - _events = { "document-open": [], # TODO: add more events here } - def __init__(self): + def __init__(self, ctx=None): + PluginBase.__init__(self, ctx) """ When the plugin is loaded its __init__() function will be called so that's a good place to put plugin initialization code. diff --git a/geanypy/plugins/Makefile.am b/geanypy/plugins/Makefile.am index 6aa07ff22..3cb662de8 100644 --- a/geanypy/plugins/Makefile.am +++ b/geanypy/plugins/Makefile.am @@ -1,4 +1,4 @@ geanypy_plugins = demo.py hello.py console.py -geanypydir = $(datadir)/geany/geanypy/plugins +geanypydir = $(libdir)/geany geanypy_DATA = $(geanypy_plugins) EXTRA_DIST = $(geanypy_plugins) diff --git a/geanypy/plugins/console.py b/geanypy/plugins/console.py index ee26ae35b..df5180e7e 100644 --- a/geanypy/plugins/console.py +++ b/geanypy/plugins/console.py @@ -191,16 +191,7 @@ def on_bg_color_changed(self, clr_btn, data=None): self.bg = clr_btn.get_color().to_string() - def show_configure(self): - dialog = gtk.Dialog("Configure Python Console", - geany.main_widgets.window, - gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, - (gtk.STOCK_CLOSE, gtk.RESPONSE_ACCEPT)) - - dialog.set_has_separator(True) - - content_area = dialog.get_content_area() - content_area.set_border_width(6) + def configure(self, dialog): vbox = gtk.VBox(spacing=6) vbox.set_border_width(6) @@ -306,8 +297,5 @@ def show_configure(self): vbox.pack_start(fra_general, True, True, 0) vbox.pack_start(fra_appearances, False, True, 0) - content_area.pack_start(vbox, True, True, 0) - content_area.show_all() - dialog.run() - dialog.destroy() + return vbox diff --git a/geanypy/src/Makefile.am b/geanypy/src/Makefile.am index 303b07a81..c81b0f51e 100644 --- a/geanypy/src/Makefile.am +++ b/geanypy/src/Makefile.am @@ -6,9 +6,9 @@ geanyplugindir = $(libdir)/geany geanypy_la_LDFLAGS = -module -avoid-version -Wl,--export-dynamic geanypy_la_CPPFLAGS = @GEANY_CFLAGS@ @PYGTK_CFLAGS@ @PYTHON_CPPFLAGS@ \ -DGEANYPY_PYTHON_DIR="\"$(libdir)/geany/geanypy\"" \ - -DGEANYPY_PLUGIN_DIR="\"$(datadir)/geany/geanypy/plugins\"" + -DGEANYPY_PLUGIN_DIR="\"$(libdir)/geany\"" geanypy_la_CFLAGS = @GEANYPY_CFLAGS@ @GMODULE_CFLAGS@ -geanypy_la_LIBADD = @GEANY_LIBS@ @PYGTK_LIBS@ @PYTHON_LDFLAGS@ \ +geanypy_la_LIBADD = @GEANY_LIBS@ @PYGTK_LIBS@ @PYTHON_LIBS@ \ @PYTHON_EXTRA_LIBS@ @PYTHON_EXTRA_LDFLAGS@ \ @GMODULE_LIBS@ geanypy_la_SOURCES = geanypy-app.c \ diff --git a/geanypy/src/geanypy-plugin.c b/geanypy/src/geanypy-plugin.c index 338067c6a..ef3df16aa 100644 --- a/geanypy/src/geanypy-plugin.c +++ b/geanypy/src/geanypy-plugin.c @@ -27,25 +27,10 @@ #include "geanypy.h" -G_MODULE_EXPORT GeanyPlugin *geany_plugin; -G_MODULE_EXPORT GeanyData *geany_data; -G_MODULE_EXPORT GeanyFunctions *geany_functions; - - -G_MODULE_EXPORT PLUGIN_VERSION_CHECK(211) - -G_MODULE_EXPORT PLUGIN_SET_INFO( - _("GeanyPy"), - _("Python plugins support"), - "1.0", - "Matthew Brush ") - - -static GtkWidget *loader_item = NULL; -static PyObject *manager = NULL; -static gchar *plugin_dir = NULL; -static SignalManager *signal_manager = NULL; +#include +#include +GeanyData *geany_data; /* Forward declarations to prevent compiler warnings. */ PyMODINIT_FUNC initapp(void); @@ -64,6 +49,7 @@ PyMODINIT_FUNC initscintilla(void); PyMODINIT_FUNC initsearch(void); PyMODINIT_FUNC inittemplates(void); PyMODINIT_FUNC initui_utils(void); +PyMODINIT_FUNC initpluginbase(void); static void @@ -104,6 +90,7 @@ GeanyPy_start_interpreter(void) initsearch(); inittemplates(); initui_utils(); + initpluginbase(); #ifdef GEANYPY_WINDOWS { /* On windows, get path at runtime since we don't really know where @@ -131,7 +118,9 @@ GeanyPy_start_interpreter(void) "import os, sys\n" "path = '%s'.replace('~', os.path.expanduser('~'))\n" "sys.path.append(path)\n" - "import geany\n", py_dir); + "path = '%s'.replace('~', os.path.expanduser('~'))\n" + "sys.path.append(path)\n" + "import geany\n", py_dir, GEANYPY_PLUGIN_DIR); g_free(py_dir); PyRun_SimpleString(init_code); @@ -146,141 +135,410 @@ GeanyPy_stop_interpreter(void) Py_Finalize(); } +typedef struct +{ + PyObject *base; + SignalManager *signal_manager; +} +GeanyPyData; -static void -GeanyPy_init_manager(const gchar *dir) +typedef struct { - PyObject *module, *man, *args; - gchar *sys_plugin_dir = NULL; + PyObject *class; + PyObject *module; + PyObject *instance; +} +GeanyPyPluginData; + +static gboolean has_error(void) +{ + if (PyErr_Occurred()) + { + PyErr_Print(); + return TRUE; + } + return FALSE; +} - g_return_if_fail(dir != NULL); +static PyTypeObject PluginBaseType; - module = PyImport_ImportModule("geany.manager"); - if (module == NULL) - { - g_warning(_("Failed to import manager module")); - return; - } +static gboolean geanypy_proxy_init(GeanyPlugin *plugin, gpointer pdata) +{ + GeanyPyPluginData *data = (GeanyPyPluginData *) pdata; + GeanyPyPluginBase *base; + PyObject *args; + + base = PyObject_New(GeanyPyPluginBase, &PluginBaseType); + base->plugin = plugin; + + /* The new-style constructor gets a context parameter, and the class must pass + * it to the geany.Plugin constructor. The new-style constructor is required + * to have certain APIs work in it (those that need the GeanyPlugin pointer) */ + args = Py_BuildValue("(O)", base); + data->instance = PyObject_CallObject(data->class, args); + Py_DECREF(args); + Py_DECREF(base); + + if (PyErr_Occurred()) { + PyErr_Clear(); + /* If the plugin still implements the old constructor we can catch this and try again */ + data->instance = PyObject_CallObject(data->class, NULL); + } - man = PyObject_GetAttrString(module, "PluginManager"); - Py_DECREF(module); + if (has_error()) + return FALSE; - if (man == NULL) - { - g_warning(_("Failed to retrieve PluginManager from manager module")); - return; - } + ((GeanyPyPluginBase *)data->instance)->plugin = plugin; -#ifdef GEANYPY_WINDOWS - { /* Detect the system plugin's dir at runtime on Windows since we - * don't really know where Geany is installed. */ - gchar *geany_base_dir; - geany_base_dir = g_win32_get_package_installation_directory_of_module(NULL); - if (geany_base_dir) - { - sys_plugin_dir = g_build_filename(geany_base_dir, "lib", "geanypy", "plugins", NULL); - g_free(geany_base_dir); - } - if (!g_file_test(sys_plugin_dir, G_FILE_TEST_EXISTS)) - { - g_warning(_("System plugin directory not found.")); - g_free(sys_plugin_dir); - sys_plugin_dir = NULL; - } + return TRUE; +} + + +static void geanypy_proxy_cleanup(GeanyPlugin *plugin, gpointer pdata) +{ + GeanyPyPluginData *data = (GeanyPyPluginData *) pdata; + + PyObject_CallMethod(data->instance, "cleanup", NULL); + if (has_error()) + return; +} + + +static GtkWidget *geanypy_proxy_configure(GeanyPlugin *plugin, GtkDialog *parent, gpointer pdata) +{ + GeanyPyPluginData *data = (GeanyPyPluginData *) pdata; + PyObject *o, *oparent; + GObject *widget; + + oparent = pygobject_new(G_OBJECT(parent)); + o = PyObject_CallMethod(data->instance, "configure", "O", oparent, NULL); + Py_DECREF(oparent); + + if (!has_error() && o != NULL) + { + /* Geany wants only the underlying GtkWidget, we must only ref that + * and free the pygobject wrapper */ + widget = g_object_ref(pygobject_get(o)); + Py_DECREF(o); + return GTK_WIDGET(widget); } -#else - sys_plugin_dir = g_strdup(GEANYPY_PLUGIN_DIR); -#endif + return NULL; +} + +static void geanypy_proxy_help(GeanyPlugin *plugin, gpointer pdata) +{ + GeanyPyPluginData *data = (GeanyPyPluginData *) pdata; + PyObject_CallMethod(data->instance, "help", NULL); + if (has_error()) + return; +} + +static gint +geanypy_probe(GeanyPlugin *proxy, const gchar *filename, gpointer pdata) +{ + gchar *file_plugin = g_strdup_printf("%.*s.plugin", + (int)(strrchr(filename, '.') - filename), filename); + gint ret = PROXY_IGNORED; + + /* avoid clash with libpeas py plugins, those come with a corresponding .plugin file */ + if (!g_file_test(file_plugin, G_FILE_TEST_EXISTS)) + ret = PROXY_MATCHED; - g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "User plugins: %s", dir); + g_free(file_plugin); + return ret; +} + + +static const gchar *string_from_attr(PyObject *o, const gchar *attr) +{ + PyObject *string = PyObject_GetAttrString(o, "__plugin_name__"); + const gchar *ret = PyString_AsString(string); + Py_DECREF(string); - if (sys_plugin_dir) + return ret; +} + + +static gpointer +geanypy_load(GeanyPlugin *proxy, GeanyPlugin *subplugin, const gchar *filename, gpointer pdata) +{ + GeanyPyData *data = pdata; + PyObject *fromlist, *module, *dict, *key, *val, *found = NULL; + Py_ssize_t pos = 0; + gchar *modulename, *dot; + gpointer ret = NULL; + + modulename = g_path_get_basename(filename); + /* We are guaranteed that filename has a .py extension + * because we did geany_plugin_register_proxy() for it */ + dot = strrchr(modulename, '.'); + *dot = '\0'; + /* we need a fromlist to be able to import modules with a '.' in the + * name. -- libpeas */ + fromlist = PyTuple_New (0); + + module = PyImport_ImportModuleEx(modulename, NULL, NULL, fromlist); + if (has_error() || !module) + goto err; + + dict = PyModule_GetDict(module); + + while (PyDict_Next (dict, &pos, &key, &val) && found == NULL) { - g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "System plugins: %s", sys_plugin_dir); - args = Py_BuildValue("([s, s])", sys_plugin_dir, dir); - g_free(sys_plugin_dir); + if (PyType_Check(val) && PyObject_IsSubclass(val, data->base)) + found = val; } - else - args = Py_BuildValue("([s])", dir); - manager = PyObject_CallObject(man, args); - if (PyErr_Occurred()) - PyErr_Print(); - Py_DECREF(man); - Py_DECREF(args); - - if (manager == NULL) - { - g_warning(_("Unable to instantiate new PluginManager")); - return; - } + if (found) + { + GeanyPyPluginData *pdata = g_slice_new(GeanyPyPluginData); + PluginInfo *info = subplugin->info; + GeanyPluginFuncs *funcs = subplugin->funcs; + Py_INCREF(found); + pdata->module = module; + pdata->class = found; + pdata->instance = NULL; + info->name = string_from_attr(pdata->class, "__plugin_name__"); + info->description = string_from_attr(pdata->class, "__plugin_description__"); + info->version = string_from_attr(pdata->class, "__plugin_version__"); + info->author = string_from_attr(pdata->class, "__plugin_author__"); + funcs->init = geanypy_proxy_init; + funcs->cleanup = geanypy_proxy_cleanup; + if (PyObject_HasAttrString(found, "configure")) + funcs->configure = geanypy_proxy_configure; + if (PyObject_HasAttrString(found, "help")) + funcs->help = geanypy_proxy_help; + if (GEANY_PLUGIN_REGISTER_FULL(subplugin, 224, pdata, NULL)) + ret = pdata; + } + +err: + g_free(modulename); + Py_DECREF(fromlist); + return ret; } static void -GeanyPy_show_manager(void) +geanypy_unload(GeanyPlugin *plugin, GeanyPlugin *subplugin, gpointer load_data, gpointer pdata_) { - PyObject *show_method; - - g_return_if_fail(manager != NULL); - - show_method = PyObject_GetAttrString(manager, "show_all"); - if (show_method == NULL) - { - g_warning(_("Unable to get show_all() method on plugin manager")); - return; - } - PyObject_CallObject(show_method, NULL); - Py_DECREF(show_method); + GeanyPyPluginData *pdata = load_data; + + Py_XDECREF(pdata->instance); + Py_DECREF(pdata->class); + Py_DECREF(pdata->module); + while (PyGC_Collect()); + g_slice_free(GeanyPyPluginData, pdata); } -static void -on_python_plugin_loader_activate(GtkMenuItem *item, gpointer user_data) +static gboolean geanypy_init(GeanyPlugin *plugin_, gpointer pdata) { - GeanyPy_show_manager(); + const gchar *exts[] = { "py", NULL }; + GeanyPyData *state = pdata; + PyObject *module; + + plugin_->proxy_funcs->probe = geanypy_probe; + plugin_->proxy_funcs->load = geanypy_load; + plugin_->proxy_funcs->unload = geanypy_unload; + + geany_data = plugin_->geany_data; + + GeanyPy_start_interpreter(); + state->signal_manager = signal_manager_new(plugin_); + + module = PyImport_ImportModule("geany.plugin"); + if (has_error() || !module) + goto err; + + state->base = PyObject_GetAttrString(module, "Plugin"); + Py_DECREF(module); + if (has_error() || !state->base) + goto err; + + if (!geany_plugin_register_proxy(plugin_, exts)) { + Py_DECREF(state->base); + goto err; + } + + return TRUE; + +err: + signal_manager_free(state->signal_manager); + GeanyPy_stop_interpreter(); + return FALSE; } +static void geanypy_cleanup(GeanyPlugin *plugin, gpointer pdata) +{ + GeanyPyData *state = pdata; + signal_manager_free(state->signal_manager); + Py_DECREF(state->base); + GeanyPy_stop_interpreter(); +} + G_MODULE_EXPORT void -plugin_init(GeanyData *data) +geany_load_module(GeanyPlugin *plugin) { - GeanyPy_start_interpreter(); - signal_manager = signal_manager_new(geany_plugin); - - plugin_dir = g_build_filename(geany->app->configdir, - "plugins", "geanypy", "plugins", NULL); - - if (!g_file_test(plugin_dir, G_FILE_TEST_IS_DIR)) - { - if (g_mkdir_with_parents(plugin_dir, 0755) == -1) - { - g_warning(_("Unable to create Python plugins directory: %s: %s"), - plugin_dir, - strerror(errno)); - g_free(plugin_dir); - plugin_dir = NULL; - } - } - - if (plugin_dir != NULL) - GeanyPy_init_manager(plugin_dir); - - loader_item = gtk_menu_item_new_with_label(_("Python Plugin Manager")); - gtk_widget_set_sensitive(loader_item, plugin_dir != NULL); - gtk_menu_append(GTK_MENU(geany->main_widgets->tools_menu), loader_item); - gtk_widget_show(loader_item); - g_signal_connect(loader_item, "activate", - G_CALLBACK(on_python_plugin_loader_activate), NULL); + GeanyPyData *state = g_new0(GeanyPyData, 1); + + plugin->info->name = _("GeanyPy"); + plugin->info->description = _("Python plugins support"); + plugin->info->version = "1.0"; + plugin->info->author = "Matthew Brush "; + plugin->funcs->init = geanypy_init; + plugin->funcs->cleanup = geanypy_cleanup; + + GEANY_PLUGIN_REGISTER_FULL(plugin, 224, state, g_free); } -G_MODULE_EXPORT void plugin_cleanup(void) +static void PluginBase_dealloc(GeanyPyPluginBase *self) { } + + +static gboolean call_key(gpointer *unused, guint key_id, gpointer data) { - signal_manager_free(signal_manager); - Py_XDECREF(manager); - GeanyPy_stop_interpreter(); - gtk_widget_destroy(loader_item); - g_free(plugin_dir); + PyObject *callback = data; + PyObject *args; + + args = Py_BuildValue("(i)", key_id); + PyObject_CallObject(callback, args); + Py_DECREF(args); +} + + +static PyObject * +PluginBase_set_kb_group(GeanyPyPluginBase *self, PyObject *args, PyObject *kwargs) +{ + static gchar *kwlist[] = { "section_name", "count", "callback", NULL }; + int count = 0; + const gchar *section_name = NULL; + GeanyKeyGroup *group = NULL; + PyObject *py_callback = NULL; + if (PyArg_ParseTupleAndKeywords(args, kwargs, "si|O", kwlist, §ion_name, &count, &py_callback)) + { + if (PyCallable_Check(py_callback)) + { + Py_INCREF(py_callback); + group = plugin_set_key_group_full(self->plugin, section_name, count, + (GeanyKeyGroupFunc) call_key, py_callback, Py_DecRef); + } + else + group = plugin_set_key_group(self->plugin, section_name, count, NULL); + } + + if (group) + { + GObject *wrapper; + PyObject *ret; + wrapper = g_object_new(G_TYPE_OBJECT, NULL); + g_object_set_data(wrapper, "pointer", group); + ret = pygobject_new(wrapper); + g_object_unref(wrapper); + return ret; + } + Py_RETURN_NONE; +} + + +static PyObject * +PluginBase_set_kb_item(GeanyPyPluginBase *self, PyObject *args, PyObject *kwargs) +{ + static gchar *kwlist[] = { "key_group", "key_id", "key", "mod", "name", "label", "menu_item", "callback", NULL }; + int id = -1; + int key; + int mod; + PyObject *py_group; + const gchar *name = NULL, *label = NULL; + PyObject *py_menu_item = NULL; + PyObject *py_callback = NULL; + + if (PyArg_ParseTupleAndKeywords(args, kwargs, "Oiiiss|OO", kwlist, + &py_group, &id, &key, &mod, &name, &label, &py_menu_item, &py_callback) && id >= 0) + { + GObject *group_wrapper = G_OBJECT(pygobject_get(py_group)); + GtkWidget *menu_item = (py_menu_item == NULL || py_menu_item == Py_None) + ? NULL : GTK_WIDGET(pygobject_get(py_menu_item)); + GeanyKeyGroup *group = g_object_get_data(group_wrapper, "pointer"); + if (PyCallable_Check(py_callback)) + { + Py_INCREF(py_callback); + keybindings_set_item_full(group, id, (guint) key, (GdkModifierType) mod, name, label, + menu_item, (GeanyKeyBindingFunc) call_key, py_callback, Py_DecRef); + } + else + keybindings_set_item(group, id, NULL, (guint) key, (GdkModifierType) mod, name, label, + menu_item); + } + Py_RETURN_NONE; +} + +static PyMethodDef +PluginBase_methods[] = { + { "set_key_group", (PyCFunction)PluginBase_set_kb_group, METH_KEYWORDS, + "Sets up a GeanyKeybindingGroup for this plugin." }, + { "set_key_item", (PyCFunction)PluginBase_set_kb_item, METH_KEYWORDS, + "Adds an action to one of the plugin's key groups" }, + { NULL } +}; + +static PyMethodDef +PluginModule_methods[] = { + { NULL } +}; + + +static PyGetSetDef +PluginBase_getseters[] = { + { NULL }, +}; + + +static int +PluginBase_init(GeanyPyPluginBase *self, PyObject *args, PyObject *kwargs) +{ + GeanyPyPluginBase *py_context; + + if (PyArg_ParseTuple(args, "O", (PyObject **) &py_context) && (PyObject *)py_context != Py_None) + self->plugin = py_context->plugin; + + return 0; +} + + +static PyTypeObject PluginBaseType = { + PyObject_HEAD_INIT(NULL) + 0, /* ob_size */ + "geany.pluginbase,PluginBase", /* tp_name */ + sizeof(GeanyPyPluginBase), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor) PluginBase_dealloc, /* tp_dealloc */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* tp_print - tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ + "Wrapper around a GeanyPlugin structure." ,/* tp_doc */ + 0, 0, 0, 0, 0, 0, /* tp_traverse - tp_iternext */ + PluginBase_methods, /* tp_methods */ + 0, /* tp_members */ + PluginBase_getseters, /* tp_getset */ + 0, 0, 0, 0, 0, /* tp_base - tp_dictoffset */ + (initproc) PluginBase_init, /* tp_init */ + 0, 0, /* tp_alloc - tp_new */ +}; + + +PyMODINIT_FUNC initpluginbase(void) +{ + PyObject *m; + + PluginBaseType.tp_new = PyType_GenericNew; + if (PyType_Ready(&PluginBaseType) < 0) + return; + + m = Py_InitModule3("geany.pluginbase", PluginModule_methods, + "Plugin management."); + + Py_INCREF(&PluginBaseType); + PyModule_AddObject(m, "PluginBase", (PyObject *)&PluginBaseType); } diff --git a/geanypy/src/geanypy-plugin.h b/geanypy/src/geanypy-plugin.h index 5457404d8..dea777c45 100644 --- a/geanypy/src/geanypy-plugin.h +++ b/geanypy/src/geanypy-plugin.h @@ -26,16 +26,18 @@ extern "C" { #endif - -extern GeanyPlugin *geany_plugin; -extern GeanyData *geany_data; -extern GeanyFunctions *geany_functions; +extern GeanyData *geany_data; #ifndef PyMODINIT_FUNC #define PyMODINIT_FUNC void #endif +typedef struct +{ + PyObject_HEAD + GeanyPlugin *plugin; +} GeanyPyPluginBase; #ifdef __cplusplus } /* extern "C" */ diff --git a/geanypy/src/geanypy-signalmanager.c b/geanypy/src/geanypy-signalmanager.c index 0f7fd5ee7..8682da816 100644 --- a/geanypy/src/geanypy-signalmanager.c +++ b/geanypy/src/geanypy-signalmanager.c @@ -88,6 +88,7 @@ GObject *signal_manager_get_gobject(SignalManager *signal_manager) static void signal_manager_connect_signals(SignalManager *man) { + GeanyPlugin *geany_plugin = man->geany_plugin; plugin_signal_connect(geany_plugin, NULL, "build-start", TRUE, G_CALLBACK(on_build_start), man); plugin_signal_connect(geany_plugin, NULL, "document-activate", TRUE, G_CALLBACK(on_document_activate), man); plugin_signal_connect(geany_plugin, NULL, "document-before-save", TRUE, G_CALLBACK(on_document_before_save), man);