medit/moo/mooedit/mooedit-lua-api.c
2008-09-07 00:15:07 -05:00

873 lines
22 KiB
C

/*
* mooedit-lua-api.c
*
* Copyright (C) 2004-2008 by Yevgen Muntyan <muntyan@tamu.edu>
*
* This file is part of medit. medit is free software; you can
* redistribute it and/or modify it under the terms of the
* GNU Lesser General Public License as published by the
* Free Software Foundation; either version 2.1 of the License,
* or (at your option) any later version.
*
* You should have received a copy of the GNU Lesser General Public
* License along with medit. If not, see <http://www.gnu.org/licenses/>.
*/
#define MOOEDIT_COMPILATION
#include "mooedit/mooedit-lua.h"
#include "mooedit/mooeditor.h"
#include "mooutils/mooutils-misc.h"
#include "mooutils/mootype-macros.h"
#include <string.h>
#include <glib/gprintf.h>
#include <gtk/gtk.h>
/********************************************************************/
/* GObject
*/
MOO_DEFINE_QUARK_STATIC (moo-lua-type, lua_type_quark)
#ifdef MOO_ENABLE_UNIT_TESTS
static int object_count;
#endif
static GObject **
check_gobject (lua_State *L)
{
return luaL_checkudata (L, 1, "medit.GObject");
}
static gpointer
get_gobject (lua_State *L,
int arg,
GType type)
{
GObject **ptr = check_gobject (L);
if (!G_TYPE_CHECK_INSTANCE_TYPE (*ptr, type))
luaL_error (L, "arg %d is not a %s", arg, g_type_name (type));
return *ptr;
}
#define GET_GOBJECT(L) G_OBJECT (get_gobject (L, 1, G_TYPE_OBJECT))
static int
cfunc_GObject__gc (lua_State *L)
{
GObject **op = check_gobject (L);
if (*op)
{
g_object_unref (*op);
*op = NULL;
#ifdef MOO_ENABLE_UNIT_TESTS
object_count -= 1;
#endif
}
return 0;
}
static int
cfunc_GObject__tostring (lua_State *L)
{
GObject *object;
object = GET_GOBJECT (L);
lua_pushfstring (L, "<%s at %p>",
g_type_name (G_OBJECT_TYPE (object)),
(gpointer) object);
return 1;
}
static void
gvalue_from_lua (lua_State *L,
int arg,
GValue *val)
{
switch (G_TYPE_FUNDAMENTAL (val->g_type))
{
case G_TYPE_CHAR:
g_value_set_char (val, lua_tointeger (L, arg));
break;
case G_TYPE_UCHAR:
g_value_set_uchar (val, lua_tointeger (L, arg));
break;
case G_TYPE_BOOLEAN:
g_value_set_boolean (val, lua_toboolean (L, arg));
break;
case G_TYPE_INT:
g_value_set_int (val, lua_tointeger (L, arg));
break;
case G_TYPE_UINT:
g_value_set_uint (val, lua_tointeger (L, arg));
break;
case G_TYPE_LONG:
g_value_set_long (val, lua_tointeger (L, arg));
break;
case G_TYPE_ULONG:
g_value_set_ulong (val, lua_tointeger (L, arg));
break;
case G_TYPE_INT64:
g_value_set_int64 (val, lua_tointeger (L, arg));
break;
case G_TYPE_UINT64:
g_value_set_uint64 (val, lua_tointeger (L, arg));
break;
case G_TYPE_ENUM:
g_value_set_enum (val, lua_tointeger (L, arg));
break;
case G_TYPE_FLAGS:
g_value_set_flags (val, lua_tointeger (L, arg));
break;
case G_TYPE_FLOAT:
g_value_set_float (val, lua_tonumber (L, arg));
break;
case G_TYPE_DOUBLE:
g_value_set_double (val, lua_tonumber (L, arg));
break;
case G_TYPE_STRING:
if (lua_isnil (L, arg))
g_value_set_string (val, NULL);
else
g_value_set_string (val, lua_tostring (L, arg));
break;
case G_TYPE_OBJECT:
if (lua_isnil (L, arg))
g_value_set_object (val, NULL);
else
g_value_set_object (val, get_gobject (L, arg, G_TYPE_OBJECT));
break;
default:
luaL_error (L, "unsupported value type '%s'",
g_type_name (val->g_type));
}
}
static int
gvalue_to_lua (lua_State *L,
const GValue *val)
{
const char *s;
GObject *o;
switch (G_TYPE_FUNDAMENTAL (val->g_type))
{
case G_TYPE_CHAR:
lua_pushnumber (L, g_value_get_char (val));
break;
case G_TYPE_UCHAR:
lua_pushnumber (L, g_value_get_uchar (val));
break;
case G_TYPE_BOOLEAN:
lua_pushboolean (L, g_value_get_boolean (val));
break;
case G_TYPE_INT:
lua_pushnumber (L, g_value_get_int (val));
break;
case G_TYPE_UINT:
lua_pushnumber (L, g_value_get_uint (val));
break;
case G_TYPE_LONG:
lua_pushnumber (L, g_value_get_long (val));
break;
case G_TYPE_ULONG:
lua_pushnumber (L, g_value_get_ulong (val));
break;
case G_TYPE_INT64:
lua_pushnumber (L, g_value_get_int64 (val));
break;
case G_TYPE_UINT64:
lua_pushnumber (L, g_value_get_uint64 (val));
break;
case G_TYPE_ENUM:
lua_pushnumber (L, g_value_get_enum (val));
break;
case G_TYPE_FLAGS:
lua_pushnumber (L, g_value_get_flags (val));
break;
case G_TYPE_FLOAT:
lua_pushnumber (L, g_value_get_float (val));
break;
case G_TYPE_DOUBLE:
lua_pushnumber (L, g_value_get_double (val));
break;
case G_TYPE_STRING:
if ((s = g_value_get_string (val)))
lua_pushstring (L, s);
else
lua_pushnil (L);
break;
case G_TYPE_OBJECT:
if ((o = g_value_get_object (val)))
_moo_lua_push_object (L, o);
else
lua_pushnil (L);
break;
default:
luaL_error (L, "unsupported value type '%s'",
g_type_name (val->g_type));
}
return 1;
}
static int
cfunc_GObject_set_property (lua_State *L)
{
GObject *object;
const char *propname;
GParamSpec *pspec;
GValue val = {0};
object = GET_GOBJECT (L);
propname = luaL_checkstring (L, 2);
luaL_checkany (L, 3);
pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (object), propname);
if (!pspec)
luaL_error (L, "no property named '%s' in object '<%s at %p>'",
propname, g_type_name (G_OBJECT_TYPE (object)),
(gpointer) object);
g_value_init (&val, pspec->value_type);
gvalue_from_lua (L, 3, &val);
g_object_set_property (object, propname, &val);
g_value_unset (&val);
return 0;
}
static int
cfunc_GObject_get_property (lua_State *L)
{
GObject *object;
const char *propname;
GParamSpec *pspec;
GValue val = {0};
int n_ret;
object = GET_GOBJECT (L);
propname = luaL_checkstring (L, 2);
pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (object), propname);
if (!pspec)
luaL_error (L, "no property named '%s' in object '<%s at %p>'",
propname, g_type_name (G_OBJECT_TYPE (object)),
(gpointer) object);
g_value_init (&val, pspec->value_type);
g_object_get_property (object, propname, &val);
n_ret = gvalue_to_lua (L, &val);
g_value_unset (&val);
return n_ret;
}
static int
cfunc_GObject__index (lua_State *L)
{
GObject *object;
GType type;
object = GET_GOBJECT (L);
luaL_checkany (L, 2);
for (type = G_OBJECT_TYPE (object); ; type = g_type_parent (type))
{
const luaL_Reg *meths;
if ((meths = g_type_get_qdata (type, lua_type_quark ())))
{
char *tname;
tname = g_strdup_printf ("medit.%s", g_type_name (type));
if (luaL_newmetatable (L, tname))
luaL_register (L, NULL, meths);
g_free (tname);
lua_pushvalue (L, 2);
lua_rawget (L, -2);
if (!lua_isnil (L, -1))
return 1;
lua_pop (L, 1);
}
if (type == G_TYPE_OBJECT)
break;
}
return 0;
}
static const luaL_Reg meths_GObject[] = {
{ "__gc", cfunc_GObject__gc },
{ "__tostring", cfunc_GObject__tostring },
{ "__index", cfunc_GObject__index },
{ "set_property", cfunc_GObject_set_property },
{ "get_property", cfunc_GObject_get_property },
{ NULL, NULL }
};
#define METH_VOID_VOID(Typ,GET_OBJ,name,func) \
static int \
cfunc_##Typ##_##name (lua_State *L) \
{ \
func (GET_OBJ (L)); \
return 0; \
}
#define METH_LREG(Type,name) { #name, cfunc_##Type##_##name }
#define METH(Type,name) static int cfunc_##Type##_##name (lua_State *L)
/********************************************************************/
/* GtkWidget
*/
#define GET_WIDGET(L) GTK_WIDGET (get_gobject (L, 1, GTK_TYPE_WIDGET))
// METH_VOID_VOID (GtkWidget, GET_WIDGET, hide, gtk_widget_hide)
// METH_VOID_VOID (GtkWidget, GET_WIDGET, show, gtk_widget_show)
static const luaL_Reg meths_GtkWidget[] = {
{ NULL, NULL }
};
/********************************************************************/
/* GtkTextView
*/
#define GET_TEXT_VIEW(L) GTK_TEXT_VIEW (get_gobject (L, 1, GTK_TYPE_TEXT_VIEW))
#define GET_BUFFER(L) GtkTextBuffer *buffer = gtk_text_view_get_buffer (GET_TEXT_VIEW (L));
static void
push_offset (lua_State *L,
int c_offset)
{
lua_pushinteger (L, c_offset + 1);
}
static void
push_iter_offset (lua_State *L,
const GtkTextIter *iter)
{
push_offset (L, gtk_text_iter_get_offset (iter));
}
static int
check_offset (lua_State *L,
int arg)
{
return luaL_checkinteger (L, arg) - 1;
}
static int
check_opt_offset (lua_State *L,
int arg,
int dflt)
{
return luaL_optinteger (L, arg, dflt + 1) - 1;
}
METH (GtkTextView, get_cursor)
{
GET_BUFFER (L);
GtkTextIter iter;
gtk_text_buffer_get_iter_at_mark (buffer, &iter,
gtk_text_buffer_get_insert (buffer));
push_iter_offset (L, &iter);
return 1;
}
METH (GtkTextView, get_selection_bound)
{
GET_BUFFER (L);
GtkTextIter iter;
gtk_text_buffer_get_iter_at_mark (buffer, &iter,
gtk_text_buffer_get_selection_bound (buffer));
push_iter_offset (L, &iter);
return 1;
}
METH (GtkTextView, get_selection)
{
GET_BUFFER (L);
GtkTextIter start, end;
if (gtk_text_buffer_get_selection_bounds (buffer, &start, &end))
{
push_iter_offset (L, &start);
push_iter_offset (L, &end);
return 2;
}
else
{
lua_pushnil (L);
return 1;
}
}
METH (GtkTextView, set_cursor)
{
GET_BUFFER (L);
int pos = check_offset (L, 2);
GtkTextIter iter;
gtk_text_buffer_get_iter_at_offset (buffer, &iter, pos);
gtk_text_buffer_place_cursor (buffer, &iter);
return 0;
}
METH (GtkTextView, select_range)
{
GET_BUFFER (L);
int sb_pos = check_offset (L, 2);
int ins_pos = check_opt_offset (L, 3, -1);
GtkTextIter sb, ins;
gtk_text_buffer_get_iter_at_offset (buffer, &sb, sb_pos);
gtk_text_buffer_get_iter_at_offset (buffer, &ins, ins_pos);
gtk_text_buffer_select_range (buffer, &ins, &sb);
return 0;
}
METH (GtkTextView, unselect)
{
GET_BUFFER (L);
GtkTextIter iter;
gtk_text_buffer_get_iter_at_mark (buffer, &iter,
gtk_text_buffer_get_insert (buffer));
gtk_text_buffer_place_cursor (buffer, &iter);
return 0;
}
METH (GtkTextView, scroll_to_cursor)
{
GtkTextView *textview = GET_TEXT_VIEW (L);
GtkTextBuffer *buffer = gtk_text_view_get_buffer (textview);
gtk_text_view_scroll_mark_onscreen (textview,
gtk_text_buffer_get_insert (buffer));
return 0;
}
METH (GtkTextView, delete_selection)
{
GET_BUFFER (L);
gtk_text_buffer_delete_selection (buffer, FALSE, TRUE);
return 0;
}
METH (GtkTextView, delete_range)
{
GET_BUFFER (L);
int start_pos = check_offset (L, 2);
int end_pos = check_opt_offset (L, 3, -1);
GtkTextIter start, end;
gtk_text_buffer_get_iter_at_offset (buffer, &start, start_pos);
gtk_text_buffer_get_iter_at_offset (buffer, &end, end_pos);
gtk_text_buffer_delete (buffer, &start, &end);
return 0;
}
METH (GtkTextView, get_text)
{
GET_BUFFER (L);
int start_pos = check_opt_offset (L, 2, 0);
int end_pos = check_opt_offset (L, 3, -1);
char *text;
GtkTextIter start, end;
gtk_text_buffer_get_iter_at_offset (buffer, &start, start_pos);
gtk_text_buffer_get_iter_at_offset (buffer, &end, end_pos);
text = gtk_text_buffer_get_slice (buffer, &start, &end, TRUE);
lua_take_utf8string (L, text);
return 1;
}
METH (GtkTextView, get_selected_text)
{
GET_BUFFER (L);
GtkTextIter start, end;
if (gtk_text_buffer_get_selection_bounds (buffer, &start, &end))
{
char *text = gtk_text_buffer_get_slice (buffer, &start, &end, TRUE);
lua_take_utf8string (L, text);
}
else
{
lua_pushnil (L);
}
return 1;
}
METH (GtkTextView, replace_selection)
{
GET_BUFFER (L);
const char *text = lua_check_utf8string (L, 2, NULL);
GtkTextIter start, end;
if (gtk_text_buffer_get_selection_bounds (buffer, &start, &end))
gtk_text_buffer_delete (buffer, &start, &end);
gtk_text_buffer_insert (buffer, &start, text, -1);
return 0;
}
METH (GtkTextView, insert_text)
{
GET_BUFFER (L);
const char *text;
GtkTextIter iter;
int pos;
text = lua_check_utf8string (L, 2, NULL);
pos = check_opt_offset (L, 3, G_MININT);
if (pos == G_MININT)
gtk_text_buffer_get_iter_at_mark (buffer, &iter, gtk_text_buffer_get_insert (buffer));
else
gtk_text_buffer_get_iter_at_offset (buffer, &iter, pos);
gtk_text_buffer_insert (buffer, &iter, text, -1);
return 0;
}
METH (GtkTextView, set_text)
{
GET_BUFFER (L);
size_t len;
const char *text = lua_check_utf8string (L, 2, &len);
gtk_text_buffer_set_text (buffer, text, len);
return 0;
}
METH (GtkTextView, get_char_count)
{
GET_BUFFER (L);
lua_pushinteger (L, gtk_text_buffer_get_char_count (buffer));
return 1;
}
METH (GtkTextView, get_line_count)
{
GET_BUFFER (L);
lua_pushinteger (L, gtk_text_buffer_get_line_count (buffer));
return 1;
}
METH (GtkTextView, get_pos_at_line)
{
GET_BUFFER (L);
GtkTextIter iter = {0};
int line = check_offset (L, 2);
gtk_text_buffer_get_iter_at_line (buffer, &iter, line);
push_iter_offset (L, &iter);
if (!gtk_text_iter_ends_line (&iter))
gtk_text_iter_forward_to_line_end (&iter);
push_iter_offset (L, &iter);
gtk_text_iter_forward_line (&iter);
push_iter_offset (L, &iter);
return 3;
}
METH (GtkTextView, get_line_at_pos)
{
GET_BUFFER (L);
GtkTextIter iter = {0};
int pos = check_offset (L, 2);
gtk_text_buffer_get_iter_at_offset (buffer, &iter, pos);
push_offset (L, gtk_text_iter_get_line (&iter));
return 1;
}
METH (GtkTextView, get_line_at_cursor)
{
GET_BUFFER (L);
GtkTextIter iter;
gtk_text_buffer_get_iter_at_mark (buffer, &iter,
gtk_text_buffer_get_insert (buffer));
push_offset (L, gtk_text_iter_get_line (&iter));
return 1;
}
METH (GtkTextView, get_line_text)
{
GET_BUFFER (L);
GtkTextIter start, end;
char *text;
int line = check_opt_offset (L, 2, G_MININT);
if (line == G_MININT)
{
gtk_text_buffer_get_iter_at_mark (buffer, &start,
gtk_text_buffer_get_insert (buffer));
gtk_text_iter_set_line_offset (&start, 0);
}
else
gtk_text_buffer_get_iter_at_line (buffer, &start, line);
end = start;
if (!gtk_text_iter_ends_line (&end))
gtk_text_iter_forward_to_line_end (&end);
text = gtk_text_buffer_get_slice (buffer, &start, &end, TRUE);
lua_pushstring (L, text);
g_free (text);
return 1;
}
METH (GtkTextView, next_pos)
{
GET_BUFFER (L);
GtkTextIter iter;
int pos = check_offset (L, 2);
gboolean ret;
gtk_text_buffer_get_iter_at_offset (buffer, &iter, pos);
ret = gtk_text_iter_forward_cursor_position (&iter);
push_iter_offset (L, &iter);
lua_pushboolean (L, ret);
return 2;
}
METH (GtkTextView, prev_pos)
{
GET_BUFFER (L);
GtkTextIter iter;
int pos = check_offset (L, 2);
gboolean ret;
gtk_text_buffer_get_iter_at_offset (buffer, &iter, pos);
ret = gtk_text_iter_backward_cursor_position (&iter);
push_iter_offset (L, &iter);
lua_pushboolean (L, ret);
return 2;
}
METH (GtkTextView, get_end_pos)
{
GET_BUFFER (L);
push_offset (L, gtk_text_buffer_get_char_count (buffer));
return 1;
}
METH (GtkTextView, is_end_pos)
{
GET_BUFFER (L);
int pos = check_offset (L, 2);
lua_pushboolean (L, pos == gtk_text_buffer_get_char_count (buffer));
return 1;
}
METH (GtkTextView, is_cursor_pos)
{
GET_BUFFER (L);
GtkTextIter iter;
int pos = check_offset (L, 2);
gtk_text_buffer_get_iter_at_offset (buffer, &iter, pos);
lua_pushboolean (L, gtk_text_iter_is_cursor_position (&iter));
return 1;
}
static const luaL_Reg meths_GtkTextView[] = {
METH_LREG (GtkTextView, get_cursor),
METH_LREG (GtkTextView, get_selection_bound),
METH_LREG (GtkTextView, get_selection),
METH_LREG (GtkTextView, set_cursor),
METH_LREG (GtkTextView, select_range),
METH_LREG (GtkTextView, unselect),
METH_LREG (GtkTextView, scroll_to_cursor),
METH_LREG (GtkTextView, next_pos),
METH_LREG (GtkTextView, prev_pos),
METH_LREG (GtkTextView, is_cursor_pos),
METH_LREG (GtkTextView, is_end_pos),
METH_LREG (GtkTextView, get_end_pos),
METH_LREG (GtkTextView, get_char_count),
METH_LREG (GtkTextView, get_line_count),
METH_LREG (GtkTextView, get_pos_at_line),
METH_LREG (GtkTextView, get_line_at_pos),
METH_LREG (GtkTextView, get_line_at_cursor),
METH_LREG (GtkTextView, get_line_text),
METH_LREG (GtkTextView, delete_selection),
METH_LREG (GtkTextView, delete_range),
METH_LREG (GtkTextView, get_text),
METH_LREG (GtkTextView, get_selected_text),
METH_LREG (GtkTextView, replace_selection),
METH_LREG (GtkTextView, insert_text),
METH_LREG (GtkTextView, set_text),
{ NULL, NULL }
};
/********************************************************************/
/* MooTextView
*/
#define GET_MOO_TEXT_VIEW(L) MOO_TEXT_VIEW (get_gobject (L, 1, MOO_TYPE_TEXT_VIEW))
static const luaL_Reg meths_MooTextView[] = {
{ NULL, NULL }
};
/********************************************************************/
/* MooEdit
*/
#define GET_MOO_EDIT(L) MOO_EDIT (get_gobject (L, 1, MOO_TYPE_EDIT))
static const luaL_Reg meths_MooEdit[] = {
{ NULL, NULL }
};
/********************************************************************/
/* MooEditor
*/
#define GET_MOO_EDITOR(L) MOO_EDITOR (get_gobject (L, 1, MOO_TYPE_EDITOR))
static const luaL_Reg meths_MooEditor[] = {
{ NULL, NULL }
};
/********************************************************************/
/* External API
*/
static void
register_type (GType type,
const luaL_Reg *meths)
{
if (meths && meths[0].name)
g_type_set_qdata (type, lua_type_quark (), (gpointer) meths);
}
static void
init_types (void)
{
static gboolean been_here;
if (been_here)
return;
been_here = TRUE;
register_type (G_TYPE_OBJECT, meths_GObject);
register_type (GTK_TYPE_WIDGET, meths_GtkWidget);
register_type (GTK_TYPE_TEXT_VIEW, meths_GtkTextView);
register_type (MOO_TYPE_TEXT_VIEW, meths_MooTextView);
register_type (MOO_TYPE_EDIT, meths_MooEdit);
register_type (MOO_TYPE_EDITOR, meths_MooEditor);
}
void
_moo_lua_push_object (lua_State *L,
GObject *object)
{
GObject **object_ptr;
init_types ();
object_ptr = lua_newuserdata (L, sizeof (gpointer));
MOO_OBJECT_REF_SINK (object);
*object_ptr = object;
#ifdef MOO_ENABLE_UNIT_TESTS
object_count += 1;
#endif
if (luaL_newmetatable (L, "medit.GObject"))
luaL_register (L, NULL, meths_GObject);
lua_setmetatable (L, -2);
}
#ifdef MOO_ENABLE_UNIT_TESTS
#include "moolua/moo-tests-lua.h"
#include <mooedit/mooedit-tests.h>
static int
cfunc_present_widget (lua_State *L)
{
GtkWidget *window;
GtkWidget *widget;
widget = GET_WIDGET (L);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_widget_set_size_request (window, 300, 300);
gtk_container_add (GTK_CONTAINER (window), widget);
gtk_widget_show_all (window);
g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL);
gtk_main ();
return 0;
}
static int
cfunc_new_text_view (lua_State *L)
{
GtkWidget *textview = gtk_text_view_new ();
_moo_lua_push_object (L, G_OBJECT (textview));
return 1;
}
static const luaL_reg mooedittestlib[] = {
{ "new_text_view", cfunc_new_text_view },
{ "present", cfunc_present_widget },
{ NULL, NULL }
};
static void
add_test_api (lua_State *L)
{
luaL_register (L, "mooedittest", mooedittestlib);
lua_pop (L, 1);
}
static void
test_gtk_text_view (void)
{
TEST_ASSERT (object_count == 0);
moo_test_run_lua_file ("textview.lua", add_test_api, NULL);
TEST_ASSERT (object_count == 0);
}
static void
test_gobject (void)
{
TEST_ASSERT (object_count == 0);
moo_test_run_lua_file ("gobject.lua", add_test_api, NULL);
TEST_ASSERT (object_count == 0);
}
static void
test_moo_edit (void)
{
TEST_ASSERT (object_count == 0);
moo_test_run_lua_file ("mooedit.lua", add_test_api, NULL);
TEST_ASSERT (object_count == 0);
}
void
moo_test_mooedit_lua_api (void)
{
MooTestSuite *suite;
suite = moo_test_suite_new ("mooedit/mooedit-lua-api.c", NULL, NULL, NULL);
moo_test_suite_add_test (suite, "test of GObject",
(MooTestFunc) test_gobject, NULL);
moo_test_suite_add_test (suite, "test of GtkTextView",
(MooTestFunc) test_gtk_text_view, NULL);
moo_test_suite_add_test (suite, "test of the editor",
(MooTestFunc) test_moo_edit, NULL);
}
#endif