Release 0.41.92
[vala-gnome.git] / codegen / valagtkmodule.vala
blobda0fe9905bde805700d9cdfadc04ac55d73fb63e
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 /* Gtk builder widget template */
234 var ui = cl.get_attribute_string ("GtkTemplate", "ui");
235 if (ui == null) {
236 Report.error (cl.source_reference, "empty ui resource declaration for Gtk widget template");
237 cl.error = true;
238 return;
241 process_current_ui_resource (ui, cl);
243 var call = new CCodeFunctionCall (new CCodeIdentifier ("gtk_widget_class_set_template_from_resource"));
244 call.add_argument (new CCodeIdentifier ("GTK_WIDGET_CLASS (klass)"));
245 call.add_argument (new CCodeConstant ("\"%s\"".printf (ui)));
246 ccode.add_expression (call);
248 current_required_app_classes.clear ();
251 public override void visit_property (Property prop) {
252 if (prop.get_attribute ("GtkChild") != null && prop.field == null) {
253 Report.error (prop.source_reference, "[GtkChild] is only allowed on automatic properties");
256 base.visit_property (prop);
259 public override void visit_field (Field f) {
260 base.visit_field (f);
262 var cl = current_class;
263 if (cl == null || cl.error) {
264 return;
267 if (f.binding != MemberBinding.INSTANCE || f.get_attribute ("GtkChild") == null) {
268 return;
271 /* If the field has a [GtkChild] attribute but its class doesn'thave a
272 [GtkTemplate] attribute, we throw an error */
273 if (!is_gtk_template (cl)) {
274 Report.error (f.source_reference, "[GtkChild] is only allowed in classes with a [GtkTemplate] attribute");
275 return;
278 push_context (class_init_context);
280 /* Map ui widget to a class field */
281 var gtk_name = f.get_attribute_string ("GtkChild", "name", f.name);
282 var child_class = current_child_to_class_map.get (gtk_name);
283 if (child_class == null) {
284 Report.error (f.source_reference, "could not find child `%s'".printf (gtk_name));
285 return;
288 /* We allow Gtk child to have stricter type than class field */
289 var field_class = f.variable_type.data_type as Class;
290 if (field_class == null || !child_class.is_subtype_of (field_class)) {
291 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()));
292 return;
295 var internal_child = f.get_attribute_bool ("GtkChild", "internal");
297 CCodeExpression offset;
298 if (f.is_private_symbol ()) {
299 // new glib api, we add the private struct offset to get the final field offset out of the instance
300 var private_field_offset = new CCodeFunctionCall (new CCodeIdentifier ("G_STRUCT_OFFSET"));
301 private_field_offset.add_argument (new CCodeIdentifier ("%sPrivate".printf (get_ccode_name (cl))));
302 private_field_offset.add_argument (new CCodeIdentifier (get_ccode_name (f)));
303 offset = new CCodeBinaryExpression (CCodeBinaryOperator.PLUS, new CCodeIdentifier ("%s_private_offset".printf (get_ccode_name (cl))), private_field_offset);
304 } else {
305 var offset_call = new CCodeFunctionCall (new CCodeIdentifier ("G_STRUCT_OFFSET"));
306 offset_call.add_argument (new CCodeIdentifier (get_ccode_name (cl)));
307 offset_call.add_argument (new CCodeIdentifier (get_ccode_name (f)));
308 offset = offset_call;
311 var call = new CCodeFunctionCall (new CCodeIdentifier ("gtk_widget_class_bind_template_child_full"));
312 call.add_argument (new CCodeIdentifier ("GTK_WIDGET_CLASS (klass)"));
313 call.add_argument (new CCodeConstant ("\"%s\"".printf (gtk_name)));
314 call.add_argument (new CCodeConstant (internal_child ? "TRUE" : "FALSE"));
315 call.add_argument (offset);
316 ccode.add_expression (call);
318 pop_context ();
320 if (!field_class.external && !field_class.external_package) {
321 current_required_app_classes.add (field_class);
325 public override void visit_method (Method m) {
326 base.visit_method (m);
328 var cl = current_class;
329 if (cl == null || cl.error || !is_gtk_template (cl)) {
330 return;
333 if (m.binding != MemberBinding.INSTANCE || m.get_attribute ("GtkCallback") == null) {
334 return;
337 /* Handler name as defined in the gtkbuilder xml */
338 var handler_name = m.get_attribute_string ("GtkCallback", "name", m.name);
339 var sig = current_handler_to_signal_map.get (handler_name);
340 if (sig == null) {
341 Report.error (m.source_reference, "could not find signal for handler `%s'".printf (handler_name));
342 return;
345 push_context (class_init_context);
347 if (sig != null) {
348 sig.check (context);
349 var method_type = new MethodType (m);
350 var signal_type = new SignalType (sig);
351 var delegate_type = signal_type.get_handler_type ();
352 if (!method_type.compatible (delegate_type)) {
353 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)));
354 } else {
355 var wrapper = generate_delegate_wrapper (m, signal_type.get_handler_type (), m);
357 var call = new CCodeFunctionCall (new CCodeIdentifier ("gtk_widget_class_bind_template_callback_full"));
358 call.add_argument (new CCodeIdentifier ("GTK_WIDGET_CLASS (klass)"));
359 call.add_argument (new CCodeConstant ("\"%s\"".printf (handler_name)));
360 call.add_argument (new CCodeIdentifier ("G_CALLBACK(%s)".printf (wrapper)));
361 ccode.add_expression (call);
365 pop_context ();
369 public override void end_instance_init (Class cl) {
370 if (cl == null || cl.error || !is_gtk_template (cl)) {
371 return;
374 foreach (var req in current_required_app_classes) {
375 /* ensure custom application widgets are initialized */
376 var call = new CCodeFunctionCall (new CCodeIdentifier ("g_type_ensure"));
377 call.add_argument (get_type_id_expression (SemanticAnalyzer.get_data_type_for_symbol (req)));
378 ccode.add_expression (call);
381 var call = new CCodeFunctionCall (new CCodeIdentifier ("gtk_widget_init_template"));
382 call.add_argument (new CCodeIdentifier ("GTK_WIDGET (self)"));
383 ccode.add_expression (call);