]> Pileus Git - ~andy/gtk/blob - gtk/gtkcssimage.c
cssimage: Add an equal vfunc
[~andy/gtk] / gtk / gtkcssimage.c
1 /*
2  * Copyright © 2011 Red Hat Inc.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but 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  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16  *
17  * Authors: Benjamin Otte <otte@gnome.org>
18  */
19
20 #include "config.h"
21
22 #include "gtkcssimageprivate.h"
23
24 #include "gtkcsscomputedvaluesprivate.h"
25
26 /* for the types only */
27 #include "gtk/gtkcssimagecrossfadeprivate.h"
28 #include "gtk/gtkcssimagegradientprivate.h"
29 #include "gtk/gtkcssimagelinearprivate.h"
30 #include "gtk/gtkcssimageurlprivate.h"
31 #include "gtk/gtkcssimagewin32private.h"
32
33 G_DEFINE_ABSTRACT_TYPE (GtkCssImage, _gtk_css_image, G_TYPE_OBJECT)
34
35 static int
36 gtk_css_image_real_get_width (GtkCssImage *image)
37 {
38   return 0;
39 }
40
41 static int
42 gtk_css_image_real_get_height (GtkCssImage *image)
43 {
44   return 0;
45 }
46
47 static double
48 gtk_css_image_real_get_aspect_ratio (GtkCssImage *image)
49 {
50   int width, height;
51
52   width = _gtk_css_image_get_width (image);
53   height = _gtk_css_image_get_height (image);
54
55   if (width && height)
56     return (double) width / height;
57   else
58     return 0;
59 }
60
61 static GtkCssImage *
62 gtk_css_image_real_compute (GtkCssImage             *image,
63                             guint                    property_id,
64                             GtkStyleProviderPrivate *provider,
65                             GtkCssComputedValues    *values,
66                             GtkCssComputedValues    *parent_values,
67                             GtkCssDependencies      *dependencies)
68 {
69   return g_object_ref (image);
70 }
71
72 static gboolean
73 gtk_css_image_real_equal (GtkCssImage *image1,
74                           GtkCssImage *image2)
75 {
76   return FALSE;
77 }
78
79 static GtkCssImage *
80 gtk_css_image_real_transition (GtkCssImage *start,
81                                GtkCssImage *end,
82                                guint        property_id,
83                                double       progress)
84 {
85   if (progress <= 0.0)
86     return g_object_ref (start);
87   else if (progress >= 1.0)
88     return end ? g_object_ref (end) : NULL;
89   else
90     return _gtk_css_image_cross_fade_new (start, end, progress);
91 }
92
93 static void
94 _gtk_css_image_class_init (GtkCssImageClass *klass)
95 {
96   klass->get_width = gtk_css_image_real_get_width;
97   klass->get_height = gtk_css_image_real_get_height;
98   klass->get_aspect_ratio = gtk_css_image_real_get_aspect_ratio;
99   klass->compute = gtk_css_image_real_compute;
100   klass->equal = gtk_css_image_real_equal;
101   klass->transition = gtk_css_image_real_transition;
102 }
103
104 static void
105 _gtk_css_image_init (GtkCssImage *image)
106 {
107 }
108
109 int
110 _gtk_css_image_get_width (GtkCssImage *image)
111 {
112   GtkCssImageClass *klass;
113
114   g_return_val_if_fail (GTK_IS_CSS_IMAGE (image), 0);
115
116   klass = GTK_CSS_IMAGE_GET_CLASS (image);
117
118   return klass->get_width (image);
119 }
120
121 int
122 _gtk_css_image_get_height (GtkCssImage *image)
123 {
124   GtkCssImageClass *klass;
125
126   g_return_val_if_fail (GTK_IS_CSS_IMAGE (image), 0);
127
128   klass = GTK_CSS_IMAGE_GET_CLASS (image);
129
130   return klass->get_height (image);
131 }
132
133 double
134 _gtk_css_image_get_aspect_ratio (GtkCssImage *image)
135 {
136   GtkCssImageClass *klass;
137
138   g_return_val_if_fail (GTK_IS_CSS_IMAGE (image), 0);
139
140   klass = GTK_CSS_IMAGE_GET_CLASS (image);
141
142   return klass->get_aspect_ratio (image);
143 }
144
145 GtkCssImage *
146 _gtk_css_image_compute (GtkCssImage             *image,
147                         guint                    property_id,
148                         GtkStyleProviderPrivate *provider,
149                         GtkCssComputedValues    *values,
150                         GtkCssComputedValues    *parent_values,
151                         GtkCssDependencies      *dependencies)
152 {
153   GtkCssDependencies unused;
154   GtkCssImageClass *klass;
155
156   g_return_val_if_fail (GTK_IS_CSS_IMAGE (image), NULL);
157   g_return_val_if_fail (GTK_IS_CSS_COMPUTED_VALUES (values), NULL);
158   g_return_val_if_fail (parent_values == NULL || GTK_IS_CSS_COMPUTED_VALUES (parent_values), NULL);
159
160   if (dependencies == NULL)
161     dependencies = &unused;
162   *dependencies = 0;
163
164   klass = GTK_CSS_IMAGE_GET_CLASS (image);
165
166   return klass->compute (image, property_id, provider, values, parent_values, dependencies);
167 }
168
169 GtkCssImage *
170 _gtk_css_image_transition (GtkCssImage *start,
171                            GtkCssImage *end,
172                            guint        property_id,
173                            double       progress)
174 {
175   GtkCssImageClass *klass;
176
177   g_return_val_if_fail (start == NULL || GTK_IS_CSS_IMAGE (start), NULL);
178   g_return_val_if_fail (end == NULL || GTK_IS_CSS_IMAGE (end), NULL);
179
180   progress = CLAMP (progress, 0.0, 1.0);
181
182   if (start == NULL)
183     {
184       if (end == NULL)
185         return NULL;
186       else
187         {
188           start = end;
189           end = NULL;
190           progress = 1.0 - progress;
191         }
192     }
193
194   klass = GTK_CSS_IMAGE_GET_CLASS (start);
195
196   return klass->transition (start, end, property_id, progress);
197 }
198
199 gboolean
200 _gtk_css_image_equal (GtkCssImage *image1,
201                       GtkCssImage *image2)
202 {
203   GtkCssImageClass *klass;
204
205   g_return_val_if_fail (image1 == NULL || GTK_IS_CSS_IMAGE (image1), FALSE);
206   g_return_val_if_fail (image2 == NULL || GTK_IS_CSS_IMAGE (image2), FALSE);
207
208   if (image1 == image2)
209     return TRUE;
210
211   if (image1 == NULL || image2 == NULL)
212     return FALSE;
213
214   if (G_OBJECT_TYPE (image1) != G_OBJECT_TYPE (image2))
215     return FALSE;
216
217   klass = GTK_CSS_IMAGE_GET_CLASS (image1);
218
219   return klass->equal (image1, image2);
220 }
221
222 void
223 _gtk_css_image_draw (GtkCssImage        *image,
224                      cairo_t            *cr,
225                      double              width,
226                      double              height)
227 {
228   GtkCssImageClass *klass;
229
230   g_return_if_fail (GTK_IS_CSS_IMAGE (image));
231   g_return_if_fail (cr != NULL);
232
233   cairo_save (cr);
234
235   klass = GTK_CSS_IMAGE_GET_CLASS (image);
236
237   klass->draw (image, cr, width, height);
238
239   cairo_restore (cr);
240 }
241
242 void
243 _gtk_css_image_print (GtkCssImage *image,
244                       GString     *string)
245 {
246   GtkCssImageClass *klass;
247
248   g_return_if_fail (GTK_IS_CSS_IMAGE (image));
249   g_return_if_fail (string != NULL);
250
251   klass = GTK_CSS_IMAGE_GET_CLASS (image);
252
253   klass->print (image, string);
254 }
255
256 /* Applies the algorithm outlined in
257  * http://dev.w3.org/csswg/css3-images/#default-sizing
258  */
259 void
260 _gtk_css_image_get_concrete_size (GtkCssImage *image,
261                                   double       specified_width,
262                                   double       specified_height,
263                                   double       default_width,
264                                   double       default_height,
265                                   double      *concrete_width,
266                                   double      *concrete_height)
267 {
268   double image_width, image_height, image_aspect;
269
270   g_return_if_fail (GTK_IS_CSS_IMAGE (image));
271   g_return_if_fail (specified_width >= 0);
272   g_return_if_fail (specified_height >= 0);
273   g_return_if_fail (default_width > 0);
274   g_return_if_fail (default_height > 0);
275   g_return_if_fail (concrete_width != NULL);
276   g_return_if_fail (concrete_height != NULL);
277
278   /* If the specified size is a definite width and height,
279    * the concrete object size is given that width and height.
280    */
281   if (specified_width && specified_height)
282     {
283       *concrete_width = specified_width;
284       *concrete_height = specified_height;
285       return;
286     }
287
288   image_width  = _gtk_css_image_get_width (image);
289   image_height = _gtk_css_image_get_height (image);
290   image_aspect = _gtk_css_image_get_aspect_ratio (image);
291
292   /* If the specified size has neither a definite width nor height,
293    * and has no additional contraints, the dimensions of the concrete
294    * object size are calculated as follows:
295    */
296   if (specified_width == 0.0 && specified_height == 0.0)
297     {
298       /* If the object has only an intrinsic aspect ratio,
299        * the concrete object size must have that aspect ratio,
300        * and additionally be as large as possible without either
301        * its height or width exceeding the height or width of the
302        * default object size.
303        */
304       if (image_aspect > 0 && image_width == 0 && image_height == 0)
305         {
306           if (image_aspect * default_height > default_width)
307             {
308               *concrete_width = default_height * image_aspect;
309               *concrete_height = default_height;
310             }
311           else
312             {
313               *concrete_width = default_width;
314               *concrete_height = default_width / image_aspect;
315             }
316         }
317
318       /* Otherwise, the width and height of the concrete object
319        * size is the same as the object's intrinsic width and
320        * intrinsic height, if they exist.
321        * If the concrete object size is still missing a width or
322        * height, and the object has an intrinsic aspect ratio,
323        * the missing dimension is calculated from the present
324        * dimension and the intrinsic aspect ratio.
325        * Otherwise, the missing dimension is taken from the default
326        * object size. 
327        */
328       if (image_width)
329         *concrete_width = image_width;
330       else if (image_aspect)
331         *concrete_width = image_height * image_aspect;
332       else
333         *concrete_width = default_width;
334
335       if (image_height)
336         *concrete_height = image_height;
337       else if (image_aspect)
338         *concrete_height = image_width / image_aspect;
339       else
340         *concrete_height = default_height;
341
342       return;
343     }
344
345   /* If the specified size has only a width or height, but not both,
346    * then the concrete object size is given that specified width or height.
347    * The other dimension is calculated as follows:
348    * If the object has an intrinsic aspect ratio, the missing dimension of
349    * the concrete object size is calculated using the intrinsic aspect-ratio
350    * and the present dimension.
351    * Otherwise, if the missing dimension is present in the object's intrinsic
352    * dimensions, the missing dimension is taken from the object's intrinsic
353    * dimensions.
354    * Otherwise, the missing dimension of the concrete object size is taken
355    * from the default object size. 
356    */
357   if (specified_width)
358     {
359       *concrete_width = specified_width;
360       if (image_aspect)
361         *concrete_height = specified_width / image_aspect;
362       else if (image_height)
363         *concrete_height = image_height;
364       else
365         *concrete_height = default_height;
366     }
367   else
368     {
369       *concrete_height = specified_height;
370       if (image_aspect)
371         *concrete_width = specified_height * image_aspect;
372       else if (image_width)
373         *concrete_width = image_width;
374       else
375         *concrete_width = default_width;
376     }
377 }
378
379 cairo_surface_t *
380 _gtk_css_image_get_surface (GtkCssImage     *image,
381                             cairo_surface_t *target,
382                             int              surface_width,
383                             int              surface_height)
384 {
385   cairo_surface_t *result;
386   cairo_t *cr;
387
388   g_return_val_if_fail (GTK_IS_CSS_IMAGE (image), NULL);
389   g_return_val_if_fail (surface_width > 0, NULL);
390   g_return_val_if_fail (surface_height > 0, NULL);
391
392   if (target)
393     result = cairo_surface_create_similar (target,
394                                            CAIRO_CONTENT_COLOR_ALPHA,
395                                            surface_width,
396                                            surface_height);
397   else
398     result = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
399                                          surface_width,
400                                          surface_height);
401
402   cr = cairo_create (result);
403   _gtk_css_image_draw (image, cr, surface_width, surface_height);
404   cairo_destroy (cr);
405
406   return result;
407 }
408
409 static GType
410 gtk_css_image_get_parser_type (GtkCssParser *parser)
411 {
412   static const struct {
413     const char *prefix;
414     GType (* type_func) (void);
415   } image_types[] = {
416     { "url", _gtk_css_image_url_get_type },
417     { "-gtk-gradient", _gtk_css_image_gradient_get_type },
418     { "-gtk-win32-theme-part", _gtk_css_image_win32_get_type },
419     { "linear-gradient", _gtk_css_image_linear_get_type },
420     { "repeating-linear-gradient", _gtk_css_image_linear_get_type },
421     { "cross-fade", _gtk_css_image_cross_fade_get_type }
422   };
423   guint i;
424
425   for (i = 0; i < G_N_ELEMENTS (image_types); i++)
426     {
427       if (_gtk_css_parser_has_prefix (parser, image_types[i].prefix))
428         return image_types[i].type_func ();
429     }
430
431   return G_TYPE_INVALID;
432 }
433
434 /**
435  * _gtk_css_image_can_parse:
436  * @parser: a css parser
437  *
438  * Checks if the parser can potentially parse the given stream as an
439  * image from looking at the first token of @parser. This is useful for
440  * implementing shorthand properties. A successful parse of an image
441  * can not be guaranteed.
442  *
443  * Returns: %TURE if it looks like an image.
444  **/
445 gboolean
446 _gtk_css_image_can_parse (GtkCssParser *parser)
447 {
448   return gtk_css_image_get_parser_type (parser) != G_TYPE_INVALID;
449 }
450
451 GtkCssImage *
452 _gtk_css_image_new_parse (GtkCssParser *parser)
453 {
454   GtkCssImageClass *klass;
455   GtkCssImage *image;
456   GType image_type;
457
458   g_return_val_if_fail (parser != NULL, NULL);
459
460   image_type = gtk_css_image_get_parser_type (parser);
461   if (image_type == G_TYPE_INVALID)
462     {
463       _gtk_css_parser_error (parser, "Not a valid image");
464       return NULL;
465     }
466
467   image = g_object_new (image_type, NULL);
468
469   klass = GTK_CSS_IMAGE_GET_CLASS (image);
470   if (!klass->parse (image, parser))
471     {
472       g_object_unref (image);
473       return NULL;
474     }
475
476   return image;
477 }
478