1 | /* GTK - The GIMP Toolkit |
2 | * |
3 | * Copyright (C) 2009 Matthias Clasen <mclasen@redhat.com> |
4 | * Copyright (C) 2008 Richard Hughes <richard@hughsie.com> |
5 | * Copyright (C) 2009 Bastien Nocera <hadess@hadess.net> |
6 | * |
7 | * This library is free software; you can redistribute it and/or |
8 | * modify it under the terms of the GNU Lesser General Public |
9 | * License as published by the Free Software Foundation; either |
10 | * version 2 of the License, or (at your option) any later version. |
11 | * |
12 | * This library is distributed in the hope that it will be useful, |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
15 | * Lesser General Public License for more details. |
16 | * |
17 | * You should have received a copy of the GNU Lesser General Public |
18 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
19 | */ |
20 | |
21 | /* |
22 | * Modified by the GTK+ Team and others 2007. See the AUTHORS |
23 | * file for a list of people on the GTK+ Team. See the ChangeLog |
24 | * files for a list of changes. These files are distributed with |
25 | * GTK+ at ftp://ftp.gtk.org/pub/gtk/. |
26 | */ |
27 | |
28 | #include "config.h" |
29 | |
30 | #include "gtkcellrendererspinner.h" |
31 | #include "gtkiconhelperprivate.h" |
32 | #include "gtkintl.h" |
33 | #include "gtksettings.h" |
34 | #include "gtksnapshot.h" |
35 | #include "gtktypebuiltins.h" |
36 | #include "gtkstylecontextprivate.h" |
37 | #include "gtkcssnumbervalueprivate.h" |
38 | |
39 | #include <math.h> |
40 | |
41 | /** |
42 | * GtkCellRendererSpinner: |
43 | * |
44 | * Renders a spinning animation in a cell |
45 | * |
46 | * `GtkCellRendererSpinner` renders a spinning animation in a cell, very |
47 | * similar to `GtkSpinner`. It can often be used as an alternative |
48 | * to a `GtkCellRendererProgress` for displaying indefinite activity, |
49 | * instead of actual progress. |
50 | * |
51 | * To start the animation in a cell, set the `GtkCellRendererSpinner:active` |
52 | * property to %TRUE and increment the `GtkCellRendererSpinner:pulse` property |
53 | * at regular intervals. The usual way to set the cell renderer properties |
54 | * for each cell is to bind them to columns in your tree model using e.g. |
55 | * gtk_tree_view_column_add_attribute(). |
56 | */ |
57 | |
58 | |
59 | enum { |
60 | PROP_0, |
61 | PROP_ACTIVE, |
62 | PROP_PULSE, |
63 | PROP_SIZE |
64 | }; |
65 | |
66 | typedef struct _GtkCellRendererSpinnerClass GtkCellRendererSpinnerClass; |
67 | typedef struct _GtkCellRendererSpinnerPrivate GtkCellRendererSpinnerPrivate; |
68 | |
69 | struct _GtkCellRendererSpinner |
70 | { |
71 | GtkCellRenderer parent; |
72 | }; |
73 | |
74 | struct _GtkCellRendererSpinnerClass |
75 | { |
76 | GtkCellRendererClass parent_class; |
77 | |
78 | /* Padding for future expansion */ |
79 | void (*_gtk_reserved1) (void); |
80 | void (*_gtk_reserved2) (void); |
81 | void (*_gtk_reserved3) (void); |
82 | void (*_gtk_reserved4) (void); |
83 | }; |
84 | |
85 | struct _GtkCellRendererSpinnerPrivate |
86 | { |
87 | gboolean active; |
88 | guint pulse; |
89 | GtkIconSize icon_size; |
90 | int size; |
91 | }; |
92 | |
93 | |
94 | static void gtk_cell_renderer_spinner_get_property (GObject *object, |
95 | guint param_id, |
96 | GValue *value, |
97 | GParamSpec *pspec); |
98 | static void gtk_cell_renderer_spinner_set_property (GObject *object, |
99 | guint param_id, |
100 | const GValue *value, |
101 | GParamSpec *pspec); |
102 | static void gtk_cell_renderer_spinner_get_size (GtkCellRendererSpinner *self, |
103 | GtkWidget *widget, |
104 | const GdkRectangle *cell_area, |
105 | int *x_offset, |
106 | int *y_offset, |
107 | int *width, |
108 | int *height); |
109 | static void gtk_cell_renderer_spinner_snapshot (GtkCellRenderer *cell, |
110 | GtkSnapshot *snapshot, |
111 | GtkWidget *widget, |
112 | const GdkRectangle *background_area, |
113 | const GdkRectangle *cell_area, |
114 | GtkCellRendererState flags); |
115 | |
116 | G_DEFINE_TYPE_WITH_PRIVATE (GtkCellRendererSpinner, gtk_cell_renderer_spinner, GTK_TYPE_CELL_RENDERER) |
117 | |
118 | static GtkSizeRequestMode |
119 | gtk_cell_renderer_spinner_get_request_mode (GtkCellRenderer *cell) |
120 | { |
121 | return GTK_SIZE_REQUEST_CONSTANT_SIZE; |
122 | } |
123 | |
124 | static void |
125 | gtk_cell_renderer_spinner_get_preferred_width (GtkCellRenderer *cell, |
126 | GtkWidget *widget, |
127 | int *minimum, |
128 | int *natural) |
129 | { |
130 | int size = 0; |
131 | |
132 | gtk_cell_renderer_spinner_get_size (GTK_CELL_RENDERER_SPINNER (cell), widget, |
133 | NULL, |
134 | NULL, NULL, width: &size, NULL); |
135 | |
136 | if (minimum != NULL) |
137 | *minimum = size; |
138 | if (natural != NULL) |
139 | *natural = size; |
140 | } |
141 | |
142 | static void |
143 | gtk_cell_renderer_spinner_get_preferred_height (GtkCellRenderer *cell, |
144 | GtkWidget *widget, |
145 | int *minimum, |
146 | int *natural) |
147 | { |
148 | int size = 0; |
149 | |
150 | gtk_cell_renderer_spinner_get_size (GTK_CELL_RENDERER_SPINNER (cell), widget, |
151 | NULL, |
152 | NULL, NULL, NULL, height: &size); |
153 | |
154 | if (minimum != NULL) |
155 | *minimum = size; |
156 | if (natural != NULL) |
157 | *natural = size; |
158 | } |
159 | |
160 | static void |
161 | gtk_cell_renderer_spinner_class_init (GtkCellRendererSpinnerClass *klass) |
162 | { |
163 | GObjectClass *object_class = G_OBJECT_CLASS (klass); |
164 | GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (klass); |
165 | |
166 | object_class->get_property = gtk_cell_renderer_spinner_get_property; |
167 | object_class->set_property = gtk_cell_renderer_spinner_set_property; |
168 | |
169 | cell_class->get_request_mode = gtk_cell_renderer_spinner_get_request_mode; |
170 | cell_class->get_preferred_width = gtk_cell_renderer_spinner_get_preferred_width; |
171 | cell_class->get_preferred_height = gtk_cell_renderer_spinner_get_preferred_height; |
172 | cell_class->snapshot = gtk_cell_renderer_spinner_snapshot; |
173 | |
174 | /* GtkCellRendererSpinner:active: |
175 | * |
176 | * Whether the spinner is active (ie. shown) in the cell |
177 | */ |
178 | g_object_class_install_property (oclass: object_class, |
179 | property_id: PROP_ACTIVE, |
180 | pspec: g_param_spec_boolean (name: "active" , |
181 | P_("Active" ), |
182 | P_("Whether the spinner is active (ie. shown) in the cell" ), |
183 | FALSE, |
184 | flags: G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); |
185 | |
186 | /** |
187 | * GtkCellRendererSpinner:pulse: |
188 | * |
189 | * Pulse of the spinner. Increment this value to draw the next frame of the |
190 | * spinner animation. Usually, you would update this value in a timeout. |
191 | * |
192 | * By default, the `GtkSpinner` widget draws one full cycle of the animation, |
193 | * consisting of 12 frames, in 750 milliseconds. |
194 | */ |
195 | g_object_class_install_property (oclass: object_class, |
196 | property_id: PROP_PULSE, |
197 | pspec: g_param_spec_uint (name: "pulse" , |
198 | P_("Pulse" ), |
199 | P_("Pulse of the spinner" ), |
200 | minimum: 0, G_MAXUINT, default_value: 0, |
201 | flags: G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); |
202 | |
203 | /** |
204 | * GtkCellRendererSpinner:size: |
205 | * |
206 | * The `GtkIconSize` value that specifies the size of the rendered spinner. |
207 | */ |
208 | g_object_class_install_property (oclass: object_class, |
209 | property_id: PROP_SIZE, |
210 | pspec: g_param_spec_enum (name: "size" , |
211 | P_("Size" ), |
212 | P_("The GtkIconSize value that specifies the size of the rendered spinner" ), |
213 | enum_type: GTK_TYPE_ICON_SIZE, default_value: GTK_ICON_SIZE_INHERIT, |
214 | flags: G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY)); |
215 | |
216 | } |
217 | |
218 | static void |
219 | gtk_cell_renderer_spinner_init (GtkCellRendererSpinner *cell) |
220 | { |
221 | GtkCellRendererSpinnerPrivate *priv = gtk_cell_renderer_spinner_get_instance_private (self: cell); |
222 | priv->pulse = 0; |
223 | priv->icon_size = GTK_ICON_SIZE_INHERIT; |
224 | } |
225 | |
226 | /** |
227 | * gtk_cell_renderer_spinner_new: |
228 | * |
229 | * Returns a new cell renderer which will show a spinner to indicate |
230 | * activity. |
231 | * |
232 | * Returns: a new `GtkCellRenderer` |
233 | */ |
234 | GtkCellRenderer * |
235 | gtk_cell_renderer_spinner_new (void) |
236 | { |
237 | return g_object_new (GTK_TYPE_CELL_RENDERER_SPINNER, NULL); |
238 | } |
239 | |
240 | static void |
241 | gtk_cell_renderer_spinner_update_size (GtkCellRendererSpinner *cell, |
242 | GtkWidget *widget) |
243 | { |
244 | GtkCellRendererSpinnerPrivate *priv = gtk_cell_renderer_spinner_get_instance_private (self: cell); |
245 | GtkStyleContext *context; |
246 | GtkCssNode *node; |
247 | GtkCssStyle *style; |
248 | |
249 | context = gtk_widget_get_style_context (widget); |
250 | gtk_style_context_save (context); |
251 | |
252 | gtk_style_context_add_class (context, class_name: "spinner" ); |
253 | node = gtk_style_context_get_node (context); |
254 | gtk_icon_size_set_style_classes (cssnode: node, icon_size: priv->icon_size); |
255 | style = gtk_css_node_get_style (cssnode: node); |
256 | priv->size = _gtk_css_number_value_get (number: style->icon->icon_size, one_hundred_percent: 100); |
257 | |
258 | gtk_style_context_restore (context); |
259 | } |
260 | |
261 | static void |
262 | gtk_cell_renderer_spinner_get_property (GObject *object, |
263 | guint param_id, |
264 | GValue *value, |
265 | GParamSpec *pspec) |
266 | { |
267 | GtkCellRendererSpinner *cell = GTK_CELL_RENDERER_SPINNER (object); |
268 | GtkCellRendererSpinnerPrivate *priv = gtk_cell_renderer_spinner_get_instance_private (self: cell); |
269 | |
270 | switch (param_id) |
271 | { |
272 | case PROP_ACTIVE: |
273 | g_value_set_boolean (value, v_boolean: priv->active); |
274 | break; |
275 | case PROP_PULSE: |
276 | g_value_set_uint (value, v_uint: priv->pulse); |
277 | break; |
278 | case PROP_SIZE: |
279 | g_value_set_enum (value, v_enum: priv->icon_size); |
280 | break; |
281 | default: |
282 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); |
283 | } |
284 | } |
285 | |
286 | static void |
287 | gtk_cell_renderer_spinner_set_property (GObject *object, |
288 | guint param_id, |
289 | const GValue *value, |
290 | GParamSpec *pspec) |
291 | { |
292 | GtkCellRendererSpinner *cell = GTK_CELL_RENDERER_SPINNER (object); |
293 | GtkCellRendererSpinnerPrivate *priv = gtk_cell_renderer_spinner_get_instance_private (self: cell); |
294 | |
295 | switch (param_id) |
296 | { |
297 | case PROP_ACTIVE: |
298 | if (priv->active != g_value_get_boolean (value)) |
299 | { |
300 | priv->active = g_value_get_boolean (value); |
301 | g_object_notify (object, property_name: "active" ); |
302 | } |
303 | break; |
304 | case PROP_PULSE: |
305 | if (priv->pulse != g_value_get_uint (value)) |
306 | { |
307 | priv->pulse = g_value_get_uint (value); |
308 | g_object_notify (object, property_name: "pulse" ); |
309 | } |
310 | break; |
311 | case PROP_SIZE: |
312 | if (priv->icon_size != g_value_get_enum (value)) |
313 | { |
314 | priv->icon_size = g_value_get_enum (value); |
315 | g_object_notify (object, property_name: "size" ); |
316 | } |
317 | break; |
318 | default: |
319 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); |
320 | } |
321 | } |
322 | |
323 | static void |
324 | gtk_cell_renderer_spinner_get_size (GtkCellRendererSpinner *self, |
325 | GtkWidget *widget, |
326 | const GdkRectangle *cell_area, |
327 | int *x_offset, |
328 | int *y_offset, |
329 | int *width, |
330 | int *height) |
331 | { |
332 | GtkCellRendererSpinnerPrivate *priv = gtk_cell_renderer_spinner_get_instance_private (self); |
333 | double align; |
334 | int w, h; |
335 | int xpad, ypad; |
336 | float xalign, yalign; |
337 | gboolean rtl; |
338 | |
339 | rtl = gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL; |
340 | |
341 | gtk_cell_renderer_spinner_update_size (cell: self, widget); |
342 | |
343 | g_object_get (object: self, |
344 | first_property_name: "xpad" , &xpad, |
345 | "ypad" , &ypad, |
346 | "xalign" , &xalign, |
347 | "yalign" , &yalign, |
348 | NULL); |
349 | |
350 | w = h = priv->size; |
351 | |
352 | if (cell_area) |
353 | { |
354 | if (x_offset) |
355 | { |
356 | align = rtl ? 1.0 - xalign : xalign; |
357 | *x_offset = align * (cell_area->width - w); |
358 | *x_offset = MAX (*x_offset, 0); |
359 | } |
360 | if (y_offset) |
361 | { |
362 | align = rtl ? 1.0 - yalign : yalign; |
363 | *y_offset = align * (cell_area->height - h); |
364 | *y_offset = MAX (*y_offset, 0); |
365 | } |
366 | } |
367 | else |
368 | { |
369 | if (x_offset) |
370 | *x_offset = 0; |
371 | if (y_offset) |
372 | *y_offset = 0; |
373 | } |
374 | |
375 | if (width) |
376 | *width = w; |
377 | if (height) |
378 | *height = h; |
379 | } |
380 | |
381 | static void |
382 | gtk_paint_spinner (GtkStyleContext *context, |
383 | cairo_t *cr, |
384 | guint step, |
385 | int x, |
386 | int y, |
387 | int width, |
388 | int height) |
389 | { |
390 | GdkRGBA color; |
391 | guint num_steps; |
392 | double dx, dy; |
393 | double radius; |
394 | double half; |
395 | int i; |
396 | guint real_step; |
397 | |
398 | num_steps = 12; |
399 | real_step = step % num_steps; |
400 | |
401 | /* set a clip region for the expose event */ |
402 | cairo_rectangle (cr, x, y, width, height); |
403 | cairo_clip (cr); |
404 | |
405 | cairo_translate (cr, tx: x, ty: y); |
406 | |
407 | /* draw clip region */ |
408 | cairo_set_operator (cr, op: CAIRO_OPERATOR_OVER); |
409 | |
410 | gtk_style_context_get_color (context, color: &color); |
411 | dx = width / 2; |
412 | dy = height / 2; |
413 | radius = MIN (width / 2, height / 2); |
414 | half = num_steps / 2; |
415 | |
416 | for (i = 0; i < num_steps; i++) |
417 | { |
418 | int inset = 0.7 * radius; |
419 | |
420 | /* transparency is a function of time and initial value */ |
421 | double t = (double) ((i + num_steps - real_step) |
422 | % num_steps) / num_steps; |
423 | |
424 | cairo_save (cr); |
425 | |
426 | cairo_set_source_rgba (cr, |
427 | red: color.red / 65535., |
428 | green: color.green / 65535., |
429 | blue: color.blue / 65535., |
430 | alpha: color.alpha * t); |
431 | |
432 | cairo_set_line_width (cr, width: 2.0); |
433 | cairo_move_to (cr, |
434 | x: dx + (radius - inset) * cos (x: i * G_PI / half), |
435 | y: dy + (radius - inset) * sin (x: i * G_PI / half)); |
436 | cairo_line_to (cr, |
437 | x: dx + radius * cos (x: i * G_PI / half), |
438 | y: dy + radius * sin (x: i * G_PI / half)); |
439 | cairo_stroke (cr); |
440 | |
441 | cairo_restore (cr); |
442 | } |
443 | } |
444 | |
445 | static void |
446 | gtk_cell_renderer_spinner_snapshot (GtkCellRenderer *cell, |
447 | GtkSnapshot *snapshot, |
448 | GtkWidget *widget, |
449 | const GdkRectangle *background_area, |
450 | const GdkRectangle *cell_area, |
451 | GtkCellRendererState flags) |
452 | { |
453 | GtkCellRendererSpinner *self = GTK_CELL_RENDERER_SPINNER (cell); |
454 | GtkCellRendererSpinnerPrivate *priv = gtk_cell_renderer_spinner_get_instance_private (self); |
455 | GdkRectangle pix_rect; |
456 | GdkRectangle draw_rect; |
457 | int xpad, ypad; |
458 | cairo_t *cr; |
459 | |
460 | if (!priv->active) |
461 | return; |
462 | |
463 | gtk_cell_renderer_spinner_get_size (self, widget, cell_area, |
464 | x_offset: &pix_rect.x, |
465 | y_offset: &pix_rect.y, |
466 | width: &pix_rect.width, |
467 | height: &pix_rect.height); |
468 | |
469 | g_object_get (object: self, |
470 | first_property_name: "xpad" , &xpad, |
471 | "ypad" , &ypad, |
472 | NULL); |
473 | |
474 | pix_rect.x += cell_area->x + xpad; |
475 | pix_rect.y += cell_area->y + ypad; |
476 | pix_rect.width -= xpad * 2; |
477 | pix_rect.height -= ypad * 2; |
478 | |
479 | if (!gdk_rectangle_intersect (src1: cell_area, src2: &pix_rect, dest: &draw_rect)) |
480 | return; |
481 | |
482 | cr = gtk_snapshot_append_cairo (snapshot, |
483 | bounds: &GRAPHENE_RECT_INIT ( |
484 | cell_area->x, cell_area->y, |
485 | cell_area->width, cell_area->height |
486 | )); |
487 | |
488 | gtk_paint_spinner (context: gtk_widget_get_style_context (widget), |
489 | cr, |
490 | step: priv->pulse, |
491 | x: draw_rect.x, y: draw_rect.y, |
492 | width: draw_rect.width, height: draw_rect.height); |
493 | |
494 | cairo_destroy (cr); |
495 | } |
496 | |