[geany/geany-plugins] 3ef891: Merge pull request #384 from kugel-/geanypy-proxy
Frank Lanitz
git-noreply at xxxxx
Fri Mar 11 06:15:18 UTC 2016
Branch: refs/heads/master
Author: Frank Lanitz <frank at frank.uvena.de>
Committer: Frank Lanitz <frank at frank.uvena.de>
Date: Fri, 11 Mar 2016 06:15:18 UTC
Commit: 3ef8910f756bb6e6bc01693541f602266db43b43
https://github.com/geany/geany-plugins/commit/3ef8910f756bb6e6bc01693541f602266db43b43
Log Message:
-----------
Merge pull request #384 from kugel-/geanypy-proxy
Geanypy proxy and keybindings
Modified Paths:
--------------
geanypy/README
geanypy/doc/source/starting.rst
geanypy/geany/Makefile.am
geanypy/geany/__init__.py
geanypy/geany/loader.py
geanypy/geany/manager.py
geanypy/geany/plugin.py
geanypy/plugins/Makefile.am
geanypy/plugins/console.py
geanypy/src/Makefile.am
geanypy/src/geanypy-keybindings.c
geanypy/src/geanypy-keybindings.h
geanypy/src/geanypy-plugin.c
geanypy/src/geanypy-plugin.h
geanypy/src/geanypy-signalmanager.c
Modified: geanypy/README
11 lines changed, 5 insertions(+), 6 deletions(-)
===================================================================
@@ -33,14 +33,13 @@ module more "Pythonic".
To write a plugin, inherit from the ``geany.Plugin`` class and implmenent the
required members (see ``geany/plugin.py`` documentation comments). Then put the
plugin in a searched plugin path. Currently two locations are search for
-plugins. The first is ``PREFIX/share/geany/geanypy/plugins`` and the recommended
+plugins. The first is ``PREFIX/lib/geany`` and the recommended
location is under your personal Geany directory (usually
-``~/.config/geany/plugins/geanypy/plugins``). To load or unload plugins, click
-the Python Plugin Manager item under the Tools menu which will appear when you
-activate GeanyPy through Geany's regular plugin manager.
+``~/.config/geany/plugins``). To load or unload plugins, use Geany's regular Plugin
++Manager. Python plugins appear there once GeanyPy is activated.
-When the GeanyPy plugin is loaded, it has an option to add a new tab to
-the notebook in the message window area that contains an interactive
+When the Python Console plugin is enabled, it will add a new tab to the notebook in
+the message window area that contains an interactive Python shell with the `geany`
Python shell with the ``geany`` module pre-imported. You can tinker
around with API with this console, for example::
Modified: geanypy/doc/source/starting.rst
40 lines changed, 18 insertions(+), 22 deletions(-)
===================================================================
@@ -7,34 +7,30 @@ GeanyPy, it's important to note how it works and some features it provides.
What the heck is GeanyPy, really?
=================================
-GeanyPy is "just another Geany plugin", really. Geany sees GeanyPy as any
-other `plugin <http://www.geany.org/manual/current/index.html#plugins>`_, so
-to activate GeanyPy, use Geany's
+GeanyPy is a proxy plugin. Geany initially sees GeanyPy as any other
+`plugin <http://www.geany.org/manual/current/index.html#plugins>`_, but
+GeanyPy registers some additional stuff that enables Geany to load python plugins
+through GeanyPy. So to activate, use Geany's
`Plugin Manager <http://www.geany.org/manual/current/index.html#plugin-manager>`_
under the Tools menu as you would for any other plugin.
-Once the GeanyPy plugin has been activated, a few elements are added to Geany's
-user interface as described below.
+Once the GeanyPy plugin has been activated, Geany should rescan the plugin
+directories and pick those up that are supported through GeanyPy. It'll integrate
+the python plugins into the Plugin Manager in an additional hierarchy level below
+GeanyPy.
-Python Plugin Manager
-=====================
+* [ ] Geany plugin 1
+* [x] GeanyPy
+ * [ ] Python plugin 1
+ * [x] Python plugin 2
+ * [ ] Python plugin 3
+* [ ] Geany plugin 3
-Under the Tools menu, you will find the Python Plugin Manager, which is meant
-to be similar to Geany's own Plugin Manager. This is where you will activate
-any plugins written in Python.
+Remember that Geany looks in three places for plugins:
-The Python Plugin Manager looks in exactly two places for plugins:
-
-1. For system-wide plugins, it will search in PREFIX/share/geany/geanypy/plugins.
-2. In Geany's config directory under your home directory, typically ~/.config/geany/plugins/geanypy/plugins.
-
-Where `PREFIX` is the prefix used at configure time with Geany/GeanyPy (see
-the previous section, Installation). Both of these paths may vary depending on
-your platform, but for most \*nix systems, the above paths should hold true.
-
-Any plugins which follow the proper interface found in either of those two
-directories will be listed in the Python Plugin Manager and you will be able
-to activate and deactivate them there.
+1. For system-wide plugins, it will search in (usually) /usr/share/geany or /usr/local/share/geany.
+2. In Geany's config directory under your home directory, typically ~/.config/geany/plugins.
+3. A user-configurable plugin directory (useful during plugin development).
Python Console
==============
Modified: geanypy/geany/Makefile.am
2 lines changed, 0 insertions(+), 2 deletions(-)
===================================================================
@@ -1,7 +1,5 @@
geanypy_sources = __init__.py \
console.py \
- manager.py \
- loader.py \
plugin.py \
signalmanager.py
geanypy_objects = $(geanypy_sources:.py=.pyc)
Modified: geanypy/geany/__init__.py
4 lines changed, 2 insertions(+), 2 deletions(-)
===================================================================
@@ -15,9 +15,7 @@
import encoding
import filetypes
import highlighting
-import loader
import main
-import manager
import msgwindow
import navqueue
import prefs
@@ -26,6 +24,7 @@
import search
import templates
import ui_utils
+import keybindings
from app import App
from prefs import Prefs, ToolPrefs
@@ -43,6 +42,7 @@
"main_widgets",
"interface_prefs",
"app",
+ "keybindings",
"general_prefs",
"search_prefs",
"template_prefs",
Modified: geanypy/geany/loader.py
172 lines changed, 0 insertions(+), 172 deletions(-)
===================================================================
@@ -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
Modified: geanypy/geany/manager.py
179 lines changed, 0 insertions(+), 179 deletions(-)
===================================================================
@@ -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('<big><b>%s</b></big> <small>%s</small>\n%s\n' +
- '<small><b>Author:</b> %s\n' +
- '<b>Filename:</b> %s</small>') % (
- 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)
Modified: geanypy/geany/plugin.py
16 lines changed, 11 insertions(+), 5 deletions(-)
===================================================================
@@ -30,12 +30,12 @@ def cleanup(self):
The guts of the API are exposed to plugins through the `geany` package and
its modules.
-Plugins should be placed in either the system plugin directory (something
-like /usr/local/share/geany/geanypy/plugins) or in their personal plugin
-directory (something like ~/.config/geany/plugins/geanypy/plugins). Only
-files with a `.py` extension will be loaded.
+Plugins should be placed in either the system plugin directory (something like
+/usr/local/lib/geany) or in the user plugin directory (something like
+~/.config/geany/plugins). Only files with a `.py` extension will be loaded.
"""
+import keybindings
class Plugin(object):
"""
@@ -49,7 +49,6 @@ class Plugin(object):
#__plugin_version__ = None
#__plugin_author__ = None
-
_events = {
"document-open": [],
# TODO: add more events here
@@ -121,3 +120,10 @@ def author(self):
return self.__plugin_author__
else:
return ""
+
+ def set_key_group(self, section_name, count, callback = None):
+ """
+ Sets up a GeanyKeyGroup for this plugin. You can use that group to add keybindings
+ with group.add_key_item().
+ """
+ return keybindings.set_key_group(self, section_name, count, callback)
Modified: geanypy/plugins/Makefile.am
2 lines changed, 1 insertions(+), 1 deletions(-)
===================================================================
@@ -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)
Modified: geanypy/plugins/console.py
16 lines changed, 2 insertions(+), 14 deletions(-)
===================================================================
@@ -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
Modified: geanypy/src/Makefile.am
3 lines changed, 2 insertions(+), 1 deletions(-)
===================================================================
@@ -6,7 +6,7 @@ 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\"" \
-DG_LOG_DOMAIN=\"GeanyPy\"
geanypy_la_CFLAGS = @GEANYPY_CFLAGS@ @GMODULE_CFLAGS@
geanypy_la_LIBADD = @GEANY_LIBS@ @PYGTK_LIBS@ \
@@ -23,6 +23,7 @@ geanypy_la_SOURCES = geanypy-app.c \
geanypy-highlighting.c \
geanypy-indentprefs.c \
geanypy-interfaceprefs.c \
+ geanypy-keybindings.c \
geanypy-main.c \
geanypy-mainwidgets.c \
geanypy-msgwindow.c \
Modified: geanypy/src/geanypy-keybindings.c
213 lines changed, 213 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,213 @@
+/*
+ * plugin.c
+ *
+ * Copyright 2015 Thomas Martitz <kugel at rockbox.org>
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ */
+
+
+#include "geanypy.h"
+#include "geanypy-keybindings.h"
+
+#include <glib.h>
+
+static gboolean call_key(gpointer *unused, guint key_id, gpointer data)
+{
+ PyObject *callback = data;
+ PyObject *args;
+
+ args = Py_BuildValue("(i)", key_id);
+ PyObject_CallObject(callback, args);
+ Py_DECREF(args);
+}
+
+
+/* plugin.py provides an OOP-style wrapper around this so call it like:
+ * class Foo(geany.Plugin):
+ * def __init__(self):
+ * self.set_key_group(...)
+ */
+static PyObject *
+Keybindings_set_key_group(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ static gchar *kwlist[] = { "plugin", "section_name", "count", "callback", NULL };
+ int count = 0;
+ const gchar *section_name = NULL;
+ GeanyKeyGroup *group = NULL;
+ PyObject *py_callback = NULL;
+ PyObject *py_ret = Py_None;
+ PyObject *py_plugin;
+ gboolean has_cb = FALSE;
+
+ Py_INCREF(Py_None);
+
+ if (PyArg_ParseTupleAndKeywords(args, kwargs, "Osi|O", kwlist,
+ &py_plugin, §ion_name, &count, &py_callback))
+ {
+ GeanyPlugin *plugin = plugin_get(py_plugin);
+ g_return_val_if_fail(plugin != NULL, Py_None);
+
+ has_cb = PyCallable_Check(py_callback);
+ if (has_cb)
+ {
+ Py_INCREF(py_callback);
+ group = plugin_set_key_group_full(plugin, section_name, count,
+ (GeanyKeyGroupFunc) call_key, py_callback,
+ (GDestroyNotify) Py_DecRef);
+ }
+ else
+ group = plugin_set_key_group(plugin, section_name, count, NULL);
+ }
+
+ if (group)
+ {
+ Py_DECREF(py_ret);
+ py_ret = KeyGroup_new_with_geany_key_group(group, has_cb);
+ }
+
+ return py_ret;
+}
+
+
+static PyObject *
+KeyGroup_add_key_item(KeyGroup *self, PyObject *args, PyObject *kwargs)
+{
+ static gchar *kwlist[] = { "name", "label", "callback", "key_id", "key", "mod" , "menu_item", NULL };
+ int id = -1;
+ int key = 0, mod = 0;
+ const gchar *name = NULL, *label = NULL;
+ PyObject *py_menu_item = NULL;
+ PyObject *py_callback = NULL;
+ GeanyKeyBinding *item = NULL;
+
+ if (PyArg_ParseTupleAndKeywords(args, kwargs, "ss|OiiiO", kwlist,
+ &name, &label, &py_callback, &id, &key, &mod, &py_menu_item))
+ {
+ if (id == -1)
+ id = self->item_index;
+
+ GtkWidget *menu_item = (py_menu_item == NULL || py_menu_item == Py_None)
+ ? NULL : GTK_WIDGET(pygobject_get(py_menu_item));
+ if (PyCallable_Check(py_callback))
+ {
+ Py_INCREF(py_callback);
+ item = keybindings_set_item_full(self->kb_group, id, (guint) key,
+ (GdkModifierType) mod, name, label, menu_item,
+ (GeanyKeyBindingFunc) call_key, py_callback,
+ (GDestroyNotify) Py_DecRef);
+ }
+ else
+ {
+ if (!self->has_cb)
+ g_warning("Either KeyGroup or the Keybinding must have a callback\n");
+ else
+ item = keybindings_set_item(self->kb_group, id, NULL, (guint) key,
+ (GdkModifierType) mod, name, label, menu_item);
+ }
+ Py_XDECREF(py_menu_item);
+
+ self->item_index = id + 1;
+ }
+
+ if (item)
+ {
+ /* Return a tuple containing the key group and the opaque GeanyKeyBinding pointer.
+ * This is in preparation of allowing chained calls like
+ * set_kb_group(X, 3).add_key_item().add_key_item().add_key_item()
+ * without losing access to the keybinding pointer (might become necessary for newer
+ * Geany APIs).
+ * Note that the plain tuple doesn't support the above yet, we've got to subclass it,
+ * but we are prepared without breaking sub-plugins */
+ PyObject *ret = PyTuple_Pack(2, self, PyCapsule_New(item, "GeanyKeyBinding", NULL));
+ return ret;
+ }
+ Py_RETURN_NONE;
+}
+
+
+static PyMethodDef
+KeyGroup_methods[] = {
+ { "add_key_item", (PyCFunction)KeyGroup_add_key_item, METH_KEYWORDS,
+ "Adds an action to the plugin's key group" },
+ { NULL }
+};
+
+static PyMethodDef
+Keybindings_methods[] = {
+ { "set_key_group", (PyCFunction)Keybindings_set_key_group, METH_KEYWORDS,
+ "Sets up a GeanyKeybindingGroup for this plugin." },
+ { NULL }
+};
+
+
+static PyGetSetDef
+KeyGroup_getseters[] = {
+ { NULL },
+};
+
+
+static void
+KeyGroup_dealloc(KeyGroup *self)
+{
+ g_return_if_fail(self != NULL);
+ self->ob_type->tp_free((PyObject *) self);
+}
+
+
+static PyTypeObject KeyGroupType = {
+ PyObject_HEAD_INIT(NULL)
+ 0, /* ob_size */
+ "geany.keybindings.KeyGroup", /* tp_name */
+ sizeof(KeyGroup), /* tp_basicsize */
+ 0, /* tp_itemsize */
+ (destructor) KeyGroup_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 GeanyKeyGroup structure." ,/* tp_doc */
+ 0, 0, 0, 0, 0, 0, /* tp_traverse - tp_iternext */
+ KeyGroup_methods, /* tp_methods */
+ 0, /* tp_members */
+ KeyGroup_getseters, /* tp_getset */
+ 0, 0, 0, 0, 0, /* tp_base - tp_dictoffset */
+ 0, 0, (newfunc) PyType_GenericNew, /* tp_init - tp_alloc, tp_new */
+};
+
+
+PyMODINIT_FUNC initkeybindings(void)
+{
+ PyObject *m;
+
+ if (PyType_Ready(&KeyGroupType) < 0)
+ return;
+
+ m = Py_InitModule3("keybindings", Keybindings_methods, "Keybindings support.");
+
+ Py_INCREF(&KeyGroupType);
+ PyModule_AddObject(m, "KeyGroup", (PyObject *)&KeyGroupType);
+}
+
+PyObject *KeyGroup_new_with_geany_key_group(GeanyKeyGroup *group, gboolean has_cb)
+{
+ KeyGroup *ret = PyObject_New(KeyGroup, &KeyGroupType);
+
+ ret->kb_group = group;
+ ret->has_cb = has_cb;
+ ret->item_index = 0;
+
+ return (PyObject *) ret;
+}
+
Modified: geanypy/src/geanypy-keybindings.h
39 lines changed, 39 insertions(+), 0 deletions(-)
===================================================================
@@ -0,0 +1,39 @@
+/*
+ * geanypy-keybindings.h
+ *
+ * Copyright 2015 Thomas Martitz <kugel at rockbox.org>
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ */
+
+
+#ifndef GEANYPY_KEYBINDINGS_H
+#define GEANYPY_KEYBINDINGS_H
+
+#include <glib.h>
+
+typedef struct
+{
+ PyObject_HEAD
+ GeanyKeyGroup *kb_group;
+ gboolean has_cb;
+ gint item_index;
+} KeyGroup;
+
+extern PyObject *
+KeyGroup_new_with_geany_key_group(GeanyKeyGroup *group, gboolean has_cb);
+
+#endif /* GEANYPY_KEYBINDINGS_H */
Modified: geanypy/src/geanypy-plugin.c
383 lines changed, 258 insertions(+), 125 deletions(-)
===================================================================
@@ -26,26 +26,12 @@
#define INCLUDE_PYGOBJECT_ONCE_FULL
#include "geanypy.h"
+#include "geanypy-keybindings.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 <mbrush at codebrainz.ca>")
-
-
-static GtkWidget *loader_item = NULL;
-static PyObject *manager = NULL;
-static gchar *plugin_dir = NULL;
-static SignalManager *signal_manager = NULL;
+#include <glib.h>
+#include <glib/gstdio.h>
+GeanyData *geany_data;
/* Forward declarations to prevent compiler warnings. */
PyMODINIT_FUNC initapp(void);
@@ -64,6 +50,7 @@ PyMODINIT_FUNC initscintilla(void);
PyMODINIT_FUNC initsearch(void);
PyMODINIT_FUNC inittemplates(void);
PyMODINIT_FUNC initui_utils(void);
+PyMODINIT_FUNC initkeybindings(void);
static void
@@ -104,6 +91,7 @@ GeanyPy_start_interpreter(void)
initsearch();
inittemplates();
initui_utils();
+ initkeybindings();
#ifdef GEANYPY_WINDOWS
{ /* On windows, get path at runtime since we don't really know where
@@ -131,7 +119,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 +136,284 @@ 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;
- g_return_if_fail(dir != NULL);
+static gboolean has_error(void)
+{
+ if (PyErr_Occurred())
+ {
+ PyErr_Print();
+ return TRUE;
+ }
+ return FALSE;
+}
- 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;
- man = PyObject_GetAttrString(module, "PluginManager");
- Py_DECREF(module);
+ data->instance = PyObject_CallObject(data->class, NULL);
+ if (has_error())
+ return FALSE;
- if (man == NULL)
- {
- g_warning(_("Failed to retrieve PluginManager from manager module"));
- return;
- }
+ return TRUE;
+}
-#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;
- }
- }
-#else
- sys_plugin_dir = g_strdup(GEANYPY_PLUGIN_DIR);
-#endif
+static void geanypy_proxy_cleanup(GeanyPlugin *plugin, gpointer pdata)
+{
+ GeanyPyPluginData *data = (GeanyPyPluginData *) pdata;
- g_log(G_LOG_DOMAIN, G_LOG_LEVEL_INFO, "User plugins: %s", dir);
+ PyObject_CallMethod(data->instance, "cleanup", NULL);
+ if (has_error())
+ return;
+}
- if (sys_plugin_dir)
+
+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 != Py_None)
{
- 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);
+ /* 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
- 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;
- }
+ Py_DECREF(o); /* Must unref even if it's Py_None */
+ return NULL;
}
-static void
-GeanyPy_show_manager(void)
+static void do_show_configure(GtkWidget *button, gpointer pdata)
+{
+ GeanyPyPluginData *data = (GeanyPyPluginData *) pdata;
+ PyObject_CallMethod(data->instance, "show_configure", NULL);
+}
+
+
+static GtkWidget *geanypy_proxy_configure_legacy(GeanyPlugin *plugin, GtkDialog *parent, 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 *data = (GeanyPyPluginData *) pdata;
+ PyObject *o, *oparent;
+ GtkWidget *box, *label, *button, *align;
+ gchar *text;
+
+ /* This creates a simple page that has only one button to show the plugin's legacy configure
+ * dialog. It is for older plugins that implement show_configure(). It's not pretty but
+ * it provides basic backwards compatibility. */
+ box = gtk_vbox_new(FALSE, 2);
+
+ text = g_strdup_printf("The plugin \"%s\" is older and hasn't been updated\nto provide a configuration UI. However, it provides a dialog to\nallow you to change the plugin's preferences.", plugin->info->name);
+ label = gtk_label_new(text);
+
+ align = gtk_alignment_new(0, 0, 1, 1);
+ gtk_container_add(GTK_CONTAINER(align), label);
+ gtk_alignment_set_padding(GTK_ALIGNMENT(align), 0, 6, 2, 2);
+ gtk_box_pack_start(GTK_BOX(box), align, FALSE, FALSE, 0);
+
+ button = gtk_button_new_with_label("Open dialog");
+ align = gtk_alignment_new(0.5, 0, 0.3f, 1);
+ gtk_container_add(GTK_CONTAINER(align), button);
+ g_signal_connect(button, "clicked", (GCallback) do_show_configure, pdata);
+ gtk_box_pack_start(GTK_BOX(box), align, FALSE, TRUE, 0);
+
+ gtk_widget_show_all(box);
+ g_free(text);
+ return box;
+}
+
+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>.plugin file */
+ if (!g_file_test(file_plugin, G_FILE_TEST_EXISTS))
+ ret = PROXY_MATCHED;
+
+ g_free(file_plugin);
+ return ret;
+}
+
+
+static const gchar *string_from_attr(PyObject *o, const gchar *attr)
+{
+ PyObject *string = PyObject_GetAttrString(o, attr);
+ const gchar *ret = PyString_AsString(string);
+ Py_DECREF(string);
+
+ 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)
+ {
+ if (PyType_Check(val) && PyObject_IsSubclass(val, data->base))
+ found = val;
+ }
+
+ if (found)
+ {
+ GeanyPyPluginData *pdata = g_slice_new(GeanyPyPluginData);
+ PluginInfo *info = subplugin->info;
+ GeanyPluginFuncs *funcs = subplugin->funcs;
+ PyObject *caps = PyCapsule_New(subplugin, "GeanyPlugin", NULL);
+ Py_INCREF(found);
+ pdata->module = module;
+ pdata->class = found;
+ PyObject_SetAttrString(pdata->class, "__geany_plugin__", caps);
+ 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;
+ else if (PyObject_HasAttrString(found, "show_configure"))
+ funcs->configure = geanypy_proxy_configure_legacy;
+ 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
-on_python_plugin_loader_activate(GtkMenuItem *item, gpointer user_data)
+geanypy_unload(GeanyPlugin *plugin, GeanyPlugin *subplugin, gpointer load_data, gpointer pdata_)
{
- GeanyPy_show_manager();
+ GeanyPyPluginData *pdata = load_data;
+
+ Py_XDECREF(pdata->instance);
+ Py_DECREF(pdata->class);
+ Py_DECREF(pdata->module);
+ while (PyGC_Collect());
+ g_slice_free(GeanyPyPluginData, pdata);
}
-G_MODULE_EXPORT void
-plugin_init(GeanyData *data)
+static gboolean geanypy_init(GeanyPlugin *plugin_, gpointer pdata)
{
- 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);
+ 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;
}
-G_MODULE_EXPORT void plugin_cleanup(void)
+static void geanypy_cleanup(GeanyPlugin *plugin, gpointer pdata)
{
- signal_manager_free(signal_manager);
- Py_XDECREF(manager);
+ GeanyPyData *state = pdata;
+ signal_manager_free(state->signal_manager);
+ Py_DECREF(state->base);
GeanyPy_stop_interpreter();
- gtk_widget_destroy(loader_item);
- g_free(plugin_dir);
+}
+
+G_MODULE_EXPORT void
+geany_load_module(GeanyPlugin *plugin)
+{
+ 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 <mbrush at codebrainz.ca>";
+ plugin->funcs->init = geanypy_init;
+ plugin->funcs->cleanup = geanypy_cleanup;
+
+ GEANY_PLUGIN_REGISTER_FULL(plugin, 226, state, g_free);
}
Modified: geanypy/src/geanypy-plugin.h
12 lines changed, 8 insertions(+), 4 deletions(-)
===================================================================
@@ -26,16 +26,20 @@
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
+static inline GeanyPlugin *plugin_get(PyObject *self)
+{
+ PyObject *caps = PyObject_GetAttrString(self, "__geany_plugin__");
+ return PyCapsule_GetPointer(caps, "GeanyPlugin");
+}
+
+
#ifdef __cplusplus
} /* extern "C" */
Modified: geanypy/src/geanypy-signalmanager.c
1 lines changed, 1 insertions(+), 0 deletions(-)
===================================================================
@@ -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);
--------------
This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure).
More information about the Plugins-Commits
mailing list