[gjs/wip/gobj-kitchen-sink: 14/20] object: Introduce support for signals
- From: Jasper St. Pierre <jstpierre src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gjs/wip/gobj-kitchen-sink: 14/20] object: Introduce support for signals
- Date: Fri, 3 Feb 2012 16:54:50 +0000 (UTC)
commit 0603f4266e2b0a790b0017c6866afd03ca239401
Author: Giovanni Campagna <gcampagna src gnome org>
Date: Sun Dec 11 02:18:18 2011 +0100
object: Introduce support for signals
Introduce Gi.signal_new, which is a thin wrapper over g_signal_newv,
and then make GObject.Class read a map of signal descriptions as
parameters to the class.
Also, use g_signal_override_class_closure to connect "on_foo" to
the respective signals.
gi/object.c | 92 ++++++++++++++++
modules/overrides/GObject.js | 40 +++++++-
test/js/testGObjectClass.js | 236 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 366 insertions(+), 2 deletions(-)
---
diff --git a/gi/object.c b/gi/object.c
index a642f05..27618a3 100644
--- a/gi/object.c
+++ b/gi/object.c
@@ -2061,6 +2061,92 @@ gjs_register_property(JSContext *cx,
}
static JSBool
+gjs_signal_new(JSContext *cx,
+ uintN argc,
+ jsval *vp)
+{
+ jsval *argv = JS_ARGV(cx, vp);
+ JSObject *obj;
+ ObjectInstance *priv;
+ gchar *signal_name = NULL;
+ GSignalAccumulator accumulator;
+ gint signal_id;
+ guint i, n_parameters;
+ GType *params;
+ JSBool ret;
+
+ if (argc != 6)
+ return JS_FALSE;
+
+ JS_BeginRequest(cx);
+
+ if (!gjs_string_to_utf8(cx, argv[1], &signal_name)) {
+ ret = JS_FALSE;
+ goto out;
+ }
+
+ obj = JSVAL_TO_OBJECT(argv[0]);
+ priv = priv_from_js(cx, obj);
+
+ /* we only support standard accumulators for now */
+ switch (JSVAL_TO_INT(argv[3])) {
+ case 1:
+ accumulator = g_signal_accumulator_first_wins;
+ break;
+ case 2:
+ accumulator = g_signal_accumulator_true_handled;
+ break;
+ case 0:
+ default:
+ accumulator = NULL;
+ }
+
+ if (accumulator == g_signal_accumulator_true_handled &&
+ JSVAL_TO_INT(argv[4]) != G_TYPE_BOOLEAN) {
+ gjs_throw (cx, "GObject.SignalAccumulator.TRUE_HANDLED can only be used with boolean signals");
+ ret = JS_FALSE;
+ goto out;
+ }
+
+ if (!JS_GetArrayLength(cx, JSVAL_TO_OBJECT(argv[5]), &n_parameters)) {
+ ret = JS_FALSE;
+ goto out;
+ }
+ params = g_newa(GType, n_parameters);
+ for (i = 0; i < n_parameters; i++) {
+ jsval gtype_val;
+ if (!JS_GetElement(cx, JSVAL_TO_OBJECT(argv[5]), i, >ype_val) ||
+ !JSVAL_IS_OBJECT(gtype_val)) {
+ gjs_throw(cx, "Invalid signal parameter number %d", i);
+ ret = JS_FALSE;
+ goto out;
+ }
+
+ params[i] = gjs_gtype_get_actual_gtype(cx, JSVAL_TO_OBJECT(gtype_val));
+ }
+
+ signal_id = g_signal_newv(signal_name,
+ priv->gtype,
+ JSVAL_TO_INT(argv[2]), /* signal_flags */
+ NULL, /* class closure */
+ accumulator,
+ NULL, /* accu_data */
+ g_cclosure_marshal_generic,
+ gjs_gtype_get_actual_gtype(cx, JSVAL_TO_OBJECT(argv[4])), /* return type */
+ n_parameters,
+ params);
+
+ JS_SET_RVAL(cx, vp, INT_TO_JSVAL(signal_id));
+ ret = JS_TRUE;
+
+ out:
+ JS_EndRequest(cx);
+
+ free (signal_name);
+ return ret;
+}
+
+static JSBool
gjs_define_stuff(JSContext *context,
JSObject *module_obj)
{
@@ -2082,6 +2168,12 @@ gjs_define_stuff(JSContext *context,
2, GJS_MODULE_PROP_FLAGS))
return JS_FALSE;
+ if (!JS_DefineFunction(context, module_obj,
+ "signal_new",
+ (JSNative)gjs_signal_new,
+ 6, GJS_MODULE_PROP_FLAGS))
+ return JS_FALSE;
+
return JS_TRUE;
}
diff --git a/modules/overrides/GObject.js b/modules/overrides/GObject.js
index f54383a..77d4ac8 100644
--- a/modules/overrides/GObject.js
+++ b/modules/overrides/GObject.js
@@ -44,12 +44,41 @@ const GObjectMeta = new Lang.Class({
Gi.register_property(this.prototype, params.Properties[prop]);
}
}
+
+ if (params.Signals) {
+ for (let signalName in params.Signals) {
+ let obj = params.Signals[signalName];
+ let flags = (obj.flags !== undefined) ? obj.flags : GObject.SignalFlags.RUN_FIRST;
+ let accumulator = (obj.accumulator !== undefined) ? obj.accumulator : GObject.AccumulatorType.NONE;
+ let rtype = (obj.return_type !== undefined) ? obj.return_type : GObject.TYPE_NONE;
+ let paramtypes = (obj.param_types !== undefined) ? obj.param_types : [];
+
+ try {
+ obj.signal_id = Gi.signal_new(this.prototype, signal_name, flags, accumulator, rtype, paramtypes);
+ } catch(e) {
+ throw new TypeError('Invalid signal ' + signal_name + ': ' + e.message);
+ }
+ }
+ }
delete params.Properties;
+ delete params.Signals;
for (let prop in params) {
let value = this.prototype[prop];
- if (typeof value === 'function' && prop.slice(0, 6) == 'vfunc_') {
- Gi.hook_up_vfunc(this.prototype, prop.slice(6), value);
+ if (typeof value === 'function') {
+ if (prop.slice(0, 6) == 'vfunc_') {
+ Gi.hook_up_vfunc(this.prototype, prop.slice(6), value);
+ } else if (prop.slice(0, 3) == 'on_') {
+ let id = GObject.signal_lookup(prop.slice(3).replace('_', '-'), this.$gtype);
+ if (id != 0) {
+ GObject.signal_override_class_closure(id, this.$gtype, function() {
+ let argArray = Array.prototype.slice.call(arguments);
+ let emitter = argArray.shift();
+
+ value.apply(emitter, argArray);
+ });
+ }
+ }
}
}
},
@@ -173,4 +202,11 @@ function _init() {
this._init.apply(this, arguments);
return this;
};
+
+ // fake enum for signal accumulators, keep in sync with gi/object.c
+ this.AccumulatorType = {
+ NONE: 0,
+ FIRST_WINS: 1,
+ TRUE_HANDLED: 2
+ };
}
diff --git a/test/js/testGObjectClass.js b/test/js/testGObjectClass.js
new file mode 100644
index 0000000..48ae5b6
--- /dev/null
+++ b/test/js/testGObjectClass.js
@@ -0,0 +1,236 @@
+// application/javascript;version=1.8 -*- mode: js; indent-tabs-mode: nil -*-
+
+if (!('assertEquals' in this)) { /* allow running this test standalone */
+ imports.lang.copyPublicProperties(imports.jsUnit, this);
+ gjstestRun = function() { return imports.jsUnit.gjstestRun(window); };
+}
+
+const Lang = imports.lang;
+const GObject = imports.gi.GObject;
+const Gio = imports.gi.Gio;
+
+const MyObject = new GObject.Class({
+ Name: 'MyObject',
+ Properties: {
+ 'readwrite': GObject.ParamSpec.string('readwrite', 'ParamReadwrite',
+ 'A read write parameter',
+ GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE,
+ ''),
+ 'readonly': GObject.ParamSpec.string('readonly', 'ParamReadonly',
+ 'A readonly parameter',
+ GObject.ParamFlags.READABLE,
+ ''),
+
+ 'construct': GObject.ParamSpec.string('construct', 'ParamConstructOnly',
+ 'A readwrite construct-only parameter',
+ GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE | GObject.ParamFlags.CONSTRUCT_ONLY,
+ 'default')
+ },
+ Signals: {
+ 'empty': { },
+ 'minimal': { param_types: [ GObject.TYPE_INT, GObject.TYPE_INT ] },
+ 'full': { flags: GObject.SignalFlags.RUN_LAST, accumulator: GObject.AccumulatorType.FIRST_WINS,
+ return_type: GObject.TYPE_INT, param_types: [ ] },
+ 'run-last': { flags: GObject.SignalFlags.RUN_LAST },
+ 'detailed': { flags: GObject.SignalFlags.RUN_FIRST | GObject.SignalFlags.DETAILED, param_types: [ GObject.TYPE_STRING ] }
+ },
+
+ _init: function(props) {
+ // check that it's safe to set properties before
+ // chaining up (priv is NULL at this point, remember)
+ this._readwrite = 'foo';
+ this._readonly = 'bar';
+ this._constructProp = null;
+ this._constructCalled = false;
+
+ this.parent(props);
+ },
+
+ get readwrite() {
+ return this._readwrite;
+ },
+
+ set readwrite(val) {
+ if (val == 'ignore')
+ return;
+
+ this._readwrite = val;
+ },
+
+ get readonly() {
+ return this._readonly;
+ },
+
+ set readonly(val) {
+ // this should never be called
+ this._readonly = 'bogus';
+ },
+
+ get construct() {
+ return this._constructProp;
+ },
+
+ set construct(val) {
+ // this should be called at most once
+ if (this._constructCalled)
+ throw Error('Construct-Only property set more than once');
+
+ this._constructProp = val;
+ this._constructCalled = true;
+ },
+
+ notify_prop: function() {
+ this._readonly = 'changed';
+
+ this.notify('readonly');
+ },
+
+ emit_empty: function() {
+ this.emit('empty');
+ },
+
+ emit_minimal: function(one, two) {
+ this.emit('minimal', one, two);
+ },
+
+ emit_full: function() {
+ return this.emit('full');
+ },
+
+ emit_detailed: function() {
+ this.emit('detailed::one');
+ this.emit('detailed::two');
+ },
+
+ emit_run_last: function(callback) {
+ this._run_last_callback = callback;
+ this.emit('run-last');
+ },
+
+ on_run_last: function() {
+ this._run_last_callback();
+ },
+
+ on_empty: function() {
+ this.empty_called = true;
+ }
+});
+
+const MyApplication = new Lang.Class({
+ Name: 'MyApplication',
+ Extends: Gio.Application,
+ Signals: { 'custom': { param_types: [ GObject.TYPE_INT ] } },
+
+ _init: function(params) {
+ this.parent(params);
+ },
+
+ emit_custom: function(n) {
+ this.emit('custom', n);
+ }
+});
+printerr("almost");
+
+function testGObjectClass() {
+ let myInstance = new MyObject();
+
+ assertEquals('foo', myInstance.readwrite);
+ assertEquals('bar', myInstance.readonly);
+ assertEquals('default', myInstance.construct);
+
+ let myInstance2 = new MyObject({ readwrite: 'baz', construct: 'asdf' });
+
+ assertEquals('baz', myInstance2.readwrite);
+ assertEquals('bar', myInstance2.readonly);
+ assertEquals('asdf', myInstance2.construct);
+
+ // the following would (should) cause a CRITICAL:
+ // myInstance.readonly = 'val';
+ // myInstance.construct = 'val';
+}
+
+function testNotify() {
+ let myInstance = new MyObject();
+ let counter = 0;
+
+ myInstance.connect('notify::readonly', function(obj) {
+ if (obj.readonly == 'changed')
+ counter++;
+ });
+
+ myInstance.notify_prop();
+ myInstance.notify_prop();
+
+ assertEquals(2, counter);
+}
+
+function testSignals() {
+ let myInstance = new MyObject();
+ let ok = false;
+
+ myInstance.connect('empty', function() {
+ ok = true;
+ });
+ myInstance.emit_empty();
+
+ assertEquals(true, ok);
+ assertEquals(true, myInstance.empty_called);
+
+ let args = [ ];
+ myInstance.connect('minimal', function(emitter, one, two) {
+ args.push(one);
+ args.push(two);
+
+ return true;
+ });
+ myInstance.emit_minimal(7, 5);
+
+ assertEquals(7, args[0]);
+ assertEquals(5, args[1]);
+
+ ok = true;
+ myInstance.connect('full', function() {
+ ok = true;
+
+ return 42;
+ });
+ myInstance.connect('full', function() {
+ // this should never be called
+ ok = false;
+
+ return -1;
+ });
+ result = myInstance.emit_full();
+
+ assertEquals(true, ok);
+ assertEquals(42, result);
+
+ let stack = [ ];
+ myInstance.connect('run-last', function() {
+ stack.push(1);
+ });
+ myInstance.emit_run_last(function() {
+ stack.push(2);
+ });
+
+ assertEquals(1, stack[0]);
+ assertEquals(2, stack[1]);
+}
+
+function testSubclass() {
+ // test that we can inherit from something that's not
+ // GObject.Object and still get all the goodies of
+ // GObject.Class
+
+ let instance = new MyApplication({ application_id: 'org.gjs.Application' });
+ let v;
+
+ instance.connect('custom', function(app, num) {
+ v = num;
+ });
+
+ instance.emit_custom(73);
+ assertEquals(73, v);
+}
+
+gjstestRun();
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]