tests: Add "while (false)" test to increase coverage
[vala-gnome.git] / codegen / valagtkmodule.vala
blob85ef5370927055407dd2f1ef17289af725269729
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 class name to Vala class mapping */
27 private HashMap<string, Class> cclass_to_vala_map = null;
28 /* GResource name to real file name mapping */
29 private HashMap<string, string> gresource_to_file_map = null;
30 /* GtkBuilder xml handler to Vala signal mapping */
31 private HashMap<string, Signal> current_handler_to_signal_map = new HashMap<string, Signal>(str_hash, str_equal);
32 /* GtkBuilder xml child to Vala class mapping */
33 private HashMap<string, Class> current_child_to_class_map = new HashMap<string, Class>(str_hash, str_equal);
34 /* Required custom application-specific gtype classes to be ref'd before initializing the template */
35 private List<Class> current_required_app_classes = new ArrayList<Class>();
37 private void ensure_cclass_to_vala_map () {
38 // map C name of gtypeinstance classes to Vala classes
39 if (cclass_to_vala_map != null) {
40 return;
42 cclass_to_vala_map = new HashMap<string, Class>(str_hash, str_equal);
43 recurse_cclass_to_vala_map (context.root);
46 private void recurse_cclass_to_vala_map (Namespace ns) {
47 foreach (var cl in ns.get_classes()) {
48 if (!cl.is_compact) {
49 cclass_to_vala_map.set (get_ccode_name (cl), cl);
52 foreach (var inner in ns.get_namespaces()) {
53 recurse_cclass_to_vala_map (inner);
57 private void ensure_gresource_to_file_map () {
58 // map gresource paths to real file names
59 if (gresource_to_file_map != null) {
60 return;
62 gresource_to_file_map = new HashMap<string, string>(str_hash, str_equal);
63 foreach (var gresource in context.gresources) {
64 if (!FileUtils.test (gresource, FileTest.EXISTS)) {
65 Report.error (null, "GResources file `%s' does not exist".printf (gresource));
66 continue;
69 MarkupReader reader = new MarkupReader (gresource);
71 int state = 0;
72 string prefix = null;
73 string alias = null;
75 MarkupTokenType current_token = reader.read_token (null, null);
76 while (current_token != MarkupTokenType.EOF) {
77 if (current_token == MarkupTokenType.START_ELEMENT && reader.name == "gresource") {
78 prefix = reader.get_attribute ("prefix");
79 } else if (current_token == MarkupTokenType.START_ELEMENT && reader.name == "file") {
80 alias = reader.get_attribute ("alias");
81 state = 1;
82 } else if (state == 1 && current_token == MarkupTokenType.TEXT) {
83 var name = reader.content;
84 var filename = context.get_gresource_path (gresource, name);
85 if (alias != null) {
86 gresource_to_file_map.set (Path.build_filename (prefix, alias), filename);
88 gresource_to_file_map.set (Path.build_filename (prefix, name), filename);
89 state = 0;
91 current_token = reader.read_token (null, null);
96 private void process_current_ui_resource (string ui_resource, CodeNode node) {
97 /* Scan a single gtkbuilder file for signal handlers in <object> elements,
98 and save an handler string -> Vala.Signal mapping for each of them */
99 ensure_cclass_to_vala_map();
100 ensure_gresource_to_file_map();
102 current_handler_to_signal_map = null;
103 current_child_to_class_map = null;
104 var ui_file = gresource_to_file_map.get (ui_resource);
105 if (ui_file == null || !FileUtils.test (ui_file, FileTest.EXISTS)) {
106 node.error = true;
107 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));
108 return;
110 current_handler_to_signal_map = new HashMap<string, Signal>(str_hash, str_equal);
111 current_child_to_class_map = new HashMap<string, Class>(str_hash, str_equal);
113 MarkupReader reader = new MarkupReader (ui_file);
114 Class current_class = null;
116 bool template_tag_found = false;
117 MarkupTokenType current_token = reader.read_token (null, null);
118 while (current_token != MarkupTokenType.EOF) {
119 if (current_token == MarkupTokenType.START_ELEMENT && (reader.name == "template" || reader.name == "object")) {
120 if (reader.name == "template") {
121 template_tag_found = true;
123 var class_name = reader.get_attribute ("class");
124 if (class_name != null) {
125 current_class = cclass_to_vala_map.get (class_name);
127 var child_name = reader.get_attribute ("id");
128 if (child_name != null) {
129 current_child_to_class_map.set (child_name, current_class);
132 } else if (current_class != null && current_token == MarkupTokenType.START_ELEMENT && reader.name == "signal") {
133 var signal_name = reader.get_attribute ("name");
134 var handler_name = reader.get_attribute ("handler");
136 if (current_class != null) {
137 if (signal_name == null || handler_name == null) {
138 Report.error (node.source_reference, "Invalid signal in ui file `%s'".printf (ui_file));
139 current_token = reader.read_token (null, null);
140 continue;
142 var sep_idx = signal_name.index_of ("::");
143 if (sep_idx >= 0) {
144 // detailed signal, we don't care about the detail
145 signal_name = signal_name.substring (0, sep_idx);
148 var sig = SemanticAnalyzer.symbol_lookup_inherited (current_class, signal_name.replace ("-", "_")) as Signal;
149 if (sig != null) {
150 current_handler_to_signal_map.set (handler_name, sig);
154 current_token = reader.read_token (null, null);
157 if (!template_tag_found) {
158 Report.error (node.source_reference, "ui resource `%s' does not describe a valid composite template".printf (ui_resource));
162 private bool is_gtk_template (Class cl) {
163 var attr = cl.get_attribute ("GtkTemplate");
164 if (attr != null) {
165 if (gtk_widget_type == null || !cl.is_subtype_of (gtk_widget_type)) {
166 if (!cl.error) {
167 Report.error (attr.source_reference, "subclassing Gtk.Widget is required for using Gtk templates");
168 cl.error = true;
170 return false;
172 if (!context.require_glib_version (2, 38)) {
173 if (!cl.error) {
174 Report.error (attr.source_reference, "glib 2.38 is required for using Gtk templates (with --target-glib=2.38)");
175 cl.error = true;
177 return false;
179 return true;
181 return false;
184 public override void generate_class_init (Class cl) {
185 base.generate_class_init (cl);
187 if (cl.error || !is_gtk_template (cl)) {
188 return;
191 // this check is necessary because calling get_instance_private_offset otherwise will result in an error
192 if (cl.has_private_fields) {
193 var private_offset = "%s_private_offset".printf (get_ccode_name (cl));
194 ccode.add_declaration ("gint", new CCodeVariableDeclarator (private_offset));
196 var cgetprivcall = new CCodeFunctionCall (new CCodeIdentifier ("g_type_class_get_instance_private_offset"));
197 cgetprivcall.add_argument (new CCodeIdentifier ("klass"));
198 ccode.add_assignment (new CCodeIdentifier (private_offset), cgetprivcall);
201 /* Gtk builder widget template */
202 var ui = cl.get_attribute_string ("GtkTemplate", "ui");
203 if (ui == null) {
204 Report.error (cl.source_reference, "empty ui resource declaration for Gtk widget template");
205 cl.error = true;
206 return;
209 process_current_ui_resource (ui, cl);
211 var call = new CCodeFunctionCall (new CCodeIdentifier ("gtk_widget_class_set_template_from_resource"));
212 call.add_argument (new CCodeIdentifier ("GTK_WIDGET_CLASS (klass)"));
213 call.add_argument (new CCodeConstant ("\""+cl.get_attribute_string ("GtkTemplate", "ui")+"\""));
214 ccode.add_expression (call);
216 current_required_app_classes.clear ();
219 public override void visit_property (Property prop) {
220 if (prop.get_attribute ("GtkChild") != null) {
221 Report.error (prop.source_reference, "Annotating properties with [GtkChild] is not yet supported");
224 base.visit_property (prop);
227 public override void visit_field (Field f) {
228 base.visit_field (f);
230 var cl = current_class;
231 if (cl == null || cl.error) {
232 return;
235 if (f.binding != MemberBinding.INSTANCE || f.get_attribute ("GtkChild") == null) {
236 return;
239 /* If the field has a [GtkChild] attribute but its class doesn'thave a
240 [GtkTemplate] attribute, we throw an error */
241 if (!is_gtk_template (cl)) {
242 Report.error (f.source_reference, "[GtkChild] is only allowed in classes with a [GtkTemplate] attribute");
243 return;
246 push_context (class_init_context);
248 /* Map ui widget to a class field */
249 var gtk_name = f.get_attribute_string ("GtkChild", "name", f.name);
250 var child_class = current_child_to_class_map.get (gtk_name);
251 if (child_class == null) {
252 Report.error (f.source_reference, "could not find child `%s'".printf (gtk_name));
253 return;
256 /* We allow Gtk child to have stricter type than class field */
257 var field_class = f.variable_type.data_type as Class;
258 if (field_class == null || !child_class.is_subtype_of (field_class)) {
259 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()));
260 return;
263 var internal_child = f.get_attribute_bool ("GtkChild", "internal");
265 CCodeExpression offset;
266 if (f.is_private_symbol ()) {
267 // new glib api, we add the private struct offset to get the final field offset out of the instance
268 var private_field_offset = new CCodeFunctionCall (new CCodeIdentifier ("G_STRUCT_OFFSET"));
269 private_field_offset.add_argument (new CCodeIdentifier ("%sPrivate".printf (get_ccode_name (cl))));
270 private_field_offset.add_argument (new CCodeIdentifier (get_ccode_name (f)));
271 offset = new CCodeBinaryExpression (CCodeBinaryOperator.PLUS, new CCodeIdentifier ("%s_private_offset".printf (get_ccode_name (cl))), private_field_offset);
272 } else {
273 var offset_call = new CCodeFunctionCall (new CCodeIdentifier ("G_STRUCT_OFFSET"));
274 offset_call.add_argument (new CCodeIdentifier (get_ccode_name (cl)));
275 offset_call.add_argument (new CCodeIdentifier (get_ccode_name (f)));
276 offset = offset_call;
279 var call = new CCodeFunctionCall (new CCodeIdentifier ("gtk_widget_class_bind_template_child_full"));
280 call.add_argument (new CCodeIdentifier ("GTK_WIDGET_CLASS (klass)"));
281 call.add_argument (new CCodeConstant ("\"%s\"".printf (gtk_name)));
282 call.add_argument (new CCodeConstant (internal_child ? "TRUE" : "FALSE"));
283 call.add_argument (offset);
284 ccode.add_expression (call);
286 pop_context ();
288 if (!field_class.external && !field_class.external_package) {
289 current_required_app_classes.add (field_class);
293 public override void visit_method (Method m) {
294 base.visit_method (m);
296 var cl = current_class;
297 if (cl == null || cl.error || !is_gtk_template (cl)) {
298 return;
301 if (m.binding != MemberBinding.INSTANCE || m.get_attribute ("GtkCallback") == null) {
302 return;
305 /* Handler name as defined in the gtkbuilder xml */
306 var handler_name = m.get_attribute_string ("GtkCallback", "name", m.name);
307 var sig = current_handler_to_signal_map.get (handler_name);
308 if (sig == null) {
309 Report.error (m.source_reference, "could not find signal for handler `%s'".printf (handler_name));
310 return;
313 push_context (class_init_context);
315 if (sig != null) {
316 sig.check (context);
317 var method_type = new MethodType (m);
318 var signal_type = new SignalType (sig);
319 var delegate_type = signal_type.get_handler_type ();
320 if (!method_type.compatible (delegate_type)) {
321 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)));
322 } else {
323 var wrapper = generate_delegate_wrapper (m, signal_type.get_handler_type (), m);
325 var call = new CCodeFunctionCall (new CCodeIdentifier ("gtk_widget_class_bind_template_callback_full"));
326 call.add_argument (new CCodeIdentifier ("GTK_WIDGET_CLASS (klass)"));
327 call.add_argument (new CCodeConstant ("\"%s\"".printf (handler_name)));
328 call.add_argument (new CCodeIdentifier ("G_CALLBACK(%s)".printf (wrapper)));
329 ccode.add_expression (call);
333 pop_context ();
337 public override void end_instance_init (Class cl) {
338 if (cl == null || cl.error || !is_gtk_template (cl)) {
339 return;
342 foreach (var req in current_required_app_classes) {
343 /* ensure custom application widgets are initialized */
344 var call = new CCodeFunctionCall (new CCodeIdentifier ("g_type_ensure"));
345 call.add_argument (get_type_id_expression (SemanticAnalyzer.get_data_type_for_symbol (req)));
346 ccode.add_expression (call);
349 var call = new CCodeFunctionCall (new CCodeIdentifier ("gtk_widget_init_template"));
350 call.add_argument (new CCodeIdentifier ("GTK_WIDGET (self)"));
351 ccode.add_expression (call);