tests: Add "continue" parser test to increase coverage
[vala-gnome.git] / codegen / valagtkmodule.vala
blob5d3299d5fc8cfc8a3bce8ac52ea5f0dbf2cceeb5
1 /* valagtkmodule.vala
3 * Copyright (C) 2013 Jürg Billeter
4 * Copyright (C) 2013-2014 Luca Bruno
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * This library 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 GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 * Author:
21 * Luca Bruno <lucabru@src.gnome.org>
25 public class Vala.GtkModule : GSignalModule {
26 /* C type-func name to Vala class mapping */
27 private HashMap<string, Class> type_id_to_vala_map = null;
28 /* C class name to Vala class mapping */
29 private HashMap<string, Class> cclass_to_vala_map = null;
30 /* GResource name to real file name mapping */
31 private HashMap<string, string> gresource_to_file_map = null;
32 /* GtkBuilder xml handler to Vala signal mapping */
33 private HashMap<string, Signal> current_handler_to_signal_map = new HashMap<string, Signal>(str_hash, str_equal);
34 /* GtkBuilder xml child to Vala class mapping */
35 private HashMap<string, Class> current_child_to_class_map = new HashMap<string, Class>(str_hash, str_equal);
36 /* Required custom application-specific gtype classes to be ref'd before initializing the template */
37 private List<Class> current_required_app_classes = new ArrayList<Class>();
39 private void ensure_type_id_to_vala_map () {
40 // map C type-func name of gtypeinstance classes to Vala classes
41 if (type_id_to_vala_map != null) {
42 return;
44 type_id_to_vala_map = new HashMap<string, Class>(str_hash, str_equal);
45 recurse_type_id_to_vala_map (context.root);
48 private void recurse_type_id_to_vala_map (Namespace ns) {
49 foreach (var cl in ns.get_classes()) {
50 if (!cl.is_compact) {
51 var type_id = get_ccode_type_id (cl);
52 if (type_id == null)
53 continue;
55 var i = type_id.index_of_char ('(');
56 if (i > 0) {
57 type_id = type_id.substring (0, i - 1).strip ();
58 } else {
59 type_id = type_id.strip ();
61 type_id_to_vala_map.set (type_id, cl);
64 foreach (var inner in ns.get_namespaces()) {
65 recurse_type_id_to_vala_map (inner);
69 private void ensure_cclass_to_vala_map () {
70 // map C name of gtypeinstance classes to Vala classes
71 if (cclass_to_vala_map != null) {
72 return;
74 cclass_to_vala_map = new HashMap<string, Class>(str_hash, str_equal);
75 recurse_cclass_to_vala_map (context.root);
78 private void recurse_cclass_to_vala_map (Namespace ns) {
79 foreach (var cl in ns.get_classes()) {
80 if (!cl.is_compact) {
81 cclass_to_vala_map.set (get_ccode_name (cl), cl);
84 foreach (var inner in ns.get_namespaces()) {
85 recurse_cclass_to_vala_map (inner);
89 private void ensure_gresource_to_file_map () {
90 // map gresource paths to real file names
91 if (gresource_to_file_map != null) {
92 return;
94 gresource_to_file_map = new HashMap<string, string>(str_hash, str_equal);
95 foreach (var gresource in context.gresources) {
96 if (!FileUtils.test (gresource, FileTest.EXISTS)) {
97 Report.error (null, "GResources file `%s' does not exist".printf (gresource));
98 continue;
101 MarkupReader reader = new MarkupReader (gresource);
103 int state = 0;
104 string prefix = null;
105 string alias = null;
107 MarkupTokenType current_token = reader.read_token (null, null);
108 while (current_token != MarkupTokenType.EOF) {
109 if (current_token == MarkupTokenType.START_ELEMENT && reader.name == "gresource") {
110 prefix = reader.get_attribute ("prefix");
111 } else if (current_token == MarkupTokenType.START_ELEMENT && reader.name == "file") {
112 alias = reader.get_attribute ("alias");
113 state = 1;
114 } else if (state == 1 && current_token == MarkupTokenType.TEXT) {
115 var name = reader.content;
116 var filename = context.get_gresource_path (gresource, name);
117 if (alias != null) {
118 gresource_to_file_map.set (Path.build_filename (prefix, alias), filename);
120 gresource_to_file_map.set (Path.build_filename (prefix, name), filename);
121 state = 0;
123 current_token = reader.read_token (null, null);
128 private void process_current_ui_resource (string ui_resource, CodeNode node) {
129 /* Scan a single gtkbuilder file for signal handlers in <object> elements,
130 and save an handler string -> Vala.Signal mapping for each of them */
131 ensure_type_id_to_vala_map ();
132 ensure_cclass_to_vala_map();
133 ensure_gresource_to_file_map();
135 current_handler_to_signal_map = null;
136 current_child_to_class_map = null;
137 var ui_file = gresource_to_file_map.get (ui_resource);
138 if (ui_file == null || !FileUtils.test (ui_file, FileTest.EXISTS)) {
139 node.error = true;
140 Report.error (node.source_reference, "UI resource not found: `%s'. Please make sure to specify the proper GResources xml files with --gresources and alternative search locations with --gresourcesdir.".printf (ui_resource));
141 return;
143 current_handler_to_signal_map = new HashMap<string, Signal>(str_hash, str_equal);
144 current_child_to_class_map = new HashMap<string, Class>(str_hash, str_equal);
146 MarkupReader reader = new MarkupReader (ui_file);
147 Class current_class = null;
149 bool template_tag_found = false;
150 MarkupTokenType current_token = reader.read_token (null, null);
151 while (current_token != MarkupTokenType.EOF) {
152 unowned string current_name = reader.name;
153 if (current_token == MarkupTokenType.START_ELEMENT && (current_name == "object" || current_name == "template")) {
154 current_class = null;
156 if (current_name == "object") {
157 var type_id = reader.get_attribute ("type-func");
158 if (type_id != null) {
159 current_class = type_id_to_vala_map.get (type_id);
161 } else if (current_name == "template") {
162 template_tag_found = true;
165 if (current_class == null) {
166 var class_name = reader.get_attribute ("class");
167 if (class_name == null) {
168 Report.error (node.source_reference, "Invalid %s in ui file `%s'".printf (current_name, ui_file));
169 current_token = reader.read_token (null, null);
170 continue;
172 current_class = cclass_to_vala_map.get (class_name);
175 if (current_class != null) {
176 var child_name = reader.get_attribute ("id");
177 if (child_name != null) {
178 current_child_to_class_map.set (child_name, current_class);
181 } else if (current_class != null && current_token == MarkupTokenType.START_ELEMENT && current_name == "signal") {
182 var signal_name = reader.get_attribute ("name");
183 var handler_name = reader.get_attribute ("handler");
185 if (current_class != null) {
186 if (signal_name == null || handler_name == null) {
187 Report.error (node.source_reference, "Invalid signal in ui file `%s'".printf (ui_file));
188 current_token = reader.read_token (null, null);
189 continue;
191 var sep_idx = signal_name.index_of ("::");
192 if (sep_idx >= 0) {
193 // detailed signal, we don't care about the detail
194 signal_name = signal_name.substring (0, sep_idx);
197 var sig = SemanticAnalyzer.symbol_lookup_inherited (current_class, signal_name.replace ("-", "_")) as Signal;
198 if (sig != null) {
199 current_handler_to_signal_map.set (handler_name, sig);
203 current_token = reader.read_token (null, null);
206 if (!template_tag_found) {
207 Report.error (node.source_reference, "ui resource `%s' does not describe a valid composite template".printf (ui_resource));
211 private bool is_gtk_template (Class cl) {
212 var attr = cl.get_attribute ("GtkTemplate");
213 if (attr != null) {
214 if (gtk_widget_type == null || !cl.is_subtype_of (gtk_widget_type)) {
215 if (!cl.error) {
216 Report.error (attr.source_reference, "subclassing Gtk.Widget is required for using Gtk templates");
217 cl.error = true;
219 return false;
221 return true;
223 return false;
226 public override void generate_class_init (Class cl) {
227 base.generate_class_init (cl);
229 if (cl.error || !is_gtk_template (cl)) {
230 return;
233 // this check is necessary because calling get_instance_private_offset otherwise will result in an error
234 if (cl.has_private_fields) {
235 var private_offset = "%s_private_offset".printf (get_ccode_name (cl));
236 ccode.add_declaration ("gint", new CCodeVariableDeclarator (private_offset));
238 var cgetprivcall = new CCodeFunctionCall (new CCodeIdentifier ("g_type_class_get_instance_private_offset"));
239 cgetprivcall.add_argument (new CCodeIdentifier ("klass"));
240 ccode.add_assignment (new CCodeIdentifier (private_offset), cgetprivcall);
243 /* Gtk builder widget template */
244 var ui = cl.get_attribute_string ("GtkTemplate", "ui");
245 if (ui == null) {
246 Report.error (cl.source_reference, "empty ui resource declaration for Gtk widget template");
247 cl.error = true;
248 return;
251 process_current_ui_resource (ui, cl);
253 var call = new CCodeFunctionCall (new CCodeIdentifier ("gtk_widget_class_set_template_from_resource"));
254 call.add_argument (new CCodeIdentifier ("GTK_WIDGET_CLASS (klass)"));
255 call.add_argument (new CCodeConstant ("\""+cl.get_attribute_string ("GtkTemplate", "ui")+"\""));
256 ccode.add_expression (call);
258 current_required_app_classes.clear ();
261 public override void visit_property (Property prop) {
262 if (prop.get_attribute ("GtkChild") != null) {
263 Report.error (prop.source_reference, "Annotating properties with [GtkChild] is not yet supported");
266 base.visit_property (prop);
269 public override void visit_field (Field f) {
270 base.visit_field (f);
272 var cl = current_class;
273 if (cl == null || cl.error) {
274 return;
277 if (f.binding != MemberBinding.INSTANCE || f.get_attribute ("GtkChild") == null) {
278 return;
281 /* If the field has a [GtkChild] attribute but its class doesn'thave a
282 [GtkTemplate] attribute, we throw an error */
283 if (!is_gtk_template (cl)) {
284 Report.error (f.source_reference, "[GtkChild] is only allowed in classes with a [GtkTemplate] attribute");
285 return;
288 push_context (class_init_context);
290 /* Map ui widget to a class field */
291 var gtk_name = f.get_attribute_string ("GtkChild", "name", f.name);
292 var child_class = current_child_to_class_map.get (gtk_name);
293 if (child_class == null) {
294 Report.error (f.source_reference, "could not find child `%s'".printf (gtk_name));
295 return;
298 /* We allow Gtk child to have stricter type than class field */
299 var field_class = f.variable_type.data_type as Class;
300 if (field_class == null || !child_class.is_subtype_of (field_class)) {
301 Report.error (f.source_reference, "cannot convert from Gtk child type `%s' to `%s'".printf (child_class.get_full_name(), field_class.get_full_name()));
302 return;
305 var internal_child = f.get_attribute_bool ("GtkChild", "internal");
307 CCodeExpression offset;
308 if (f.is_private_symbol ()) {
309 // new glib api, we add the private struct offset to get the final field offset out of the instance
310 var private_field_offset = new CCodeFunctionCall (new CCodeIdentifier ("G_STRUCT_OFFSET"));
311 private_field_offset.add_argument (new CCodeIdentifier ("%sPrivate".printf (get_ccode_name (cl))));
312 private_field_offset.add_argument (new CCodeIdentifier (get_ccode_name (f)));
313 offset = new CCodeBinaryExpression (CCodeBinaryOperator.PLUS, new CCodeIdentifier ("%s_private_offset".printf (get_ccode_name (cl))), private_field_offset);
314 } else {
315 var offset_call = new CCodeFunctionCall (new CCodeIdentifier ("G_STRUCT_OFFSET"));
316 offset_call.add_argument (new CCodeIdentifier (get_ccode_name (cl)));
317 offset_call.add_argument (new CCodeIdentifier (get_ccode_name (f)));
318 offset = offset_call;
321 var call = new CCodeFunctionCall (new CCodeIdentifier ("gtk_widget_class_bind_template_child_full"));
322 call.add_argument (new CCodeIdentifier ("GTK_WIDGET_CLASS (klass)"));
323 call.add_argument (new CCodeConstant ("\"%s\"".printf (gtk_name)));
324 call.add_argument (new CCodeConstant (internal_child ? "TRUE" : "FALSE"));
325 call.add_argument (offset);
326 ccode.add_expression (call);
328 pop_context ();
330 if (!field_class.external && !field_class.external_package) {
331 current_required_app_classes.add (field_class);
335 public override void visit_method (Method m) {
336 base.visit_method (m);
338 var cl = current_class;
339 if (cl == null || cl.error || !is_gtk_template (cl)) {
340 return;
343 if (m.binding != MemberBinding.INSTANCE || m.get_attribute ("GtkCallback") == null) {
344 return;
347 /* Handler name as defined in the gtkbuilder xml */
348 var handler_name = m.get_attribute_string ("GtkCallback", "name", m.name);
349 var sig = current_handler_to_signal_map.get (handler_name);
350 if (sig == null) {
351 Report.error (m.source_reference, "could not find signal for handler `%s'".printf (handler_name));
352 return;
355 push_context (class_init_context);
357 if (sig != null) {
358 sig.check (context);
359 var method_type = new MethodType (m);
360 var signal_type = new SignalType (sig);
361 var delegate_type = signal_type.get_handler_type ();
362 if (!method_type.compatible (delegate_type)) {
363 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.to_prototype_string (m.name)));
364 } else {
365 var wrapper = generate_delegate_wrapper (m, signal_type.get_handler_type (), m);
367 var call = new CCodeFunctionCall (new CCodeIdentifier ("gtk_widget_class_bind_template_callback_full"));
368 call.add_argument (new CCodeIdentifier ("GTK_WIDGET_CLASS (klass)"));
369 call.add_argument (new CCodeConstant ("\"%s\"".printf (handler_name)));
370 call.add_argument (new CCodeIdentifier ("G_CALLBACK(%s)".printf (wrapper)));
371 ccode.add_expression (call);
375 pop_context ();
379 public override void end_instance_init (Class cl) {
380 if (cl == null || cl.error || !is_gtk_template (cl)) {
381 return;
384 foreach (var req in current_required_app_classes) {
385 /* ensure custom application widgets are initialized */
386 var call = new CCodeFunctionCall (new CCodeIdentifier ("g_type_ensure"));
387 call.add_argument (get_type_id_expression (SemanticAnalyzer.get_data_type_for_symbol (req)));
388 ccode.add_expression (call);
391 var call = new CCodeFunctionCall (new CCodeIdentifier ("gtk_widget_init_template"));
392 call.add_argument (new CCodeIdentifier ("GTK_WIDGET (self)"));
393 ccode.add_expression (call);