.
1 /*
2 Copyright (C) 2016 Johan Mattsson
3
4 This library is free software; you can redistribute it and/or modify
5 it under the terms of the GNU Lesser General Public License as
6 published by the Free Software Foundation; either version 3 of the
7 License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful, but
10 WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13 */
14
15 using B;
16 using SvgBird;
17 using Gee;
18 using Cairo;
19
20 namespace Bird {
21
22 public abstract class Component : GLib.Object {
23 public double width { get; protected set; }
24 public double height { get; protected set; }
25
26 public double padded_width {
27 get {
28 return width + get_padding_top () + get_padding_bottom ();
29 }
30 }
31
32 public double padded_height {
33 get {
34 return height + get_padding_left () + get_padding_right ();
35 }
36 }
37
38 public double padded_x {
39 get {
40 return x + get_padding_top ();
41 }
42 }
43
44 public double padded_y {
45 get {
46 return y + get_padding_left ();
47 }
48 }
49
50 /** Vertical placement for this component relative to the parent container. */
51 public double x { get; protected set; }
52
53 /** Horizontal placement for this component relative to the parent container. */
54 public double y { get; protected set; }
55
56 /** The parts this component is made of. */
57 protected ArrayList<Component> components = new ArrayList<Component> ();
58
59 XmlElement component_tag;
60
61 /** Style sheet and other SVG definitions. */
62 Defs defs = new Defs ();
63 protected SvgStyle style = new SvgStyle ();
64
65 string? css_class = null;
66 string? id = null;
67
68 Overflow overflow = Overflow.VISIBLE;
69
70 public Component.load (string file_name, double width, double height) {
71 this.width = width;
72 this.height = height;
73 load_file (file_name, null);
74 layout (width, height);
75 }
76
77 /** Parse component ui file but don't do layout. */
78 internal Component.load_component (string file_name, Component parent) {
79 load_file (file_name, parent);
80 }
81
82 public Component (XmlElement component_tag, Defs defs) {
83 this.defs = defs;
84 this.component_tag = component_tag;
85 set_identification ();
86 parse (component_tag);
87 }
88
89 public Component.embedded (XmlElement component_tag, Defs defs) {
90 this.defs = defs;
91 this.component_tag = component_tag;
92 set_identification ();
93 }
94
95 void set_identification () {
96 foreach (Attribute attribute in component_tag.get_attributes ()) {
97 string attribute_name = attribute.get_name ();
98
99 if (attribute_name == "id") {
100 id = attribute.get_content ();
101 } else if (attribute_name == "class") {
102 css_class = attribute.get_content ();
103 }
104 }
105 }
106
107 private void inherit_styles_sheet (Defs defs) {
108 this.defs = defs;
109 }
110
111 protected void parse_style (XmlElement style_tag) {
112 defs = defs.shallow_copy ();
113 StyleSheet style_sheet = StyleSheet.parse (defs, style_tag);
114 defs.style_sheet.merge (style_sheet);
115 }
116
117 protected void parse_svg (XmlElement svg_tag) {
118 foreach (Attribute attribute in svg_tag.get_attributes ()) {
119 string attribute_name = attribute.get_name ();
120
121 if (attribute_name == "file") {
122 string file_name = attribute.get_content ();
123 SvgComponent svg = new SvgComponent (svg_tag, defs, file_name);
124 add_component (svg);
125 }
126 }
127 }
128
129 protected void add_component (Component component) {
130 components.add (component);
131 component.set_style_properties (this);
132 }
133
134 protected void set_style_properties (Component parent) {
135 inherit_styles_sheet (parent.defs);
136 style = SvgStyle.parse (parent.defs, parent.style, component_tag);
137 set_overflow_property_from_css ();
138 set_identification ();
139 }
140
141 protected void set_overflow_property_from_css () {
142 string? css_overflow = style.get_css_property ("overflow");
143
144 if (css_overflow != null) {
145 string overflow_property = (!) css_overflow;
146
147 if (overflow_property == "hidden") {
148 overflow = Overflow.HIDDEN;
149 } else if (overflow_property == "visible") {
150 overflow = Overflow.VISIBLE;
151 }
152 }
153 }
154
155 protected void parse_layout (XmlElement layout_tag) {
156 string type = "";
157
158 foreach (Attribute attribute in layout_tag.get_attributes ()) {
159 string attribute_name = attribute.get_name ();
160
161 if (attribute_name == "type") {
162 type = attribute.get_content ();
163 }
164 }
165
166 if (type == "hbox") {
167 HBox hbox = new HBox (layout_tag, defs);
168 add_component (hbox);
169 } else if (type == "vbox") {
170 VBox vbox = new VBox (layout_tag, defs);
171 add_component (vbox);
172 } else {
173 warning (type + " layout of type is supported in this verison.");
174 }
175 }
176
177 protected void parse_component (XmlElement component_tag) {
178 foreach (Attribute attribute in component_tag.get_attributes ()) {
179 string attribute_name = attribute.get_name ();
180
181 if (attribute_name == "file") {
182 string file_name = attribute.get_content ();
183 UI ui = new UI.load_component (file_name, this);
184
185 foreach (Component component in ui.components) {
186 add_component (component);
187
188 SvgStyle copied_style;
189 copied_style = SvgStyle.parse (defs, style, component_tag);
190 copied_style.apply (component.style);
191 component.style = copied_style;
192 }
193 }
194 }
195 }
196
197 protected void parse (XmlElement component_tag) {
198 foreach (XmlElement tag in component_tag) {
199 string tag_name = tag.get_name ();
200
201 if (tag_name == "ui") {
202 parse (tag);
203 } else if (tag_name == "layout") {
204 parse_layout (tag);
205 } else if (tag_name == "component") {
206 parse_component (tag);
207 } else if (tag_name == "svg") {
208 parse_svg (tag);
209 } else if (tag_name == "style") {
210 parse_style (tag);
211 } else {
212 unused_tag (tag_name);
213 }
214 }
215 }
216
217 internal void unused_attribute (string attribute) {
218 warning ("The attribute " + attribute + " is not known in this version.");
219 }
220
221 internal void unused_tag (string tag_name) {
222 warning ("The tag " + tag_name + " is not known in this version.");
223 }
224
225 public void load_file (string file_name, Component? parent) {
226 if (file_name.has_suffix (".ui")) {
227 load_layout (file_name, parent);
228 } else if (file_name.has_suffix (".svg")) {
229 SvgComponent svg = new SvgComponent.for_file (file_name);
230 add_component (svg);
231 } else {
232 warning (file_name + " is not a ui file or svg file.");
233 }
234 }
235
236 public void load_layout (string file_name, Component? parent) {
237 string? path = find_file (file_name);
238
239 if (path == null) {
240 warning (file_name + " not found.");
241 return;
242 }
243
244 string xml_data;
245 File layout_file = File.new_for_path ((!) path);
246
247 try {
248 FileUtils.get_contents((!) layout_file.get_path (), out xml_data);
249 } catch (GLib.Error error) {
250 warning (error.message);
251 return;
252 }
253
254 XmlTree xml_parser = new XmlTree (xml_data);
255 XmlElement tag = xml_parser.get_root ();
256
257 if (parent == null) {
258 component_tag = tag;
259 defs = new Defs ();
260 } else {
261 Component p = (!) parent;
262 component_tag = p.component_tag;
263 defs = p.defs;
264 }
265
266 parse (tag);
267 }
268
269 public static string find_file (string file_name) {
270 File file = File.new_for_path ("birdui/" + file_name);
271
272 if (file.query_exists ()) {
273 return (!) file.get_path ();
274 }
275
276 return file_name;
277 }
278
279 public virtual void get_min_size (out double min_width, out double min_height) {
280 min_width = width;
281 min_height = height;
282
283 if (unlikely (components.size > 1)) {
284 warning ("A component has several parts but no layout has been set.");
285 }
286
287 foreach (Component component in components) {
288 component.get_min_size (out min_width, out min_height);
289 }
290
291 min_width += get_padding_left ();
292 min_width += get_padding_right ();
293 min_height += get_padding_top ();
294 min_height += get_padding_bottom ();
295 }
296
297 public virtual void layout (double parent_width, double parent_height) {
298 warning ("A component without a layout was added to the view.");
299
300 double w, h;
301 get_min_size (out w, out h);
302
303 width = w;
304 height = h;
305
306 foreach (Component component in components) {
307 component.layout (parent_width, parent_height);
308 }
309 }
310
311 public void clip (Context cairo) {
312 if (overflow == Overflow.HIDDEN) {
313 cairo.rectangle (0, 0, width, height);
314 cairo.clip ();
315 }
316 }
317
318 public abstract void draw (Context cairo);
319
320 public virtual void motion_notify_event (double x, double y) {
321 }
322
323 public virtual void button_press_event (uint button, double x, double y) {
324 }
325
326 public virtual string to_string () {
327 return @"$(component_tag.get_name ())";
328 }
329
330 public void print_tree () {
331 print_tree_level (0);
332 }
333
334 protected void print_tree_level (int indent) {
335 for (int i = 0; i < indent; i++) {
336 print ("\t");
337 }
338
339 print (@"$(to_string ()) width: $padded_width, height: $padded_height x $x y $y $(style)\n");
340
341 foreach (Component component in components) {
342 component.print_tree_level (indent + 1);
343 }
344 }
345
346 protected double get_padding_bottom () {
347 return SvgFile.parse_number (style.get_css_property ("padding-bottom"));
348 }
349
350 protected double get_padding_top () {
351 return SvgFile.parse_number (style.get_css_property ("padding-top"));
352 }
353
354 protected double get_padding_left () {
355 return SvgFile.parse_number (style.get_css_property ("padding-left"));
356 }
357
358 protected double get_padding_right () {
359 return SvgFile.parse_number (style.get_css_property ("padding-right"));
360 }
361
362 protected void limit_size (ref double width, ref double height) {
363 string? min_width = style.get_css_property ("min-width");
364 if (min_width != null) {
365 double w = SvgFile.parse_number (min_width);
366 if (width < w) {
367 width = w;
368 }
369 }
370
371 string? min_height = style.get_css_property ("min-height");
372 if (min_height != null) {
373 double h = SvgFile.parse_number (min_height);
374
375 if (height < h) {
376 height = h;
377 }
378 }
379
380 string? max_width = style.get_css_property ("max-width");
381 if (max_width != null) {
382 double w = SvgFile.parse_number (max_width);
383
384 if (width > w) {
385 width = w;
386 }
387 }
388
389 string? max_height = style.get_css_property ("max-height");
390 if (max_height != null) {
391 double h = SvgFile.parse_number (max_height);
392
393 if (height > h) {
394 height = h;
395 }
396 }
397
398 if (width < 0) {
399 width = 0;
400 }
401
402 if (height < 0) {
403 height = 0;
404 }
405 }
406 }
407
408 }
409
410