[vala/wip/gtktemplate] Static type checking of [GtkCallback] using gresources



commit a3984b7d1e57e90be0100e02a75d36e1c45a91e0
Author: Luca Bruno <lucabru src gnome org>
Date:   Sat Jun 1 15:55:17 2013 +0200

    Static type checking of [GtkCallback] using gresources

 codegen/valaccodedelegatemodule.vala |    2 +-
 codegen/valagtkmodule.vala           |  170 ++++++++++++++++++++++++---------
 compiler/valacompiler.vala           |    5 +
 vala/valacodecontext.vala            |    2 +
 vala/valasignaltype.vala             |    2 +-
 5 files changed, 132 insertions(+), 49 deletions(-)
---
diff --git a/codegen/valaccodedelegatemodule.vala b/codegen/valaccodedelegatemodule.vala
index 2391028..2fba0f4 100644
--- a/codegen/valaccodedelegatemodule.vala
+++ b/codegen/valaccodedelegatemodule.vala
@@ -173,7 +173,7 @@ public class Vala.CCodeDelegateModule : CCodeArrayModule {
                return base.get_implicit_cast_expression (source_cexpr, expression_type, target_type, node);
        }
 
-       private string generate_delegate_wrapper (Method m, DelegateType dt, CodeNode? node) {
+       public string generate_delegate_wrapper (Method m, DelegateType dt, CodeNode? node) {
                var d = dt.delegate_symbol;
                string delegate_name;
                var sig = d.parent_symbol as Signal;
diff --git a/codegen/valagtkmodule.vala b/codegen/valagtkmodule.vala
index 467972b..e5e0e8b 100644
--- a/codegen/valagtkmodule.vala
+++ b/codegen/valagtkmodule.vala
@@ -23,6 +23,101 @@
 
 
 public class Vala.GtkModule : GSignalModule {
+       /* C class name to Vala class mapping */
+       private HashMap<string, Class> cclass_to_vala_map = null;
+       /* GResource name to real file name mapping */
+       private HashMap<string, string> gresource_to_file_map = null;
+       /* GtkBuilder xml handler to Vala signal mapping */
+       private HashMap<string, Signal> current_handler_to_signal_map = new HashMap<string, Signal>(str_hash, 
str_equal);
+
+       private void ensure_cclass_to_vala_map () {
+               // map C name of gtypeinstance classes to Vala classes
+               if (cclass_to_vala_map != null) {
+                       return;
+               }
+               cclass_to_vala_map = new HashMap<string, Class>(str_hash, str_equal);
+               recurse_cclass_to_vala_map (context.root);
+       }
+
+       private void recurse_cclass_to_vala_map (Namespace ns) {
+               foreach (var cl in ns.get_classes()) {
+                       if (!cl.is_compact) {
+                               cclass_to_vala_map.set (get_ccode_name (cl), cl);
+                       }
+               }
+               foreach (var inner in ns.get_namespaces()) {
+                       recurse_cclass_to_vala_map (inner);
+               }
+       }
+
+       private void ensure_gresource_to_file_map () {
+               // map gresource paths to real file names
+               if (gresource_to_file_map != null) {
+                       return;
+               }
+               gresource_to_file_map = new HashMap<string, string>(str_hash, str_equal);               
+               foreach (var gresource in context.gresources) {
+                       if (!FileUtils.test (gresource, FileTest.EXISTS)) {
+                               Report.error (null, "GResources file `%s' does not exist".printf (gresource));
+                               continue;
+                       }
+                       var gresource_dir = Path.get_dirname (gresource);
+                       MarkupReader reader = new MarkupReader (gresource);
+
+                       int state = 0;
+                       string prefix = null;
+
+                       MarkupTokenType current_token = reader.read_token (null, null);
+                       while (current_token != MarkupTokenType.EOF) {
+                               if (current_token == MarkupTokenType.START_ELEMENT && reader.name == 
"gresource") {
+                                       prefix = reader.get_attribute ("prefix");
+                               } else if (current_token == MarkupTokenType.START_ELEMENT && reader.name == 
"file") {
+                                       state = 1;
+                               } else if (state == 1 && current_token == MarkupTokenType.TEXT) {
+                                       var name = reader.content;
+                                       gresource_to_file_map.set (Path.build_filename (prefix, name), 
Path.build_filename (gresource_dir, name));
+                                       state = 0;
+                               }
+                               current_token = reader.read_token (null, null);
+                       }
+               }
+       }
+
+       private void create_handler_to_signal_map (string ui_resource, SourceReference source_reference) {
+               /* Scan a single gtkbuilder file for signal handlers in <object> elements,
+                  and save an handler string -> Vala.Signal mapping for each of them */
+               ensure_cclass_to_vala_map();
+               ensure_gresource_to_file_map();
+
+               current_handler_to_signal_map = new HashMap<string, Signal>(str_hash, str_equal);
+               var ui_file = gresource_to_file_map.get (ui_resource);
+               if (ui_file == null || !FileUtils.test (ui_file, FileTest.EXISTS)) {
+                       Report.warning (source_reference, "UI resource does not exist: `%s'".printf 
(ui_resource));
+                       return;
+               }
+
+               MarkupReader reader = new MarkupReader (ui_file);
+               Class current_class = null;
+
+               MarkupTokenType current_token = reader.read_token (null, null);
+               while (current_token != MarkupTokenType.EOF) {
+                       if (current_token == MarkupTokenType.START_ELEMENT && reader.name == "object") {
+                               var class_name = reader.get_attribute ("class");
+                               current_class = cclass_to_vala_map.get (class_name);
+                       } else if (current_token == MarkupTokenType.START_ELEMENT && reader.name == "signal") 
{
+                               var signal_name = reader.get_attribute ("name");
+                               var handler_name = reader.get_attribute ("handler");
+                               if (current_class != null) {
+                                       var sig = SemanticAnalyzer.symbol_lookup_inherited (current_class, 
signal_name.replace("-", "_")) as Signal;
+                                       if (sig != null) {
+                                               current_handler_to_signal_map.set (handler_name, sig);
+                                       }
+                               }
+                       }
+                       current_token = reader.read_token (null, null);
+               }
+       }
+
        private bool is_gtk_template (Class? cl) {
                return cl != null && gtk_widget_type != null && cl.is_subtype_of (gtk_widget_type) && 
cl.get_attribute ("GtkTemplate") != null;
        }
@@ -37,11 +132,13 @@ public class Vala.GtkModule : GSignalModule {
                /* Gtk builder widget template */
                var ui = cl.get_attribute_string ("GtkTemplate", "ui");
                if (ui == null) {
-                       Report.error (cl.source_reference, "Empty ui file declaration for Gtk widget 
template");
+                       Report.error (cl.source_reference, "Empty ui resource declaration for Gtk widget 
template");
                        cl.error = true;
                        return;
                }
 
+               create_handler_to_signal_map (ui, cl.source_reference);
+
                var call = new CCodeFunctionCall (new CCodeIdentifier 
("gtk_widget_class_set_template_from_resource"));
                call.add_argument (new CCodeIdentifier ("GTK_WIDGET_CLASS (klass)"));
                call.add_argument (new CCodeConstant ("\""+cl.get_attribute_string ("GtkTemplate", 
"ui")+"\""));
@@ -80,35 +177,6 @@ public class Vala.GtkModule : GSignalModule {
                pop_context ();
        }
 
-       /* Generate a wrapper function that swaps two parameters */
-       private string generate_gtk_swap_callback (Method m) {
-               var wrapper_name = "_vala_gtktemplate_callback_%s".printf (get_ccode_name (m));
-               if (!add_wrapper (wrapper_name)) {
-                       return wrapper_name;
-               }
-
-               var function = new CCodeFunction (wrapper_name, "void");
-               function.add_parameter (new CCodeParameter ("connect_object", "gpointer"));
-               function.add_parameter (new CCodeParameter ("object", "gpointer"));
-               function.modifiers = CCodeModifiers.STATIC;
-
-               push_function (function);
-
-               var call = new CCodeFunctionCall (new CCodeIdentifier (get_ccode_name (m)));
-               call.add_argument (new CCodeIdentifier ("object"));
-               if (m.get_parameters().size > 0) {
-                       call.add_argument (new CCodeIdentifier ("connect_object"));
-               }
-               ccode.add_expression (call);
-
-               pop_function ();
-
-               cfile.add_function_declaration (function);
-               cfile.add_function (function);
-
-               return wrapper_name;
-       }
-
        public override void visit_method (Method m) {
                base.visit_method (m);
 
@@ -121,25 +189,33 @@ public class Vala.GtkModule : GSignalModule {
                        return;
                }
 
-               push_context (class_init_context);
-
-               /* Map a ui callback to a class method */
-               var gtk_name = m.get_attribute_string ("GtkCallback", "name", m.name);
-               string callback_name;
-               // This is the "swap" option of gtkbuilder/glade.
-               if (!m.get_attribute_bool ("GtkCallback", "swap")) {
-                       /* If swap=false, the function is called with (object, data) but we want (data, 
object).
-                        * Therefore we create a wrapper that swaps the two parameters. */
-                       callback_name = generate_gtk_swap_callback (m);
-               } else {
-                       callback_name = get_ccode_name (m);
+               /* Handler name as defined in the gtkbuilder xml */
+               var handler_name = m.get_attribute_string ("GtkCallback", "name", m.name);
+               var sig = current_handler_to_signal_map.get (handler_name);
+               if (sig == null) {
+                       Report.warning (m.source_reference, "signal not found for handler `%s'".printf 
(handler_name));
+                       return;
                }
 
-               var call = new CCodeFunctionCall (new CCodeIdentifier ("gtk_widget_class_declare_callback"));
-               call.add_argument (new CCodeIdentifier ("GTK_WIDGET_CLASS (klass)"));
-               call.add_argument (new CCodeConstant ("\"%s\"".printf (gtk_name)));
-               call.add_argument (new CCodeIdentifier ("G_CALLBACK(%s)".printf (callback_name)));
-               ccode.add_expression (call);
+               push_context (class_init_context);
+                       
+               if (sig != null) {
+                       sig.check (context);
+                       var method_type = new MethodType (m);
+                       var signal_type = new SignalType (sig);
+                       var delegate_type = signal_type.get_handler_type ();
+                       if (!method_type.compatible (delegate_type)) {
+                               Report.error (m.source_reference, "method `%s' is incompatible with signal 
`%s', expected `%s'".printf (method_type.to_string (), delegate_type.to_string (), 
delegate_type.delegate_symbol.get_prototype_string (m.name)));
+                       } else {
+                               var wrapper = generate_delegate_wrapper (m, signal_type.get_handler_type (), 
m);
+
+                               var call = new CCodeFunctionCall (new CCodeIdentifier 
("gtk_widget_class_declare_callback"));
+                               call.add_argument (new CCodeIdentifier ("GTK_WIDGET_CLASS (klass)"));
+                               call.add_argument (new CCodeConstant ("\"%s\"".printf (handler_name)));
+                               call.add_argument (new CCodeIdentifier ("G_CALLBACK(%s)".printf (wrapper)));
+                               ccode.add_expression (call);
+                       }
+               }
 
                pop_context ();
        }
diff --git a/compiler/valacompiler.vala b/compiler/valacompiler.vala
index 1f471ed..4d379fd 100644
--- a/compiler/valacompiler.vala
+++ b/compiler/valacompiler.vala
@@ -44,6 +44,8 @@ class Vala.Compiler {
        [CCode (array_length = false, array_null_terminated = true)]
        static string[] fast_vapis;
        static string target_glib;
+       [CCode (array_length = false, array_null_terminated = true)]
+       static string[] gresources;
 
        static bool ccode_only;
        static string header_filename;
@@ -133,6 +135,7 @@ class Vala.Compiler {
                { "quiet", 'q', 0, OptionArg.NONE, ref quiet_mode, "Do not print messages to the console", 
null },
                { "verbose", 'v', 0, OptionArg.NONE, ref verbose_mode, "Print additional messages to the 
console", null },
                { "target-glib", 0, 0, OptionArg.STRING, ref target_glib, "Target version of glib for code 
generation", "MAJOR.MINOR" },
+               { "gresources", 0, 0, OptionArg.STRING_ARRAY, ref gresources, "XML of gresources", "FILE..." 
},
                { "enable-version-header", 0, 0, OptionArg.NONE, ref enable_version_header, "Write vala build 
version in generated files", null },
                { "disable-version-header", 0, 0, OptionArg.NONE, ref disable_version_header, "Do not write 
vala build version in generated files", null },
                { "", 0, 0, OptionArg.FILENAME_ARRAY, ref sources, null, "FILE..." },
@@ -270,6 +273,8 @@ class Vala.Compiler {
                        }
                        context.use_fast_vapi = true;
                }
+
+               context.gresources = gresources;
                
                if (context.report.get_errors () > 0 || (fatal_warnings && context.report.get_warnings () > 
0)) {
                        return quit ();
diff --git a/vala/valacodecontext.vala b/vala/valacodecontext.vala
index 5e92e81..afad107 100644
--- a/vala/valacodecontext.vala
+++ b/vala/valacodecontext.vala
@@ -188,6 +188,8 @@ public class Vala.CodeContext {
 
        public bool run_output { get; set; }
 
+       public string[] gresources;
+
        private List<SourceFile> source_files = new ArrayList<SourceFile> ();
        private List<string> c_source_files = new ArrayList<string> ();
        private Namespace _root = new Namespace (null);
diff --git a/vala/valasignaltype.vala b/vala/valasignaltype.vala
index 1429046..237a549 100644
--- a/vala/valasignaltype.vala
+++ b/vala/valasignaltype.vala
@@ -60,7 +60,7 @@ public class Vala.SignalType : DataType {
                return signal_symbol.get_full_name ();
        }
 
-       DelegateType get_handler_type () {
+       public DelegateType get_handler_type () {
                var type_sym = (ObjectTypeSymbol) signal_symbol.parent_symbol;
                var sender_type = SemanticAnalyzer.get_data_type_for_symbol (type_sym);
                var result = new DelegateType (signal_symbol.get_delegate (sender_type, this));


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]