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
59enum {
60 PROP_0,
61 PROP_ACTIVE,
62 PROP_PULSE,
63 PROP_SIZE
64};
65
66typedef struct _GtkCellRendererSpinnerClass GtkCellRendererSpinnerClass;
67typedef struct _GtkCellRendererSpinnerPrivate GtkCellRendererSpinnerPrivate;
68
69struct _GtkCellRendererSpinner
70{
71 GtkCellRenderer parent;
72};
73
74struct _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
85struct _GtkCellRendererSpinnerPrivate
86{
87 gboolean active;
88 guint pulse;
89 GtkIconSize icon_size;
90 int size;
91};
92
93
94static void gtk_cell_renderer_spinner_get_property (GObject *object,
95 guint param_id,
96 GValue *value,
97 GParamSpec *pspec);
98static void gtk_cell_renderer_spinner_set_property (GObject *object,
99 guint param_id,
100 const GValue *value,
101 GParamSpec *pspec);
102static 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);
109static 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
116G_DEFINE_TYPE_WITH_PRIVATE (GtkCellRendererSpinner, gtk_cell_renderer_spinner, GTK_TYPE_CELL_RENDERER)
117
118static GtkSizeRequestMode
119gtk_cell_renderer_spinner_get_request_mode (GtkCellRenderer *cell)
120{
121 return GTK_SIZE_REQUEST_CONSTANT_SIZE;
122}
123
124static void
125gtk_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
142static void
143gtk_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
160static void
161gtk_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
218static void
219gtk_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 */
234GtkCellRenderer *
235gtk_cell_renderer_spinner_new (void)
236{
237 return g_object_new (GTK_TYPE_CELL_RENDERER_SPINNER, NULL);
238}
239
240static void
241gtk_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
261static void
262gtk_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
286static void
287gtk_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
323static void
324gtk_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
381static void
382gtk_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
445static void
446gtk_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

source code of gtk/gtk/gtkcellrendererspinner.c