gtkswitch.c 38.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/* GTK - The GIMP Toolkit
 *
 * Copyright (C) 2010  Intel Corporation
 * Copyright (C) 2010  RedHat, Inc.
 *
 * 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
Javier Jardón's avatar
Javier Jardón committed
17
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
18 19 20 21 22
 *
 * Author:
 *      Emmanuele Bassi <ebassi@linux.intel.com>
 *      Matthias Clasen <mclasen@redhat.com>
 *
23
 * Based on similar code from Mx.
24 25 26 27
 */

/**
 * SECTION:gtkswitch
28
 * @Short_Description: A “light switch” style toggle
29 30 31 32 33 34
 * @Title: GtkSwitch
 * @See_Also: #GtkToggleButton
 *
 * #GtkSwitch is a widget that has two states: on or off. The user can control
 * which state should be active by clicking the empty area, or by dragging the
 * handle.
35 36 37
 *
 * GtkSwitch can also handle situations where the underlying state changes with
 * a delay. See #GtkSwitch::state-set for details.
38 39 40
 *
 * # CSS nodes
 *
41 42 43 44 45
 * |[<!-- language="plain" -->
 * switch
 * ╰── slider
 * ]|
 *
46 47
 * GtkSwitch has two css nodes, the main node with the name switch and a subnode
 * named slider. Neither of them is using any style classes.
48 49 50 51 52 53
 */

#include "config.h"

#include "gtkswitch.h"

54
#include "deprecated/gtkactivatable.h"
55
#include "deprecated/gtktoggleaction.h"
56 57
#include "gtkintl.h"
#include "gtkprivate.h"
58
#include "gtkwidget.h"
59
#include "gtkmarshalers.h"
60 61
#include "gtkapplicationprivate.h"
#include "gtkactionable.h"
62
#include "a11y/gtkswitchaccessible.h"
63
#include "gtkactionhelper.h"
Benjamin Otte's avatar
Benjamin Otte committed
64 65
#include "gtkcsscustomgadgetprivate.h"
#include "gtkcssgadgetprivate.h"
66
#include "gtkstylecontextprivate.h"
67
#include "gtkwidgetprivate.h"
68
#include "gtkcssshadowsvalueprivate.h"
69
#include "gtkcssnumbervalueprivate.h"
70
#include "gtkprogresstrackerprivate.h"
71
#include "gtksettingsprivate.h"
72

73
#include "fallback-c89.c"
74 75

#define DEFAULT_SLIDER_WIDTH    (36)
76
#define DEFAULT_SLIDER_HEIGHT   (22)
77 78 79 80 81

struct _GtkSwitchPrivate
{
  GdkWindow *event_window;
  GtkAction *action;
82
  GtkActionHelper *action_helper;
83

84 85 86
  GtkGesture *pan_gesture;
  GtkGesture *multipress_gesture;

Benjamin Otte's avatar
Benjamin Otte committed
87 88
  GtkCssGadget *gadget;
  GtkCssGadget *slider_gadget;
89 90
  PangoLayout  *off_layout;
  PangoLayout  *on_layout;
91

92
  double handle_pos;
93
  guint tick_id;
94
  GtkProgressTracker tracker;
95

96
  guint state                 : 1;
97 98 99 100 101 102 103 104 105
  guint is_active             : 1;
  guint in_switch             : 1;
  guint use_action_appearance : 1;
};

enum
{
  PROP_0,
  PROP_ACTIVE,
106
  PROP_STATE,
107 108
  PROP_RELATED_ACTION,
  PROP_USE_ACTION_APPEARANCE,
109 110 111
  LAST_PROP,
  PROP_ACTION_NAME,
  PROP_ACTION_TARGET
112 113
};

114 115 116
enum
{
  ACTIVATE,
117
  STATE_SET,
118 119 120 121 122
  LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

123 124
static GParamSpec *switch_props[LAST_PROP] = { NULL, };

125
static void gtk_switch_actionable_iface_init (GtkActionableInterface *iface);
126 127
static void gtk_switch_activatable_interface_init (GtkActivatableIface *iface);

128
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
129
G_DEFINE_TYPE_WITH_CODE (GtkSwitch, gtk_switch, GTK_TYPE_WIDGET,
130
                         G_ADD_PRIVATE (GtkSwitch)
131 132
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTIONABLE,
                                                gtk_switch_actionable_iface_init)
133 134
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_ACTIVATABLE,
                                                gtk_switch_activatable_interface_init));
135
G_GNUC_END_IGNORE_DEPRECATIONS;
136

137 138 139 140 141 142 143
static void
gtk_switch_end_toggle_animation (GtkSwitch *sw)
{
  GtkSwitchPrivate *priv = sw->priv;

  if (priv->tick_id != 0)
    {
144
      gtk_widget_remove_tick_callback (GTK_WIDGET (sw), priv->tick_id);
145 146 147 148
      priv->tick_id = 0;
    }
}

149 150 151 152
static gboolean
gtk_switch_on_frame_clock_update (GtkWidget     *widget,
                                  GdkFrameClock *clock,
                                  gpointer       user_data)
153
{
154
  GtkSwitch *sw = GTK_SWITCH (widget);
155 156
  GtkSwitchPrivate *priv = sw->priv;

157 158
  gtk_progress_tracker_advance_frame (&priv->tracker,
                                      gdk_frame_clock_get_frame_time (clock));
159

160
  if (gtk_progress_tracker_get_state (&priv->tracker) != GTK_PROGRESS_STATE_AFTER)
161
    {
162
      if (priv->is_active)
163
        priv->handle_pos = 1.0 - gtk_progress_tracker_get_ease_out_cubic (&priv->tracker, FALSE);
164
      else
165
        priv->handle_pos = gtk_progress_tracker_get_ease_out_cubic (&priv->tracker, FALSE);
166 167 168 169 170 171
    }
  else
    {
      gtk_switch_set_active (sw, !priv->is_active);
    }

Benjamin Otte's avatar
Benjamin Otte committed
172
  gtk_widget_queue_allocate (GTK_WIDGET (sw));
173 174

  return G_SOURCE_CONTINUE;
175 176 177 178 179 180 181 182 183
}

#define ANIMATION_DURATION 100

static void
gtk_switch_begin_toggle_animation (GtkSwitch *sw)
{
  GtkSwitchPrivate *priv = sw->priv;

184
  if (gtk_settings_get_enable_animations (gtk_widget_get_settings (GTK_WIDGET (sw))))
185
    {
186
      gtk_progress_tracker_start (&priv->tracker, 1000 * ANIMATION_DURATION, 0, 1.0);
187
      if (priv->tick_id == 0)
188 189 190
        priv->tick_id = gtk_widget_add_tick_callback (GTK_WIDGET (sw),
                                                      gtk_switch_on_frame_clock_update,
                                                      NULL, NULL);
191 192 193 194 195 196 197
    }
  else
    {
      gtk_switch_set_active (sw, !priv->is_active);
    }
}

198 199 200 201 202 203
static void
gtk_switch_multipress_gesture_pressed (GtkGestureMultiPress *gesture,
                                       gint                  n_press,
                                       gdouble               x,
                                       gdouble               y,
                                       GtkSwitch            *sw)
204
{
205
  GtkSwitchPrivate *priv = sw->priv;
206 207
  GtkAllocation allocation;

208 209
  gtk_widget_get_allocation (GTK_WIDGET (sw), &allocation);
  gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
210

211 212 213 214 215 216
  /* If the press didn't happen in the draggable handle,
   * cancel the pan gesture right away
   */
  if ((priv->is_active && x <= allocation.width / 2) ||
      (!priv->is_active && x > allocation.width / 2))
    gtk_gesture_set_state (priv->pan_gesture, GTK_EVENT_SEQUENCE_DENIED);
217 218
}

219 220 221 222 223 224
static void
gtk_switch_multipress_gesture_released (GtkGestureMultiPress *gesture,
                                        gint                  n_press,
                                        gdouble               x,
                                        gdouble               y,
                                        GtkSwitch            *sw)
225
{
226 227
  GtkSwitchPrivate *priv = sw->priv;
  GdkEventSequence *sequence;
228

229
  sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
230

231 232
  if (priv->in_switch &&
      gtk_gesture_handles_sequence (GTK_GESTURE (gesture), sequence))
233
    gtk_switch_begin_toggle_animation (sw);
234 235
}

236 237 238 239 240
static void
gtk_switch_pan_gesture_pan (GtkGesturePan   *gesture,
                            GtkPanDirection  direction,
                            gdouble          offset,
                            GtkSwitch       *sw)
241
{
242 243
  GtkWidget *widget = GTK_WIDGET (sw);
  GtkSwitchPrivate *priv = sw->priv;
244
  gint width;
245 246 247

  if (direction == GTK_PAN_DIRECTION_LEFT)
    offset = -offset;
248

249 250
  gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);

251
  width = gtk_widget_get_allocated_width (widget);
252

253
  if (priv->is_active)
254 255 256
    offset += width / 2;
  
  offset /= width / 2;
257
  /* constrain the handle within the trough width */
258
  priv->handle_pos = CLAMP (offset, 0, 1.0);
259

260
  /* we need to redraw the handle */
Benjamin Otte's avatar
Benjamin Otte committed
261
  gtk_widget_queue_allocate (widget);
262
}
263

264 265 266 267 268 269 270 271 272
static void
gtk_switch_pan_gesture_drag_end (GtkGestureDrag *gesture,
                                 gdouble         x,
                                 gdouble         y,
                                 GtkSwitch      *sw)
{
  GtkSwitchPrivate *priv = sw->priv;
  GdkEventSequence *sequence;
  GtkAllocation allocation;
273
  gboolean active;
274

275
  sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
276

277
  if (gtk_gesture_get_sequence_state (GTK_GESTURE (gesture), sequence) == GTK_EVENT_SEQUENCE_CLAIMED)
278
    {
279
      gtk_widget_get_allocation (GTK_WIDGET (sw), &allocation);
280 281 282 283

      /* if half the handle passed the middle of the switch, then we
       * consider it to be on
       */
284
      active = priv->handle_pos >= 0.5;
285
    }
286 287 288 289
  else if (!gtk_gesture_handles_sequence (priv->multipress_gesture, sequence))
    active = priv->is_active;
  else
    return;
290

291
  priv->handle_pos = active ? 1.0 : 0.0;
292
  gtk_switch_set_active (sw, active);
Benjamin Otte's avatar
Benjamin Otte committed
293
  gtk_widget_queue_allocate (GTK_WIDGET (sw));
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 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340
static void
gtk_switch_create_pango_layouts (GtkSwitch *self)
{
  GtkSwitchPrivate *priv = self->priv;

  g_clear_object (&priv->on_layout);
  /* Translators: if the "on" state label requires more than three
   * glyphs then use MEDIUM VERTICAL BAR (U+2759) as the text for
   * the state
   */
  priv->on_layout = gtk_widget_create_pango_layout (GTK_WIDGET (self), C_("switch", "ON"));


  g_clear_object (&priv->off_layout);
  /* Translators: if the "off" state label requires more than three
   * glyphs then use WHITE CIRCLE (U+25CB) as the text for the state
   */
  priv->off_layout = gtk_widget_create_pango_layout (GTK_WIDGET (self), C_("switch", "OFF"));
}

static void
gtk_switch_screen_changed (GtkWidget *widget,
                           GdkScreen *prev_screen)
{
  gtk_switch_create_pango_layouts (GTK_SWITCH (widget));
}

static void
gtk_switch_style_updated (GtkWidget *widget)
{
  GtkSwitch *self = GTK_SWITCH (widget);
  GtkStyleContext *context;
  GtkCssStyleChange *change;

  GTK_WIDGET_CLASS (gtk_switch_parent_class)->style_updated (widget);

  context = gtk_widget_get_style_context (widget);
  change = gtk_style_context_get_change (context);

  if (change == NULL || gtk_css_style_change_affects (change, GTK_CSS_AFFECTS_FONT))
    gtk_switch_create_pango_layouts (self);
}



341 342 343 344 345 346 347
static gboolean
gtk_switch_enter (GtkWidget        *widget,
                  GdkEventCrossing *event)
{
  GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;

  if (event->window == priv->event_window)
348 349
    {
      priv->in_switch = TRUE;
350
      gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_PRELIGHT, FALSE);
351
    }
352 353 354 355 356 357 358 359 360 361 362

  return FALSE;
}

static gboolean
gtk_switch_leave (GtkWidget        *widget,
                  GdkEventCrossing *event)
{
  GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;

  if (event->window == priv->event_window)
363 364
    {
      priv->in_switch = FALSE;
365
      gtk_widget_unset_state_flags (widget, GTK_STATE_FLAG_PRELIGHT);
366
    }
367 368 369 370

  return FALSE;
}

371 372
static void
gtk_switch_activate (GtkSwitch *sw)
373
{
374
  gtk_switch_begin_toggle_animation (sw);
375 376 377
}

static void
Benjamin Otte's avatar
Benjamin Otte committed
378 379 380 381 382 383 384 385 386 387
gtk_switch_get_slider_size (GtkCssGadget   *gadget,
                            GtkOrientation  orientation,
                            gint            for_size,
                            gint           *minimum,
                            gint           *natural,
                            gint           *minimum_baseline,
                            gint           *natural_baseline,
                            gpointer        unused)
{
  GtkWidget *widget = gtk_css_gadget_get_owner (gadget);
388
  gdouble min_size;
Benjamin Otte's avatar
Benjamin Otte committed
389

390
  if (orientation == GTK_ORIENTATION_HORIZONTAL)
Benjamin Otte's avatar
Benjamin Otte committed
391
    {
392 393 394 395 396 397
      min_size = _gtk_css_number_value_get (gtk_css_style_get_value (gtk_css_gadget_get_style (gadget), GTK_CSS_PROPERTY_MIN_WIDTH), 100);

      if (min_size > 0.0)
        *minimum = 0;
      else
        gtk_widget_style_get (widget, "slider-width", minimum, NULL);
Benjamin Otte's avatar
Benjamin Otte committed
398 399 400
    }
  else
    {
401 402 403 404 405 406
      min_size = _gtk_css_number_value_get (gtk_css_style_get_value (gtk_css_gadget_get_style (gadget), GTK_CSS_PROPERTY_MIN_HEIGHT), 100);

      if (min_size > 0.0)
        *minimum = 0;
      else
        gtk_widget_style_get (widget, "slider-height", minimum, NULL);
Benjamin Otte's avatar
Benjamin Otte committed
407 408
    }

409
  *natural = *minimum;
Benjamin Otte's avatar
Benjamin Otte committed
410 411 412 413 414 415 416 417 418 419 420
}

static void
gtk_switch_get_content_size (GtkCssGadget   *gadget,
                             GtkOrientation  orientation,
                             gint            for_size,
                             gint           *minimum,
                             gint           *natural,
                             gint           *minimum_baseline,
                             gint           *natural_baseline,
                             gpointer        unused)
421
{
Benjamin Otte's avatar
Benjamin Otte committed
422
  GtkWidget *widget;
423 424
  GtkSwitch *self;
  GtkSwitchPrivate *priv;
Benjamin Otte's avatar
Benjamin Otte committed
425 426
  gint slider_minimum, slider_natural;
  PangoRectangle on_rect, off_rect;
427

Benjamin Otte's avatar
Benjamin Otte committed
428
  widget = gtk_css_gadget_get_owner (gadget);
429 430
  self = GTK_SWITCH (widget);
  priv = self->priv;
431

Benjamin Otte's avatar
Benjamin Otte committed
432 433 434 435 436
  gtk_css_gadget_get_preferred_size (priv->slider_gadget,
                                     orientation,
                                     -1,
                                     &slider_minimum, &slider_natural,
                                     NULL, NULL);
437

438 439
  pango_layout_get_pixel_extents (priv->on_layout, NULL, &on_rect);
  pango_layout_get_pixel_extents (priv->off_layout, NULL, &off_rect);
440

Benjamin Otte's avatar
Benjamin Otte committed
441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464
  if (orientation == GTK_ORIENTATION_HORIZONTAL)
    {
      int text_width = MAX (on_rect.width, off_rect.width);
      *minimum = 2 * MAX (slider_minimum, text_width);
      *natural = 2 * MAX (slider_natural, text_width);
    }
  else
    {
      int text_height = MAX (on_rect.height, off_rect.height);
      *minimum = MAX (slider_minimum, text_height);
      *natural = MAX (slider_natural, text_height);
    }
}

static void
gtk_switch_get_preferred_width (GtkWidget *widget,
                                gint      *minimum,
                                gint      *natural)
{
  gtk_css_gadget_get_preferred_size (GTK_SWITCH (widget)->priv->gadget,
                                     GTK_ORIENTATION_HORIZONTAL,
                                     -1,
                                     minimum, natural,
                                     NULL, NULL);
465 466 467 468 469 470 471
}

static void
gtk_switch_get_preferred_height (GtkWidget *widget,
                                 gint      *minimum,
                                 gint      *natural)
{
Benjamin Otte's avatar
Benjamin Otte committed
472 473 474 475 476 477
  gtk_css_gadget_get_preferred_size (GTK_SWITCH (widget)->priv->gadget,
                                     GTK_ORIENTATION_VERTICAL,
                                     -1,
                                     minimum, natural,
                                     NULL, NULL);
}
478

Benjamin Otte's avatar
Benjamin Otte committed
479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498
static void
gtk_switch_allocate_contents (GtkCssGadget        *gadget,
                              const GtkAllocation *allocation,
                              int                  baseline,
                              GtkAllocation       *out_clip,
                              gpointer             unused)
{
  GtkSwitch *self = GTK_SWITCH (gtk_css_gadget_get_owner (gadget));
  GtkSwitchPrivate *priv = self->priv;
  GtkAllocation slider_alloc;
  
  slider_alloc.x = allocation->x + round (priv->handle_pos * (allocation->width - allocation->width / 2));
  slider_alloc.y = allocation->y;
  slider_alloc.width = allocation->width / 2;
  slider_alloc.height = allocation->height;

  gtk_css_gadget_allocate (priv->slider_gadget,
                           &slider_alloc,
                           baseline,
                           out_clip);
499 500 501 502 503 504 505 506 507 508 509

  if (gtk_widget_get_realized (GTK_WIDGET (self)))
    {
      GtkAllocation border_allocation;
      gtk_css_gadget_get_border_allocation (gadget, &border_allocation, NULL);
      gdk_window_move_resize (priv->event_window,
                              border_allocation.x,
                              border_allocation.y,
                              border_allocation.width,
                              border_allocation.height);
    }
510 511 512 513 514 515 516
}

static void
gtk_switch_size_allocate (GtkWidget     *widget,
                          GtkAllocation *allocation)
{
  GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
517
  GtkAllocation clip;
518 519

  gtk_widget_set_allocation (widget, allocation);
Benjamin Otte's avatar
Benjamin Otte committed
520 521 522 523
  gtk_css_gadget_allocate (priv->gadget,
                           allocation,
                           gtk_widget_get_allocated_baseline (widget),
                           &clip);
524

Benjamin Otte's avatar
Benjamin Otte committed
525
  gtk_widget_set_clip (widget, &clip);
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
}

static void
gtk_switch_realize (GtkWidget *widget)
{
  GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
  GdkWindow *parent_window;
  GdkWindowAttr attributes;
  gint attributes_mask;
  GtkAllocation allocation;

  gtk_widget_set_realized (widget, TRUE);
  parent_window = gtk_widget_get_parent_window (widget);
  gtk_widget_set_window (widget, parent_window);
  g_object_ref (parent_window);

  gtk_widget_get_allocation (widget, &allocation);

  attributes.window_type = GDK_WINDOW_CHILD;
  attributes.wclass = GDK_INPUT_ONLY;
  attributes.x = allocation.x;
  attributes.y = allocation.y;
  attributes.width = allocation.width;
  attributes.height = allocation.height;
  attributes.event_mask = gtk_widget_get_events (widget);
  attributes.event_mask |= (GDK_BUTTON_PRESS_MASK |
                            GDK_BUTTON_RELEASE_MASK |
                            GDK_BUTTON1_MOTION_MASK |
                            GDK_POINTER_MOTION_MASK |
                            GDK_ENTER_NOTIFY_MASK |
                            GDK_LEAVE_NOTIFY_MASK);
  attributes_mask = GDK_WA_X | GDK_WA_Y;

  priv->event_window = gdk_window_new (parent_window,
                                       &attributes,
                                       attributes_mask);
562
  gtk_widget_register_window (widget, priv->event_window);
563 564 565 566 567 568 569 570 571
}

static void
gtk_switch_unrealize (GtkWidget *widget)
{
  GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;

  if (priv->event_window != NULL)
    {
572
      gtk_widget_unregister_window (widget, priv->event_window);
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
      gdk_window_destroy (priv->event_window);
      priv->event_window = NULL;
    }

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

static void
gtk_switch_map (GtkWidget *widget)
{
  GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;

  GTK_WIDGET_CLASS (gtk_switch_parent_class)->map (widget);

  if (priv->event_window)
    gdk_window_show (priv->event_window);
}

static void
gtk_switch_unmap (GtkWidget *widget)
{
  GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;

  if (priv->event_window)
    gdk_window_hide (priv->event_window);

  GTK_WIDGET_CLASS (gtk_switch_parent_class)->unmap (widget);
}

Benjamin Otte's avatar
Benjamin Otte committed
602 603 604 605 606 607 608 609
static gboolean
gtk_switch_render_slider (GtkCssGadget *gadget,
                          cairo_t      *cr,
                          int           x,
                          int           y,
                          int           width,
                          int           height,
                          gpointer      data)
610
{
Benjamin Otte's avatar
Benjamin Otte committed
611
  return gtk_widget_has_visible_focus (gtk_css_gadget_get_owner (gadget));
612 613 614
}

static gboolean
Benjamin Otte's avatar
Benjamin Otte committed
615 616 617 618 619 620 621
gtk_switch_render_trough (GtkCssGadget *gadget,
                          cairo_t      *cr,
                          int           x,
                          int           y,
                          int           width,
                          int           height,
                          gpointer      data)
622
{
Benjamin Otte's avatar
Benjamin Otte committed
623
  GtkWidget *widget = gtk_css_gadget_get_owner (gadget);
624
  GtkSwitchPrivate *priv = GTK_SWITCH (widget)->priv;
Benjamin Otte's avatar
Benjamin Otte committed
625
  GtkStyleContext *context = gtk_widget_get_style_context (widget);
626 627 628
  PangoRectangle rect;
  gint label_x, label_y;

629
  pango_layout_get_pixel_extents (priv->on_layout, NULL, &rect);
630

631 632
  label_x = x + ((width / 2) - rect.width) / 2;
  label_y = y + (height - rect.height) / 2;
633

634
  gtk_render_layout (context, cr, label_x, label_y, priv->on_layout);
635

636
  pango_layout_get_pixel_extents (priv->off_layout, NULL, &rect);
637

638 639
  label_x = x + (width / 2) + ((width / 2) - rect.width) / 2;
  label_y = y + (height - rect.height) / 2;
640

641
  gtk_render_layout (context, cr, label_x, label_y, priv->off_layout);
642

Benjamin Otte's avatar
Benjamin Otte committed
643
  gtk_css_gadget_draw (priv->slider_gadget, cr);
644

Benjamin Otte's avatar
Benjamin Otte committed
645 646
  return FALSE;
}
647

Benjamin Otte's avatar
Benjamin Otte committed
648 649 650 651 652
static gboolean
gtk_switch_draw (GtkWidget *widget,
                 cairo_t   *cr)
{
  gtk_css_gadget_draw (GTK_SWITCH (widget)->priv->gadget, cr);
653

654 655 656 657 658 659 660 661 662 663 664 665
  return FALSE;
}

static void
gtk_switch_set_related_action (GtkSwitch *sw,
                               GtkAction *action)
{
  GtkSwitchPrivate *priv = sw->priv;

  if (priv->action == action)
    return;

666
  G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
667
  gtk_activatable_do_set_related_action (GTK_ACTIVATABLE (sw), action);
668
  G_GNUC_END_IGNORE_DEPRECATIONS;
669 670 671 672 673 674 675 676 677 678 679 680 681 682

  priv->action = action;
}

static void
gtk_switch_set_use_action_appearance (GtkSwitch *sw,
                                      gboolean   use_appearance)
{
  GtkSwitchPrivate *priv = sw->priv;

  if (priv->use_action_appearance != use_appearance)
    {
      priv->use_action_appearance = use_appearance;

683
      G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
684
      gtk_activatable_sync_action_properties (GTK_ACTIVATABLE (sw), priv->action);
685
      G_GNUC_END_IGNORE_DEPRECATIONS;
686 687 688
    }
}

689 690 691 692 693 694
static void
gtk_switch_set_action_name (GtkActionable *actionable,
                            const gchar   *action_name)
{
  GtkSwitch *sw = GTK_SWITCH (actionable);

695 696
  if (!sw->priv->action_helper)
    sw->priv->action_helper = gtk_action_helper_new (actionable);
697

698
  gtk_action_helper_set_action_name (sw->priv->action_helper, action_name);
699 700 701 702 703 704 705 706
}

static void
gtk_switch_set_action_target_value (GtkActionable *actionable,
                                    GVariant      *action_target)
{
  GtkSwitch *sw = GTK_SWITCH (actionable);

707 708
  if (!sw->priv->action_helper)
    sw->priv->action_helper = gtk_action_helper_new (actionable);
709

710
  gtk_action_helper_set_action_target_value (sw->priv->action_helper, action_target);
711 712 713 714 715 716 717
}

static const gchar *
gtk_switch_get_action_name (GtkActionable *actionable)
{
  GtkSwitch *sw = GTK_SWITCH (actionable);

718
  return gtk_action_helper_get_action_name (sw->priv->action_helper);
719 720 721 722 723 724 725
}

static GVariant *
gtk_switch_get_action_target_value (GtkActionable *actionable)
{
  GtkSwitch *sw = GTK_SWITCH (actionable);

726
  return gtk_action_helper_get_action_target_value (sw->priv->action_helper);
727 728 729 730 731 732 733 734 735 736 737
}

static void
gtk_switch_actionable_iface_init (GtkActionableInterface *iface)
{
  iface->get_action_name = gtk_switch_get_action_name;
  iface->set_action_name = gtk_switch_set_action_name;
  iface->get_action_target_value = gtk_switch_get_action_target_value;
  iface->set_action_target_value = gtk_switch_set_action_target_value;
}

738 739 740 741 742 743 744 745 746 747 748 749 750 751
static void
gtk_switch_set_property (GObject      *gobject,
                         guint         prop_id,
                         const GValue *value,
                         GParamSpec   *pspec)
{
  GtkSwitch *sw = GTK_SWITCH (gobject);

  switch (prop_id)
    {
    case PROP_ACTIVE:
      gtk_switch_set_active (sw, g_value_get_boolean (value));
      break;

752 753 754 755
    case PROP_STATE:
      gtk_switch_set_state (sw, g_value_get_boolean (value));
      break;

756 757 758 759 760 761 762 763
    case PROP_RELATED_ACTION:
      gtk_switch_set_related_action (sw, g_value_get_object (value));
      break;

    case PROP_USE_ACTION_APPEARANCE:
      gtk_switch_set_use_action_appearance (sw, g_value_get_boolean (value));
      break;

764 765 766 767 768 769 770 771
    case PROP_ACTION_NAME:
      gtk_switch_set_action_name (GTK_ACTIONABLE (sw), g_value_get_string (value));
      break;

    case PROP_ACTION_TARGET:
      gtk_switch_set_action_target_value (GTK_ACTIONABLE (sw), g_value_get_variant (value));
      break;

772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
    }
}

static void
gtk_switch_get_property (GObject    *gobject,
                         guint       prop_id,
                         GValue     *value,
                         GParamSpec *pspec)
{
  GtkSwitchPrivate *priv = GTK_SWITCH (gobject)->priv;

  switch (prop_id)
    {
    case PROP_ACTIVE:
      g_value_set_boolean (value, priv->is_active);
      break;

791 792 793 794
    case PROP_STATE:
      g_value_set_boolean (value, priv->state);
      break;

795 796 797 798 799 800 801 802
    case PROP_RELATED_ACTION:
      g_value_set_object (value, priv->action);
      break;

    case PROP_USE_ACTION_APPEARANCE:
      g_value_set_boolean (value, priv->use_action_appearance);
      break;

803
    case PROP_ACTION_NAME:
804
      g_value_set_string (value, gtk_action_helper_get_action_name (priv->action_helper));
805 806 807
      break;

    case PROP_ACTION_TARGET:
808
      g_value_set_variant (value, gtk_action_helper_get_action_target_value (priv->action_helper));
809 810
      break;

811 812 813 814 815 816 817 818 819 820
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
    }
}

static void
gtk_switch_dispose (GObject *object)
{
  GtkSwitchPrivate *priv = GTK_SWITCH (object)->priv;

821
  g_clear_object (&priv->action_helper);
822

823 824
  if (priv->action)
    {
825
      G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
826
      gtk_activatable_do_set_related_action (GTK_ACTIVATABLE (object), NULL);
827
      G_GNUC_END_IGNORE_DEPRECATIONS;
828 829 830
      priv->action = NULL;
    }

Benjamin Otte's avatar
Benjamin Otte committed
831 832 833
  g_clear_object (&priv->gadget);
  g_clear_object (&priv->slider_gadget);

834 835 836
  g_clear_object (&priv->pan_gesture);
  g_clear_object (&priv->multipress_gesture);

837 838 839
  g_clear_object (&priv->on_layout);
  g_clear_object (&priv->off_layout);

840 841 842
  G_OBJECT_CLASS (gtk_switch_parent_class)->dispose (object);
}

843 844 845 846 847 848 849 850
static void
gtk_switch_finalize (GObject *object)
{
  gtk_switch_end_toggle_animation (GTK_SWITCH (object));

  G_OBJECT_CLASS (gtk_switch_parent_class)->finalize (object);
}

851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866
static gboolean
state_set (GtkSwitch *sw, gboolean state)
{
  if (sw->priv->action_helper)
    gtk_action_helper_activate (sw->priv->action_helper);

  G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
  if (sw->priv->action)
    gtk_action_activate (sw->priv->action);
  G_GNUC_END_IGNORE_DEPRECATIONS;

  gtk_switch_set_state (sw, state);

  return TRUE;
}

867 868 869 870 871 872 873
static void
gtk_switch_class_init (GtkSwitchClass *klass)
{
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  gpointer activatable_iface;

874
  G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
875
  activatable_iface = g_type_default_interface_peek (GTK_TYPE_ACTIVATABLE);
876 877
  G_GNUC_END_IGNORE_DEPRECATIONS;

878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897
  switch_props[PROP_RELATED_ACTION] =
    g_param_spec_override ("related-action",
                           g_object_interface_find_property (activatable_iface,
                                                             "related-action"));

  switch_props[PROP_USE_ACTION_APPEARANCE] =
    g_param_spec_override ("use-action-appearance",
                           g_object_interface_find_property (activatable_iface,
                                                             "use-action-appearance"));

  /**
   * GtkSwitch:active:
   *
   * Whether the #GtkSwitch widget is in its on or off state.
   */
  switch_props[PROP_ACTIVE] =
    g_param_spec_boolean ("active",
                          P_("Active"),
                          P_("Whether the switch is on or off"),
                          FALSE,
898
                          GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
899

900 901 902 903 904 905 906 907 908 909 910 911 912
  /**
   * GtkSwitch:state:
   *
   * The backend state that is controlled by the switch. 
   * See #GtkSwitch::state-set for details.
   *
   * Since: 3.14
   */
  switch_props[PROP_STATE] =
    g_param_spec_boolean ("state",
                          P_("State"),
                          P_("The backend state"),
                          FALSE,
913
                          GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
914

915 916 917
  gobject_class->set_property = gtk_switch_set_property;
  gobject_class->get_property = gtk_switch_get_property;
  gobject_class->dispose = gtk_switch_dispose;
918
  gobject_class->finalize = gtk_switch_finalize;
919 920 921 922 923 924 925 926 927 928 929 930 931

  g_object_class_install_properties (gobject_class, LAST_PROP, switch_props);

  widget_class->get_preferred_width = gtk_switch_get_preferred_width;
  widget_class->get_preferred_height = gtk_switch_get_preferred_height;
  widget_class->size_allocate = gtk_switch_size_allocate;
  widget_class->realize = gtk_switch_realize;
  widget_class->unrealize = gtk_switch_unrealize;
  widget_class->map = gtk_switch_map;
  widget_class->unmap = gtk_switch_unmap;
  widget_class->draw = gtk_switch_draw;
  widget_class->enter_notify_event = gtk_switch_enter;
  widget_class->leave_notify_event = gtk_switch_leave;
932 933
  widget_class->screen_changed = gtk_switch_screen_changed;
  widget_class->style_updated = gtk_switch_style_updated;
934

935
  klass->activate = gtk_switch_activate;
936
  klass->state_set = state_set;
937

938 939 940 941
  /**
   * GtkSwitch:slider-width:
   *
   * The minimum width of the #GtkSwitch handle, in pixels.
942 943
   *
   * Deprecated: 3.20: Use the CSS min-width property instead.
944 945 946 947 948 949 950
   */
  gtk_widget_class_install_style_property (widget_class,
                                           g_param_spec_int ("slider-width",
                                                             P_("Slider Width"),
                                                             P_("The minimum width of the handle"),
                                                             DEFAULT_SLIDER_WIDTH, G_MAXINT,
                                                             DEFAULT_SLIDER_WIDTH,
951
                                                             GTK_PARAM_READABLE|G_PARAM_DEPRECATED));
952

953 954 955 956 957 958
  /**
   * GtkSwitch:slider-height:
   *
   * The minimum height of the #GtkSwitch handle, in pixels.
   *
   * Since: 3.18
959 960
   *
   * Deprecated: 3.20: Use the CSS min-height property instead.
961 962 963 964 965 966 967
   */
  gtk_widget_class_install_style_property (widget_class,
                                           g_param_spec_int ("slider-height",
                                                             P_("Slider Height"),
                                                             P_("The minimum height of the handle"),
                                                             DEFAULT_SLIDER_HEIGHT, G_MAXINT,
                                                             DEFAULT_SLIDER_HEIGHT,
968
                                                             GTK_PARAM_READABLE|G_PARAM_DEPRECATED));
969

970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988
  /**
   * GtkSwitch::activate:
   * @widget: the object which received the signal.
   *
   * The ::activate signal on GtkSwitch is an action signal and
   * emitting it causes the switch to animate.
   * Applications should never connect to this signal, but use the
   * notify::active signal.
   */
  signals[ACTIVATE] =
    g_signal_new (I_("activate"),
                  G_OBJECT_CLASS_TYPE (gobject_class),
                  G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
                  G_STRUCT_OFFSET (GtkSwitchClass, activate),
                  NULL, NULL,
                  _gtk_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);
  widget_class->activate_signal = signals[ACTIVATE];

989 990 991
  /**
   * GtkSwitch::state-set:
   * @widget: the object on which the signal was emitted
Jasper St. Pierre's avatar
Jasper St. Pierre committed
992
   * @state: the new state of the switch
993 994 995
   *
   * The ::state-set signal on GtkSwitch is emitted to change the underlying
   * state. It is emitted when the user changes the switch position. The
996
   * default handler keeps the state in sync with the #GtkSwitch:active
997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021
   * property.
   *
   * To implement delayed state change, applications can connect to this signal,
   * initiate the change of the underlying state, and call gtk_switch_set_state()
   * when the underlying state change is complete. The signal handler should
   * return %TRUE to prevent the default handler from running.
   *
   * Visually, the underlying state is represented by the trough color of
   * the switch, while the #GtkSwitch:active property is represented by the
   * position of the switch.
   *
   * Returns: %TRUE to stop the signal emission
   *
   * Since: 3.14
   */
  signals[STATE_SET] =
    g_signal_new (I_("state-set"),
                  G_OBJECT_CLASS_TYPE (gobject_class),
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GtkSwitchClass, state_set),
                  _gtk_boolean_handled_accumulator, NULL,
                  _gtk_marshal_BOOLEAN__BOOLEAN,
                  G_TYPE_BOOLEAN, 1,
                  G_TYPE_BOOLEAN);

1022 1023 1024
  g_object_class_override_property (gobject_class, PROP_ACTION_NAME, "action-name");
  g_object_class_override_property (gobject_class, PROP_ACTION_TARGET, "action-target");

1025
  gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_SWITCH_ACCESSIBLE);
1026
  gtk_widget_class_set_accessible_role (widget_class, ATK_ROLE_TOGGLE_BUTTON);
1027 1028

  gtk_widget_class_set_css_name (widget_class, "switch");
1029 1030 1031 1032 1033
}

static void
gtk_switch_init (GtkSwitch *self)
{
1034
  GtkSwitchPrivate *priv;
1035
  GtkGesture *gesture;
1036
  GtkCssNode *widget_node;
1037

1038 1039 1040
  priv = self->priv = gtk_switch_get_instance_private (self);

  priv->use_action_appearance = TRUE;
1041 1042
  gtk_widget_set_has_window (GTK_WIDGET (self), FALSE);
  gtk_widget_set_can_focus (GTK_WIDGET (self), TRUE);
1043

1044
  widget_node = gtk_widget_get_css_node (GTK_WIDGET (self));
Benjamin Otte's avatar
Benjamin Otte committed
1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061
  priv->gadget = gtk_css_custom_gadget_new_for_node (widget_node,
                                                     GTK_WIDGET (self),
                                                     gtk_switch_get_content_size,
                                                     gtk_switch_allocate_contents,
                                                     gtk_switch_render_trough,
                                                     NULL,
                                                     NULL);

  priv->slider_gadget = gtk_css_custom_gadget_new ("slider",
                                                   GTK_WIDGET (self),
                                                   priv->gadget,
                                                   NULL,
                                                   gtk_switch_get_slider_size,
                                                   NULL,
                                                   gtk_switch_render_slider,
                                                   NULL,
                                                   NULL);
1062

1063 1064 1065 1066 1067 1068 1069
  gesture = gtk_gesture_multi_press_new (GTK_WIDGET (self));
  gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture), FALSE);
  gtk_gesture_single_set_exclusive (GTK_GESTURE_SINGLE (gesture), TRUE);
  g_signal_connect (gesture, "pressed",
                    G_CALLBACK (gtk_switch_multipress_gesture_pressed), self);
  g_signal_connect (gesture, "released",
                    G_CALLBACK (gtk_switch_multipress_gesture_released), self);
1070
  gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture),
1071
                                              GTK_PHASE_BUBBLE);
1072
  priv->multipress_gesture = gesture;
1073 1074

  gesture = gtk_gesture_pan_new (GTK_WIDGET (self),
1075
                                 GTK_ORIENTATION_HORIZONTAL);
1076 1077 1078 1079 1080 1081
  gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (gesture), FALSE);
  gtk_gesture_single_set_exclusive (GTK_GESTURE_SINGLE (gesture), TRUE);
  g_signal_connect (gesture, "pan",
                    G_CALLBACK (gtk_switch_pan_gesture_pan), self);
  g_signal_connect (gesture, "drag-end",
                    G_CALLBACK (gtk_switch_pan_gesture_drag_end), self);
1082
  gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (gesture),
1083
                                              GTK_PHASE_BUBBLE);
1084
  priv->pan_gesture = gesture;
1085 1086

  gtk_switch_create_pango_layouts (self);
1087 1088 1089 1090 1091 1092 1093
}

/**
 * gtk_switch_new:
 *
 * Creates a new #GtkSwitch widget.
 *
1094
 * Returns: the newly created #GtkSwitch instance
1095 1096
 *
 * Since: 3.0
1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109
 */
GtkWidget *
gtk_switch_new (void)
{
  return g_object_new (GTK_TYPE_SWITCH, NULL);
}

/**
 * gtk_switch_set_active:
 * @sw: a #GtkSwitch
 * @is_active: %TRUE if @sw should be active, and %FALSE otherwise
 *
 * Changes the state of @sw to the desired one.
1110 1111
 *
 * Since: 3.0
1112 1113 1114 1115 1116 1117 1118 1119 1120
 */
void
gtk_switch_set_active (GtkSwitch *sw,
                       gboolean   is_active)
{
  GtkSwitchPrivate *priv;

  g_return_if_fail (GTK_IS_SWITCH (sw));

1121 1122
  gtk_switch_end_toggle_animation (sw);

1123 1124 1125 1126 1127 1128
  is_active = !!is_active;

  priv = sw->priv;

  if (priv->is_active != is_active)
    {
1129
      AtkObject *accessible;
1130
      gboolean handled;
1131

1132 1133
      priv->is_active = is_active;

1134
      if (priv->is_active)
1135
        priv->handle_pos = 1.0;
1136
      else
1137
        priv->handle_pos = 0.0;
1138

1139
      g_signal_emit (sw, signals[STATE_SET], 0, is_active, &handled);
1140

1141 1142
      g_object_notify_by_pspec (G_OBJECT (sw), switch_props[PROP_ACTIVE]);

1143 1144 1145
      accessible = gtk_widget_get_accessible (GTK_WIDGET (sw));
      atk_object_notify_state_change (accessible, ATK_STATE_CHECKED, priv->is_active);

Benjamin Otte's avatar
Benjamin Otte committed
1146
      gtk_widget_queue_allocate (GTK_WIDGET (sw));
1147 1148 1149 1150 1151 1152 1153
    }
}

/**
 * gtk_switch_get_active:
 * @sw: a #GtkSwitch
 *
1154
 * Gets whether the #GtkSwitch is in its “on” or “off” state.
1155
 *
1156
 * Returns: %TRUE if the #GtkSwitch is active, and %FALSE otherwise
1157 1158
 *
 * Since: 3.0
1159 1160 1161 1162 1163 1164 1165 1166 1167
 */
gboolean
gtk_switch_get_active (GtkSwitch *sw)
{
  g_return_val_if_fail (GTK_IS_SWITCH (sw), FALSE);

  return sw->priv->is_active;
}

1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202
/**
 * gtk_switch_set_state:
 * @sw: a #GtkSwitch
 * @state: the new state
 *
 * Sets the underlying state of the #GtkSwitch.
 *
 * Normally, this is the same as #GtkSwitch:active, unless the switch
 * is set up for delayed state changes. This function is typically
 * called from a #GtkSwitch::state-set signal handler.
 *
 * See #GtkSwitch::state-set for details.
 *
 * Since: 3.14
 */
void
gtk_switch_set_state (GtkSwitch *sw,
                      gboolean   state)
{
  g_return_if_fail (GTK_IS_SWITCH (sw));

  state = state != FALSE;

  if (sw->priv->state == state)
    return;

  sw->priv->state = state;

  /* This will be a no-op if we're switching the state in response
   * to a UI change. We're setting active anyway, to catch 'spontaneous'
   * state changes.
   */
  gtk_switch_set_active (sw, state);

  if (state)
1203
    gtk_widget_set_state_flags (GTK_WIDGET (sw), GTK_STATE_FLAG_CHECKED, FALSE);
1204
  else
1205
    gtk_widget_unset_state_flags (GTK_WIDGET (sw), GTK_STATE_FLAG_CHECKED);
1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227

  g_object_notify (G_OBJECT (sw), "state");
}

/**
 * gtk_switch_get_state:
 * @sw: a #GtkSwitch
 *
 * Gets the underlying state of the #GtkSwitch.
 *
 * Returns: the underlying state
 *
 * Since: 3.14
 */
gboolean
gtk_switch_get_state (GtkSwitch *sw)
{
  g_return_val_if_fail (GTK_IS_SWITCH (sw), FALSE);

  return sw->priv->state;
}

1228 1229 1230 1231 1232
static void
gtk_switch_update (GtkActivatable *activatable,
                   GtkAction      *action,
                   const gchar    *property_name)
{
1233 1234
  G_GNUC_BEGIN_IGNORE_DEPRECATIONS;

1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251
  if (strcmp (property_name, "visible") == 0)
    {
      if (gtk_action_is_visible (action))
        gtk_widget_show (GTK_WIDGET (activatable));
      else
        gtk_widget_hide (GTK_WIDGET (activatable));
    }
  else if (strcmp (property_name, "sensitive") == 0)
    {
      gtk_widget_set_sensitive (GTK_WIDGET (activatable), gtk_action_is_sensitive (action));
    }
  else if (strcmp (property_name, "active") == 0)
    {
      gtk_action_block_activate (action);
      gtk_switch_set_active (GTK_SWITCH (activatable), gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
      gtk_action_unblock_activate (action);
    }
1252 1253

  G_GNUC_END_IGNORE_DEPRECATIONS;
1254 1255 1256 1257 1258 1259 1260 1261 1262
}

static void
gtk_switch_sync_action_properties (GtkActivatable *activatable,
                                   GtkAction      *action)
{
  if (!action)
    return;

1263 1264
  G_GNUC_BEGIN_IGNORE_DEPRECATIONS;

1265 1266 1267 1268 1269 1270 1271 1272 1273 1274
  if (gtk_action_is_visible (action))
    gtk_widget_show (GTK_WIDGET (activatable));
  else
    gtk_widget_hide (GTK_WIDGET (activatable));

  gtk_widget_set_sensitive (GTK_WIDGET (activatable), gtk_action_is_sensitive (action));

  gtk_action_block_activate (action);
  gtk_switch_set_active (GTK_SWITCH (activatable), gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
  gtk_action_unblock_activate (action);
1275 1276

  G_GNUC_END_IGNORE_DEPRECATIONS;
1277 1278 1279 1280 1281 1282 1283 1284
}

static void
gtk_switch_activatable_interface_init (GtkActivatableIface *iface)
{
  iface->update = gtk_switch_update;
  iface->sync_action_properties = gtk_switch_sync_action_properties;
}