gtkspinner.c 18.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
/* GTK - The GIMP Toolkit
 *
 * Copyright (C) 2007 John Stowers, Neil Jagdish Patel.
 * Copyright (C) 2009 Bastien Nocera, David Zeuthen
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA  02111-1307, USA.
 *
 * Code adapted from egg-spinner
 * by Christian Hergert <christian.hergert@gmail.com>
 */

/*
 * Modified by the GTK+ Team and others 2007.  See the AUTHORS
 * file for a list of people on the GTK+ Team.  See the ChangeLog
 * files for a list of changes.  These files are distributed with
 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
 */

#include "config.h"

#include "gtkintl.h"
#include "gtkaccessible.h"
#include "gtkimage.h"
37
#include "gtksizerequest.h"
38 39 40
#include "gtkspinner.h"
#include "gtkstyle.h"

41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56

/**
 * SECTION:gtkspinner
 * @Short_description: Show a spinner animation
 * @Title: GtkSpinner
 * @See_also: #GtkCellRendererSpinner, #GtkProgressBar
 *
 * A GtkSpinner widget displays an icon-size spinning animation.
 * It is often used as an alternative to a #GtkProgressBar for
 * displaying indefinite activity, instead of actual progress.
 *
 * To start the animation, use gtk_spinner_start(), to stop it
 * use gtk_spinner_stop().
 */


57 58
#define SPINNER_SIZE 12

59 60 61 62 63 64 65 66 67
enum {
  PROP_0,
  PROP_ACTIVE
};

struct _GtkSpinnerPrivate
{
  guint current;
  guint num_steps;
68
  guint cycle_duration;
69
  gboolean active;
70 71 72
  guint timeout;
};

73
static void gtk_spinner_dispose        (GObject         *gobject);
74 75
static void gtk_spinner_realize        (GtkWidget       *widget);
static void gtk_spinner_unrealize      (GtkWidget       *widget);
76 77 78 79 80 81 82 83 84 85 86 87 88 89
static gboolean gtk_spinner_expose     (GtkWidget       *widget,
                                        GdkEventExpose  *event);
static void gtk_spinner_screen_changed (GtkWidget       *widget,
                                        GdkScreen       *old_screen);
static void gtk_spinner_style_set      (GtkWidget       *widget,
                                        GtkStyle        *prev_style);
static void gtk_spinner_get_property   (GObject         *object,
                                        guint            param_id,
                                        GValue          *value,
                                        GParamSpec      *pspec);
static void gtk_spinner_set_property   (GObject         *object,
                                        guint            param_id,
                                        const GValue    *value,
                                        GParamSpec      *pspec);
90 91
static void gtk_spinner_set_active     (GtkSpinner      *spinner,
                                        gboolean         active);
92 93
static AtkObject *gtk_spinner_get_accessible      (GtkWidget *widget);
static GType      gtk_spinner_accessible_get_type (void);
94
static void gtk_spinner_size_request_init (GtkSizeRequestIface *iface);
95

96
G_DEFINE_TYPE_WITH_CODE (GtkSpinner, gtk_spinner, GTK_TYPE_WIDGET,
97 98
			 G_IMPLEMENT_INTERFACE (GTK_TYPE_SIZE_REQUEST,
						gtk_spinner_size_request_init))
99

100 101 102 103 104 105 106 107 108 109 110 111 112 113
static void
gtk_spinner_class_init (GtkSpinnerClass *klass)
{
  GObjectClass *gobject_class;
  GtkWidgetClass *widget_class;

  gobject_class = G_OBJECT_CLASS(klass);
  g_type_class_add_private (gobject_class, sizeof (GtkSpinnerPrivate));
  gobject_class->dispose = gtk_spinner_dispose;
  gobject_class->get_property = gtk_spinner_get_property;
  gobject_class->set_property = gtk_spinner_set_property;

  widget_class = GTK_WIDGET_CLASS(klass);
  widget_class->expose_event = gtk_spinner_expose;
114 115
  widget_class->realize = gtk_spinner_realize;
  widget_class->unrealize = gtk_spinner_unrealize;
116 117 118 119
  widget_class->screen_changed = gtk_spinner_screen_changed;
  widget_class->style_set = gtk_spinner_style_set;
  widget_class->get_accessible = gtk_spinner_get_accessible;

120
  /* GtkSpinner:active:
121 122 123
   *
   * Whether the spinner is active
   *
124
   * Since: 2.20
125 126 127 128 129 130 131 132 133
   */
  g_object_class_install_property (gobject_class,
                                   PROP_ACTIVE,
                                   g_param_spec_boolean ("active",
                                                         P_("Active"),
                                                         P_("Whether the spinner is active"),
                                                         FALSE,
                                                         G_PARAM_READWRITE));
  /**
134
   * GtkSpinner:num-steps:
135
   *
136
   * The number of steps for the spinner to complete a full loop.
137 138
   * The animation will complete a full cycle in one second by default
   * (see the #GtkSpinner:cycle-duration style property).
139 140 141 142 143 144
   *
   * Since: 2.20
   */
  gtk_widget_class_install_style_property (widget_class,
                                           g_param_spec_uint ("num-steps",
                                                             P_("Number of steps"),
145
                                                             P_("The number of steps for the spinner to complete a full loop. The animation will complete a full cycle in one second by default (see #GtkSpinner:cycle-duration)."),
146 147 148 149
                                                             1,
                                                             G_MAXUINT,
                                                             12,
                                                             G_PARAM_READABLE));
150 151

  /**
152
   * GtkSpinner:cycle-duration:
153 154 155 156 157 158 159 160 161 162 163 164 165
   *
   * The duration in milliseconds for the spinner to complete a full cycle.
   *
   * Since: 2.20
   */
  gtk_widget_class_install_style_property (widget_class,
                                           g_param_spec_uint ("cycle-duration",
                                                             P_("Animation duration"),
                                                             P_("The length of time in milliseconds for the spinner to complete a full loop"),
                                                             500,
                                                             G_MAXUINT,
                                                             1000,
                                                             G_PARAM_READABLE));
166 167 168 169 170 171 172 173 174 175
}

static void
gtk_spinner_get_property (GObject    *object,
                          guint       param_id,
                          GValue     *value,
                          GParamSpec *pspec)
{
  GtkSpinnerPrivate *priv;

176
  priv = GTK_SPINNER (object)->priv;
177 178 179 180

  switch (param_id)
    {
      case PROP_ACTIVE:
181
        g_value_set_boolean (value, priv->active);
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
        break;
      default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
    }
}

static void
gtk_spinner_set_property (GObject      *object,
                          guint         param_id,
                          const GValue *value,
                          GParamSpec   *pspec)
{
  switch (param_id)
    {
      case PROP_ACTIVE:
197
        gtk_spinner_set_active (GTK_SPINNER (object), g_value_get_boolean (value));
198 199 200 201 202 203 204 205 206 207 208
        break;
      default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
    }
}

static void
gtk_spinner_init (GtkSpinner *spinner)
{
  GtkSpinnerPrivate *priv;

209 210 211
  priv = G_TYPE_INSTANCE_GET_PRIVATE (spinner,
                                      GTK_TYPE_SPINNER,
                                      GtkSpinnerPrivate);
212 213 214
  priv->current = 0;
  priv->timeout = 0;

215 216
  spinner->priv = priv;

217
  gtk_widget_set_has_window (GTK_WIDGET (spinner), FALSE);
218 219
}

220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
static void
gtk_spinner_get_width (GtkSizeRequest *widget,
                       gint           *minimum_size,
                       gint           *natural_size)
{
  if (minimum_size)
    *minimum_size = SPINNER_SIZE;

  if (natural_size)
    *natural_size = SPINNER_SIZE;
}

static void
gtk_spinner_get_height (GtkSizeRequest *widget,
                        gint           *minimum_size,
                        gint           *natural_size)
{
  if (minimum_size)
    *minimum_size = SPINNER_SIZE;

  if (natural_size)
    *natural_size = SPINNER_SIZE;
}

static void
gtk_spinner_size_request_init (GtkSizeRequestIface *iface)
{
  iface->get_width  = gtk_spinner_get_width;
  iface->get_height = gtk_spinner_get_height;
}


252
static gboolean
253 254
gtk_spinner_expose (GtkWidget      *widget,
                    GdkEventExpose *event)
255
{
256
  GtkAllocation allocation;
257 258 259 260
  GtkStateType state_type;
  GtkSpinnerPrivate *priv;
  int width, height;

261
  priv = GTK_SPINNER (widget)->priv;
262

263 264 265
  gtk_widget_get_allocation (widget, &allocation);
  width = allocation.width;
  height = allocation.height;
266 267

  state_type = GTK_STATE_NORMAL;
268
  if (!gtk_widget_is_sensitive (widget))
269 270
   state_type = GTK_STATE_INSENSITIVE;

271 272
  gtk_paint_spinner (gtk_widget_get_style (widget),
                     gtk_widget_get_window (widget),
273
                     state_type,
274 275 276
                     &event->area,
                     widget,
                     "spinner",
277 278 279 280 281 282 283
                     priv->current,
                     event->area.x, event->area.y,
                     event->area.width, event->area.height);

  return FALSE;
}

284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
static gboolean
gtk_spinner_timeout (gpointer data)
{
  GtkSpinnerPrivate *priv;

  priv = GTK_SPINNER (data)->priv;

  if (priv->current + 1 >= priv->num_steps)
    priv->current = 0;
  else
    priv->current++;

  gtk_widget_queue_draw (GTK_WIDGET (data));

  return TRUE;
}

static void
gtk_spinner_add_timeout (GtkSpinner *spinner)
{
  GtkSpinnerPrivate *priv;

  priv = spinner->priv;

  priv->timeout = gdk_threads_add_timeout ((guint) priv->cycle_duration / priv->num_steps, gtk_spinner_timeout, spinner);
}

static void
gtk_spinner_remove_timeout (GtkSpinner *spinner)
{
  GtkSpinnerPrivate *priv;

  priv = spinner->priv;

  g_source_remove (priv->timeout);
  priv->timeout = 0;
}

322 323 324 325 326
static void
gtk_spinner_realize (GtkWidget *widget)
{
  GtkSpinnerPrivate *priv;

327
  priv = GTK_SPINNER (widget)->priv;
328 329 330 331

  GTK_WIDGET_CLASS (gtk_spinner_parent_class)->realize (widget);

  if (priv->active)
332
    gtk_spinner_add_timeout (GTK_SPINNER (widget));
333 334 335 336 337 338 339
}

static void
gtk_spinner_unrealize (GtkWidget *widget)
{
  GtkSpinnerPrivate *priv;

340
  priv = GTK_SPINNER (widget)->priv;
341 342 343

  if (priv->timeout != 0)
    {
344
      gtk_spinner_remove_timeout (GTK_SPINNER (widget));
345 346 347 348 349
    }

  GTK_WIDGET_CLASS (gtk_spinner_parent_class)->unrealize (widget);
}

350 351 352 353 354 355 356
static void
gtk_spinner_screen_changed (GtkWidget* widget, GdkScreen* old_screen)
{
  GtkSpinner *spinner;
  GdkScreen* new_screen;
  GdkColormap* colormap;

357
  spinner = GTK_SPINNER (widget);
358 359 360 361 362 363

  new_screen = gtk_widget_get_screen (widget);
  colormap = gdk_screen_get_rgba_colormap (new_screen);

  if (!colormap)
    {
364
      colormap = gdk_screen_get_default_colormap (new_screen);
365 366 367 368 369 370 371 372 373 374 375
    }

  gtk_widget_set_colormap (widget, colormap);
}

static void
gtk_spinner_style_set (GtkWidget *widget,
                       GtkStyle  *prev_style)
{
  GtkSpinnerPrivate *priv;

376
  priv = GTK_SPINNER (widget)->priv;
377 378 379

  gtk_widget_style_get (GTK_WIDGET (widget),
                        "num-steps", &(priv->num_steps),
380
                        "cycle-duration", &(priv->cycle_duration),
381 382 383 384 385 386
                        NULL);

  if (priv->current > priv->num_steps)
    priv->current = 0;
}

387 388
static void
gtk_spinner_dispose (GObject *gobject)
389 390 391
{
  GtkSpinnerPrivate *priv;

392
  priv = GTK_SPINNER (gobject)->priv;
393

394 395 396 397
  if (priv->timeout != 0)
    {
      gtk_spinner_remove_timeout (GTK_SPINNER (gobject));
    }
398

399
  G_OBJECT_CLASS (gtk_spinner_parent_class)->dispose (gobject);
400
}
401

402
static void
403
gtk_spinner_set_active (GtkSpinner *spinner, gboolean active)
404 405 406
{
  GtkSpinnerPrivate *priv;

407
  active = active != FALSE;
408

409 410 411
  priv = GTK_SPINNER (spinner)->priv;

  if (priv->active != active)
412
    {
413 414 415
      priv->active = active;
      g_object_notify (G_OBJECT (spinner), "active");

416
      if (active && gtk_widget_get_realized (GTK_WIDGET (spinner)) && priv->timeout == 0)
417 418 419 420 421 422 423
        {
          gtk_spinner_add_timeout (spinner);
        }
      else if (!active && priv->timeout != 0)
        {
          gtk_spinner_remove_timeout (spinner);
        }
424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491
    }
}

static GType
gtk_spinner_accessible_factory_get_accessible_type (void)
{
  return gtk_spinner_accessible_get_type ();
}

static AtkObject *
gtk_spinner_accessible_new (GObject *obj)
{
  AtkObject *accessible;

  g_return_val_if_fail (GTK_IS_WIDGET (obj), NULL);

  accessible = g_object_new (gtk_spinner_accessible_get_type (), NULL);
  atk_object_initialize (accessible, obj);

  return accessible;
}

static AtkObject*
gtk_spinner_accessible_factory_create_accessible (GObject *obj)
{
  return gtk_spinner_accessible_new (obj);
}

static void
gtk_spinner_accessible_factory_class_init (AtkObjectFactoryClass *klass)
{
  klass->create_accessible = gtk_spinner_accessible_factory_create_accessible;
  klass->get_accessible_type = gtk_spinner_accessible_factory_get_accessible_type;
}

static GType
gtk_spinner_accessible_factory_get_type (void)
{
  static GType type = 0;

  if (!type)
    {
      const GTypeInfo tinfo =
      {
        sizeof (AtkObjectFactoryClass),
        NULL,           /* base_init */
        NULL,           /* base_finalize */
        (GClassInitFunc) gtk_spinner_accessible_factory_class_init,
        NULL,           /* class_finalize */
        NULL,           /* class_data */
        sizeof (AtkObjectFactory),
        0,             /* n_preallocs */
        NULL, NULL
      };

      type = g_type_register_static (ATK_TYPE_OBJECT_FACTORY,
                                    I_("GtkSpinnerAccessibleFactory"),
                                    &tinfo, 0);
    }
  return type;
}

static AtkObjectClass *a11y_parent_class = NULL;

static void
gtk_spinner_accessible_initialize (AtkObject *accessible,
                                   gpointer   widget)
{
492 493
  atk_object_set_name (accessible, C_("throbbing progress animation widget", "Spinner"));
  atk_object_set_description (accessible, _("Provides visual indication of progress"));
494 495 496 497 498 499 500 501 502 503 504 505 506 507

  a11y_parent_class->initialize (accessible, widget);
}

static void
gtk_spinner_accessible_class_init (AtkObjectClass *klass)
{
  a11y_parent_class = g_type_class_peek_parent (klass);

  klass->initialize = gtk_spinner_accessible_initialize;
}

static void
gtk_spinner_accessible_image_get_size (AtkImage *image,
508 509
                                       gint     *width,
                                       gint     *height)
510
{
511
  GtkAllocation allocation;
512 513
  GtkWidget *widget;

514
  widget = gtk_accessible_get_widget (GTK_ACCESSIBLE (image));
515 516 517 518 519 520
  if (!widget)
    {
      *width = *height = 0;
    }
  else
    {
521 522 523
      gtk_widget_get_allocation (widget, &allocation);
      *width = allocation.width;
      *height = allocation.height;
524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618
    }
}

static void
gtk_spinner_accessible_image_interface_init (AtkImageIface *iface)
{
  iface->get_image_size = gtk_spinner_accessible_image_get_size;
}

static GType
gtk_spinner_accessible_get_type (void)
{
  static GType type = 0;

  /* Action interface
     Name etc. ... */
  if (G_UNLIKELY (type == 0))
    {
      const GInterfaceInfo atk_image_info = {
              (GInterfaceInitFunc) gtk_spinner_accessible_image_interface_init,
              (GInterfaceFinalizeFunc) NULL,
              NULL
      };
      GType parent_atk_type;
      GTypeInfo tinfo = { 0 };
      GTypeQuery query;
      AtkObjectFactory *factory;

      if ((type = g_type_from_name ("GtkSpinnerAccessible")))
        return type;

      factory = atk_registry_get_factory (atk_get_default_registry (),
                                          GTK_TYPE_IMAGE);
      if (!factory)
        return G_TYPE_INVALID;

      parent_atk_type = atk_object_factory_get_accessible_type (factory);
      if (!parent_atk_type)
        return G_TYPE_INVALID;

      /*
       * Figure out the size of the class and instance
       * we are deriving from
       */
      g_type_query (parent_atk_type, &query);

      tinfo.class_init = (GClassInitFunc) gtk_spinner_accessible_class_init;
      tinfo.class_size    = query.class_size;
      tinfo.instance_size = query.instance_size;

      /* Register the type */
      type = g_type_register_static (parent_atk_type,
                                     "GtkSpinnerAccessible",
                                     &tinfo, 0);

      g_type_add_interface_static (type, ATK_TYPE_IMAGE,
                                   &atk_image_info);
    }

  return type;
}

static AtkObject *
gtk_spinner_get_accessible (GtkWidget *widget)
{
  static gboolean first_time = TRUE;

  if (first_time)
    {
      AtkObjectFactory *factory;
      AtkRegistry *registry;
      GType derived_type;
      GType derived_atk_type;

      /*
       * Figure out whether accessibility is enabled by looking at the
       * type of the accessible object which would be created for
       * the parent type of GtkSpinner.
       */
      derived_type = g_type_parent (GTK_TYPE_SPINNER);

      registry = atk_get_default_registry ();
      factory = atk_registry_get_factory (registry,
                                          derived_type);
      derived_atk_type = atk_object_factory_get_accessible_type (factory);
      if (g_type_is_a (derived_atk_type, GTK_TYPE_ACCESSIBLE))
        atk_registry_set_factory_type (registry,
                                       GTK_TYPE_SPINNER,
                                       gtk_spinner_accessible_factory_get_type ());
      first_time = FALSE;
    }
  return GTK_WIDGET_CLASS (gtk_spinner_parent_class)->get_accessible (widget);
}

/**
619
 * gtk_spinner_new:
620 621 622 623 624 625 626 627 628 629 630 631 632 633
 *
 * Returns a new spinner widget. Not yet started.
 *
 * Return value: a new #GtkSpinner
 *
 * Since: 2.20
 */
GtkWidget *
gtk_spinner_new (void)
{
  return g_object_new (GTK_TYPE_SPINNER, NULL);
}

/**
634 635
 * gtk_spinner_start:
 * @spinner: a #GtkSpinner
636
 *
637
 * Starts the animation of the spinner.
638 639 640 641 642 643 644 645
 *
 * Since: 2.20
 */
void
gtk_spinner_start (GtkSpinner *spinner)
{
  g_return_if_fail (GTK_IS_SPINNER (spinner));

646
  gtk_spinner_set_active (spinner, TRUE);
647 648 649
}

/**
650 651
 * gtk_spinner_stop:
 * @spinner: a #GtkSpinner
652
 *
653
 * Stops the animation of the spinner.
654 655 656 657 658 659 660 661
 *
 * Since: 2.20
 */
void
gtk_spinner_stop (GtkSpinner *spinner)
{
  g_return_if_fail (GTK_IS_SPINNER (spinner));

662
  gtk_spinner_set_active (spinner, FALSE);
663
}