]> git.draconx.ca Git - gob-dx.git/commitdiff
Implement chaining of interface methods.
authorNick Bowler <nbowler@draconx.ca>
Thu, 6 Feb 2020 05:08:08 +0000 (00:08 -0500)
committerNick Bowler <nbowler@draconx.ca>
Fri, 7 Feb 2020 02:24:45 +0000 (21:24 -0500)
GObject interface methods are very similar to virtual methods.  A class
may wish to override some or all of the interface methods implemented
in a parent class.  When doing so, it can be useful to call the parent
class implementation to offload some of the work.

For ordinary virtual methods, using the "override" keyword causes a
PARENT_HANDLER macro to be defined which can call up to the parent
class implementation.  Let's do exactly the same thing for interface
methods, and expose the parent interface structures (in case the user
wishes to call up to different methods).

NEWS
doc/gob2.1.in
src/main.c
tests/interface.at

diff --git a/NEWS b/NEWS
index b1f9e590489c0d63a93dd6f3cee07569975eabff..bed51af31f280839caefb5f8a0b90be47509ba50 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -2,6 +2,7 @@
        * Improve build system.
        * Remove NOINSTGOB functionality as it seems pointless.
        * Add support for dynamic GObject types.
+       * Improved support for overriding interfaces in subclasses.
        * New and improved GOB2_CHECK macro.
 
 2.0.20:
index 9037aa2673b985c5b81023e6474644cca7a0017c..560dd8ae65ae0c762e782b4a0d6f7169a4a4367d 100644 (file)
@@ -1065,6 +1065,21 @@ a specific method of the interface, just add \'interface <typename>\'
 before the method definition.  The method can, and probably should be,
 private.
 .PP
+Interface methods behave very similarly to virtual/override methods.
+As with override methods, you can use PARENT_HANDLER in the definition of an
+interface method to call the implementation of the same interface method in
+a parent class.
+.PP
+The NAME_parent_iface variable may be used to access the complete interface
+structure from a parent class, where NAME is the implemented interface (with
+colons replaced by underscores).
+This enables access to the full parent implementation of an interface.
+For example, if a class implements the Gtk:Tree:Model interface then the
+implementation of the same interface in the parent class is available via
+the Gtk_Tree_Model_parent_iface pointer.
+If an interface is not implemented in the parent class, then the corresponding
+parent_iface value will be a null pointer.
+.PP
 The following example implements a new object, that implements the
 Gtk:Tree:Model interface and implements the get_flags method of that
 interface.  Do note that except for standard (GTK+ and glib) specific
index 6a0c7911cadee6726195ce40eb05754cb288b28b..c7171c50fc441390d82d9d9cdf7b43926a650384 100644 (file)
@@ -954,6 +954,59 @@ add_signal_prots(Method *m)
        out_printf (out, "}\n\n");
 }
 
+static char *
+interface_type(const char *if_name)
+{
+       char *rawtype = remove_sep(if_name);
+       char *end = "", *typename;
+
+       if (!gtk3_ok) {
+               /*
+                * EEEK! evil, we should have some sort of option
+                * to force this for arbitrary interfaces, since
+                * some are Class and some are Iface.  Glib is shite
+                * in consistency.
+                */
+
+               if (strcmp (rawtype, "GtkEditable") == 0
+                   || strcmp (rawtype, "GTypePlugin") == 0)
+               {
+                       end = "Class";
+               } else {
+                       /* We'll assume Iface is the standard ending */
+                       end = "Iface";
+               }
+       } else {
+               /* GTK3 doesn't need Iface end */
+               end = "Interface";
+       }
+
+       typename = g_strconcat(rawtype, end, (char *)NULL);
+       g_free(rawtype);
+
+       return typename;
+}
+
+static void
+define_parent_interface_refs(Class *c)
+{
+       GList *li;
+
+       if (!c->interfaces)
+               return;
+
+       out_printf(out, "\n/* parent class interface implementations */\n");
+       for (li = c->interfaces; li != NULL; li = li->next) {
+               char *name = replace_sep(li->data, '_');
+               char *type = interface_type(li->data);
+
+               out_printf (out, "static %s *%s_parent_iface;\n", type, name);
+
+               g_free(name);
+               g_free(type);
+       }
+}
+
 static void
 add_enums(Class *c)
 {
@@ -1003,7 +1056,9 @@ add_enums(Class *c)
                           "static guint object_signals[LAST_SIGNAL] = {0};\n\n");
 
        out_printf(out, "/* pointer to the class of our parent */\n");
-       out_printf(out, "static %sClass *parent_class = NULL;\n\n", ptypebase);
+       out_printf(out, "static %sClass *parent_class = NULL;\n", ptypebase);
+       define_parent_interface_refs(c);
+       out_printf(out, "\n");
 }
 
 static void
@@ -1037,7 +1092,7 @@ add_interface_methods (Class *c, const char *interface)
 }
 
 static void
-add_interface_inits (Class *c)
+add_interface_inits(Class *c)
 {
        GList *li;
 
@@ -1047,39 +1102,19 @@ add_interface_inits (Class *c)
        out_printf(out, "\n");
 
        for (li = c->interfaces; li != NULL; li = li->next) {
-               const char *interface = li->data;
-               const char *end;
-               char *name = replace_sep (interface, '_');
-               char *type = remove_sep (interface);
+               char *name = replace_sep(li->data, '_');
+               char *type = interface_type(li->data);
 
-               if(!gtk3_ok)
-               {
-                       /* EEEK! evil, we should have some sort of option
-                        * to force this for arbitrary interfaces, since
-                        * some are Class and some are Iface.  Glib is shite
-                        * in consistency. */
-               
-                       if (strcmp (type, "GtkEditable") == 0 ||
-                           strcmp (type, "GTypePlugin") == 0)
-                               end = "Class";
-                       else
-                               // We'll assume Iface is the standard ending 
-                               end = "Iface";
-                       }
-               else
-               {
-                       /*GTK3 doesn't need Iface end*/
-                       end="Interface";
-               }
-               
-               out_printf (out, "\nstatic void\n"
-                           "___%s_init (%s%s *iface)\n"
-                           "{\n",
-                           name, type, end);
+               out_printf(out, "static void\n"
+                               "___%s_init (%s *iface)\n"
+                               "{\n", name, type);
 
-               add_interface_methods (c, interface);
+               add_interface_methods(c, li->data);
 
-               out_printf (out, "}\n\n");
+               out_printf(out, "\t%s_parent_iface\n", name);
+               out_printf(out, for_cpp ? "\t\t= (%s *)" : "\t\t= ", type);
+               out_printf(out, "g_type_interface_peek_parent(iface);\n"
+                               "}\n\n");
 
                g_free (name);
                g_free (type);
@@ -3053,6 +3088,35 @@ get_arg_names_for_macro (Method *m)
        return g_string_free (gs, FALSE);
 }
 
+static gboolean method_is_void(Method *m)
+{
+       return !strcmp(m->mtype->name, "void") && !m->mtype->pointer;
+}
+
+static const char *method_err_retval(Method *m)
+{
+       if (method_is_void(m))
+               return "(void)0";
+       if (m->onerror)
+               return m->onerror;
+       return "0";
+}
+
+static void
+put_interface_parent_handler(Method *m)
+{
+       const char *errval = method_err_retval(m);
+       char *name = replace_sep(m->interface, '_');
+       char *args = get_arg_names_for_macro(m);
+
+       out_printf(out, "#define PARENT_HANDLER(%s) (%s_parent_iface \\\n"
+                       "\t? %s_parent_iface->%s(%s) \\\n"
+                       "\t: %s)\n", args, name, name, m->id, args, errval);
+
+       g_free(name);
+       g_free(args);
+}
+
 static void
 put_method(Method *m)
 {
@@ -3068,6 +3132,7 @@ put_method(Method *m)
                        g_free(doc);
                }
        }
+
        switch(m->method) {
        case REGULAR_METHOD:
                if(m->line_no > 0)
@@ -3078,7 +3143,18 @@ put_method(Method *m)
                else /* PUBLIC, PROTECTED */
                        print_method(out, "", "\n", "", " ", "", "\n",
                                     m, FALSE, FALSE, FALSE, TRUE, FALSE, FALSE);
+
+               if (m->interface) {
+                       out_addline_outfile(out);
+                       put_interface_parent_handler(m);
+               }
+
                print_method_body(m, TRUE, TRUE);
+
+               if (m->interface) {
+                       out_printf(out, "#undef PARENT_HANDLER\n");
+               }
+
                /* the outfile line was added above */
                break;
        case SIGNAL_FIRST_METHOD:
index c8e2a9c88d7f70668bf28f4e2bd18a64bedb37ce..61c42bb4f35ce8cab82437d8c0829e7f8c279597 100644 (file)
@@ -65,6 +65,7 @@ class $1 from $3]m4_default_nblank([
   (interface Test:Fooable)
 {
   interface Test:Fooable private int foo(G:Object *go)
+    onerror 88
   {
     $5
     abort();
@@ -291,3 +292,97 @@ Test:B foo called
 ])
 
 AT_CLEANUP
+
+AT_SETUP([interface method chaining])
+AT_KEYWORDS([runtime])dnl
+
+TEST_FOOABLE_IFACE()
+TEST_FOOABLE_IMPL([Test:A], [G:Object],
+  [puts("Test:A foo called"); return PARENT_HANDLER(go);])
+TEST_FOOABLE_IMPL([Test:B], [Test:A],
+  [puts("Test:B foo called"); return PARENT_HANDLER(go);])
+TEST_FOOABLE_IMPL([Test:C], [Test:B],
+  [puts("Test:C foo called"); return PARENT_HANDLER(go);])
+
+AT_DATA([main.c],
+[[#include <stdio.h>
+#include "test-fooable.h"
+#include "test-a.h"
+#include "test-b.h"
+#include "test-c.h"
+
+int main(void)
+{
+  int rc;
+
+  rc = test_foo(g_object_new(TEST_TYPE_C, NULL));
+  printf("%d\n", rc);
+  if (rc < 0)
+    return EXIT_FAILURE;
+
+  return 0;
+}
+]])
+
+TEST_COMPILE_GOBJECT([main.c], [0], [], [ignore])
+
+AT_CHECK([$CC $CFLAGS $LDFLAGS $LIBGOBJECT_LIBS -o main \
+  test-a.o test-b.o test-c.o test-fooable.o main.o])
+AT_CHECK([./main], [0], [Test:C foo called
+Test:B foo called
+Test:A foo called
+88
+])
+
+AT_CLEANUP
+
+AT_SETUP([interface method chaining (dynamic)])
+AT_KEYWORDS([runtime])dnl
+
+TEST_FOOABLE_IFACE()
+TEST_FOOABLE_IMPL_DYN([Test:A], [G:Object],
+  [puts("Test:A foo called"); return PARENT_HANDLER(go);])
+TEST_FOOABLE_IMPL_DYN([Test:B], [Test:A],
+  [puts("Test:B foo called"); return PARENT_HANDLER(go);])
+TEST_FOOABLE_IMPL_DYN([Test:C], [Test:B],
+  [puts("Test:C foo called"); return PARENT_HANDLER(go);])
+
+AT_DATA([main.c],
+[[#include <stdio.h>
+#include "test-fooable.h"
+#include "test-a.h"
+#include "test-a-mod.h"
+#include "test-b.h"
+#include "test-b-mod.h"
+#include "test-c.h"
+#include "test-c-mod.h"
+
+int main(void)
+{
+  int rc;
+
+  g_type_module_use(g_object_new(TEST_TYPE_A_MOD, NULL));
+  g_type_module_use(g_object_new(TEST_TYPE_B_MOD, NULL));
+  g_type_module_use(g_object_new(TEST_TYPE_C_MOD, NULL));
+
+  rc = test_foo(g_object_new(TEST_TYPE_C, NULL));
+  printf("%d\n", rc);
+  if (rc < 0)
+    return EXIT_FAILURE;
+
+  return 0;
+}
+]])
+
+TEST_COMPILE_GOBJECT([main.c], [0], [], [ignore])
+
+AT_CHECK([$CC $CFLAGS $LDFLAGS $LIBGOBJECT_LIBS -o main \
+  test-a.o test-a-mod.o test-b.o test-b-mod.o test-c.o test-c-mod.o \
+  test-fooable.o main.o])
+AT_CHECK([./main], [0], [Test:C foo called
+Test:B foo called
+Test:A foo called
+88
+])
+
+AT_CLEANUP