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
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) {
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()) {
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) {
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
));
69 MarkupReader reader
= new
MarkupReader (gresource
);
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");
82 } else if (state
== 1 && current_token
== MarkupTokenType
.TEXT
) {
83 var name
= reader
.content
;
84 var filename
= context
.get_gresource_path (gresource
, name
);
86 gresource_to_file_map
.set (Path
.build_filename (prefix
, alias
), filename
);
88 gresource_to_file_map
.set (Path
.build_filename (prefix
, name
), filename
);
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
)) {
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
));
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);
142 var sep_idx
= signal_name
.index_of ("::");
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
;
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");
165 if (gtk_widget_type
== null || !cl
.is_subtype_of (gtk_widget_type
)) {
167 Report
.error (attr
.source_reference
, "subclassing Gtk.Widget is required for using Gtk templates");
172 if (!context
.require_glib_version (2, 38)) {
174 Report
.error (attr
.source_reference
, "glib 2.38 is required for using Gtk templates (with --target-glib=2.38)");
184 public override void generate_class_init (Class cl
) {
185 base.generate_class_init (cl
);
187 if (cl
.error
|| !is_gtk_template (cl
)) {
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");
204 Report
.error (cl
.source_reference
, "empty ui resource declaration for Gtk widget template");
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
) {
235 if (f
.binding
!= MemberBinding
.INSTANCE
|| f
.get_attribute ("GtkChild") == null) {
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");
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
));
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()));
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
);
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
);
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
)) {
301 if (m
.binding
!= MemberBinding
.INSTANCE
|| m
.get_attribute ("GtkCallback") == null) {
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
);
309 Report
.error (m
.source_reference
, "could not find signal for handler `%s'".printf (handler_name
));
313 push_context (class_init_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
)));
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
);
337 public override void end_instance_init (Class cl
) {
338 if (cl
== null || cl
.error
|| !is_gtk_template (cl
)) {
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
);