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 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) {
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()) {
51 var type_id
= get_ccode_type_id (cl
);
55 var i
= type_id
.index_of_char ('(');
57 type_id
= type_id
.substring (0, i
- 1).strip ();
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) {
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()) {
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) {
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
));
101 MarkupReader reader
= new
MarkupReader (gresource
);
104 string prefix
= 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");
114 } else if (state
== 1 && current_token
== MarkupTokenType
.TEXT
) {
115 var name
= reader
.content
;
116 var filename
= context
.get_gresource_path (gresource
, name
);
118 gresource_to_file_map
.set (Path
.build_filename (prefix
, alias
), filename
);
120 gresource_to_file_map
.set (Path
.build_filename (prefix
, name
), filename
);
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
)) {
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
));
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);
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);
191 var sep_idx
= signal_name
.index_of ("::");
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
;
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");
214 if (gtk_widget_type
== null || !cl
.is_subtype_of (gtk_widget_type
)) {
216 Report
.error (attr
.source_reference
, "subclassing Gtk.Widget is required for using Gtk templates");
226 public override void generate_class_init (Class cl
) {
227 base.generate_class_init (cl
);
229 if (cl
.error
|| !is_gtk_template (cl
)) {
233 /* Gtk builder widget template */
234 var ui
= cl
.get_attribute_string ("GtkTemplate", "ui");
236 Report
.error (cl
.source_reference
, "empty ui resource declaration for Gtk widget template");
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
) {
267 if (f
.binding
!= MemberBinding
.INSTANCE
|| f
.get_attribute ("GtkChild") == null) {
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");
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
));
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()));
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
);
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
);
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
)) {
333 if (m
.binding
!= MemberBinding
.INSTANCE
|| m
.get_attribute ("GtkCallback") == null) {
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
);
341 Report
.error (m
.source_reference
, "could not find signal for handler `%s'".printf (handler_name
));
345 push_context (class_init_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
)));
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
);
369 public override void end_instance_init (Class cl
) {
370 if (cl
== null || cl
.error
|| !is_gtk_template (cl
)) {
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
);