1 /*
2  * plugin.c
3  *
4  * Copyright 2015 Thomas Martitz <kugel@rockbox.org>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19  * MA 02110-1301, USA.
20  */
21 
22 
23 #include "geanypy.h"
24 #include "geanypy-keybindings.h"
25 
26 #include <glib.h>
27 
call_key(gpointer * unused,guint key_id,gpointer data)28 static gboolean call_key(gpointer *unused, guint key_id, gpointer data)
29 {
30 	PyObject *callback = data;
31 	PyObject *args;
32 
33 	args = Py_BuildValue("(i)", key_id);
34 	PyObject_CallObject(callback, args);
35 	Py_DECREF(args);
36 }
37 
38 
39 /* plugin.py provides an OOP-style wrapper around this so call it like:
40  * class Foo(geany.Plugin):
41  *   def __init__(self):
42  *     self.set_key_group(...)
43  */
44 static PyObject *
Keybindings_set_key_group(PyObject * self,PyObject * args,PyObject * kwargs)45 Keybindings_set_key_group(PyObject *self, PyObject *args, PyObject *kwargs)
46 {
47 	static gchar *kwlist[] = { "plugin", "section_name", "count", "callback", NULL };
48 	int count = 0;
49 	const gchar *section_name = NULL;
50 	GeanyKeyGroup *group = NULL;
51 	PyObject *py_callback = NULL;
52 	PyObject *py_ret = Py_None;
53 	PyObject *py_plugin;
54 	gboolean has_cb = FALSE;
55 
56 	Py_INCREF(Py_None);
57 
58 	if (PyArg_ParseTupleAndKeywords(args, kwargs, "Osi|O", kwlist,
59 		&py_plugin, &section_name, &count, &py_callback))
60 	{
61 		GeanyPlugin *plugin = plugin_get(py_plugin);
62 		g_return_val_if_fail(plugin != NULL, Py_None);
63 
64 		has_cb = PyCallable_Check(py_callback);
65 		if (has_cb)
66 		{
67 			Py_INCREF(py_callback);
68 			group = plugin_set_key_group_full(plugin, section_name, count,
69 			                                  (GeanyKeyGroupFunc) call_key, py_callback,
70 			                                  (GDestroyNotify) Py_DecRef);
71 		}
72 		else
73 			group = plugin_set_key_group(plugin, section_name, count, NULL);
74 	}
75 
76 	if (group)
77 	{
78 		Py_DECREF(py_ret);
79 		py_ret = KeyGroup_new_with_geany_key_group(group, has_cb);
80 	}
81 
82 	return py_ret;
83 }
84 
85 
86 static PyObject *
KeyGroup_add_key_item(KeyGroup * self,PyObject * args,PyObject * kwargs)87 KeyGroup_add_key_item(KeyGroup *self, PyObject *args, PyObject *kwargs)
88 {
89 	static gchar *kwlist[] = { "name", "label", "callback", "key_id", "key", "mod" , "menu_item", NULL };
90 	int id = -1;
91 	int key = 0, mod = 0;
92 	const gchar *name = NULL, *label = NULL;
93 	PyObject *py_menu_item = NULL;
94 	PyObject *py_callback  = NULL;
95 	GeanyKeyBinding *item = NULL;
96 
97 	if (PyArg_ParseTupleAndKeywords(args, kwargs, "ss|OiiiO", kwlist,
98 		&name, &label, &py_callback, &id, &key, &mod, &py_menu_item))
99 	{
100 		if (id == -1)
101 			id = self->item_index;
102 
103 		GtkWidget *menu_item = (py_menu_item == NULL || py_menu_item == Py_None)
104 									? NULL : GTK_WIDGET(pygobject_get(py_menu_item));
105 		if (PyCallable_Check(py_callback))
106 		{
107 			Py_INCREF(py_callback);
108 			item = keybindings_set_item_full(self->kb_group, id, (guint) key,
109 			                                 (GdkModifierType) mod, name, label, menu_item,
110 			                                 (GeanyKeyBindingFunc) call_key, py_callback,
111 			                                 (GDestroyNotify) Py_DecRef);
112 		}
113 		else
114 		{
115 			if (!self->has_cb)
116 				g_warning("Either KeyGroup or the Keybinding must have a callback\n");
117 			else
118 				item = keybindings_set_item(self->kb_group, id, NULL, (guint) key,
119 				                            (GdkModifierType) mod, name, label, menu_item);
120 		}
121 		Py_XDECREF(py_menu_item);
122 
123 		self->item_index = id + 1;
124 	}
125 
126 	if (item)
127 	{
128 		/* Return a tuple containing the key group and the opaque GeanyKeyBinding pointer.
129 		 * This is in preparation of allowing chained calls like
130 		 * set_kb_group(X, 3).add_key_item().add_key_item().add_key_item()
131 		 * without losing access to the keybinding pointer (might become necessary for newer
132 		 * Geany APIs).
133 		 * Note that the plain tuple doesn't support the above yet, we've got to subclass it,
134 		 * but we are prepared without breaking sub-plugins */
135 		PyObject *ret = PyTuple_Pack(2, self, PyCapsule_New(item, "GeanyKeyBinding", NULL));
136 		return ret;
137 	}
138 	Py_RETURN_NONE;
139 }
140 
141 
142 static PyMethodDef
143 KeyGroup_methods[] = {
144 	{ "add_key_item",				(PyCFunction)KeyGroup_add_key_item,	METH_KEYWORDS,
145 		"Adds an action to the plugin's key group" },
146 	{ NULL }
147 };
148 
149 static PyMethodDef
150 Keybindings_methods[] = {
151 	{ "set_key_group",				(PyCFunction)Keybindings_set_key_group,	METH_KEYWORDS,
152 		"Sets up a GeanyKeybindingGroup for this plugin." },
153 	{ NULL }
154 };
155 
156 
157 static PyGetSetDef
158 KeyGroup_getseters[] = {
159 	{ NULL },
160 };
161 
162 
163 static void
KeyGroup_dealloc(KeyGroup * self)164 KeyGroup_dealloc(KeyGroup *self)
165 {
166 	g_return_if_fail(self != NULL);
167 	self->ob_type->tp_free((PyObject *) self);
168 }
169 
170 
171 static PyTypeObject KeyGroupType = {
172 	PyObject_HEAD_INIT(NULL)
173 	0,											/* ob_size */
174 	"geany.keybindings.KeyGroup",					/* tp_name */
175 	sizeof(KeyGroup),								/* tp_basicsize */
176 	0,											/* tp_itemsize */
177 	(destructor) KeyGroup_dealloc,				/* tp_dealloc */
178 	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,	/* tp_print - tp_as_buffer */
179 	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,	/* tp_flags */
180 	"Wrapper around a GeanyKeyGroup structure."	,/* tp_doc */
181 	0, 0, 0, 0, 0, 0,							/* tp_traverse - tp_iternext */
182 	KeyGroup_methods,							/* tp_methods */
183 	0,											/* tp_members */
184 	KeyGroup_getseters,							/* tp_getset */
185 	0, 0, 0, 0, 0,								/* tp_base - tp_dictoffset */
186 	0, 0, (newfunc) PyType_GenericNew,			/* tp_init - tp_alloc, tp_new */
187 };
188 
189 
initkeybindings(void)190 PyMODINIT_FUNC initkeybindings(void)
191 {
192 	PyObject *m;
193 
194 	if (PyType_Ready(&KeyGroupType) < 0)
195 		return;
196 
197 	m = Py_InitModule3("keybindings", Keybindings_methods, "Keybindings support.");
198 
199 	Py_INCREF(&KeyGroupType);
200 	PyModule_AddObject(m, "KeyGroup", (PyObject *)&KeyGroupType);
201 }
202 
KeyGroup_new_with_geany_key_group(GeanyKeyGroup * group,gboolean has_cb)203 PyObject *KeyGroup_new_with_geany_key_group(GeanyKeyGroup *group, gboolean has_cb)
204 {
205 	KeyGroup *ret = PyObject_New(KeyGroup, &KeyGroupType);
206 
207 	ret->kb_group = group;
208 	ret->has_cb = has_cb;
209 	ret->item_index = 0;
210 
211 	return (PyObject *) ret;
212 }
213 
214