gtkstack.c 78.8 KB
Newer Older
Matthias Clasen's avatar
Matthias Clasen committed
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
/*
 * Copyright (c) 2013 Red Hat, Inc.
 *
 * This program 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 program 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 program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Author: Alexander Larsson <alexl@redhat.com>
 *
 */

#include "config.h"

#include <gtk/gtk.h>
#include "gtkstack.h"
#include "gtkprivate.h"
#include "gtkintl.h"
Matthias Clasen's avatar
Matthias Clasen committed
28
29
#include "gtkcsscustomgadgetprivate.h"
#include "gtkcontainerprivate.h"
Matt Watson's avatar
Matt Watson committed
30
#include "gtkprogresstrackerprivate.h"
31
#include "gtksettingsprivate.h"
Matthias Clasen's avatar
Matthias Clasen committed
32
#include "gtkwidgetprivate.h"
Timm Bäder's avatar
Timm Bäder committed
33
34
#include "a11y/gtkstackaccessible.h"
#include "a11y/gtkstackaccessibleprivate.h"
Matthias Clasen's avatar
Matthias Clasen committed
35
36
37
#include <math.h>
#include <string.h>

Matthias Clasen's avatar
Matthias Clasen committed
38
39
40
41
42
43
44
45
/**
 * SECTION:gtkstack
 * @Short_description: A stacking container
 * @Title: GtkStack
 * @See_also: #GtkNotebook, #GtkStackSwitcher
 *
 * The GtkStack widget is a container which only shows
 * one of its children at a time. In contrast to GtkNotebook,
Matthias Clasen's avatar
Matthias Clasen committed
46
47
48
 * GtkStack does not provide a means for users to change the
 * visible child. Instead, the #GtkStackSwitcher widget can be
 * used with GtkStack to provide this functionality.
Matthias Clasen's avatar
Matthias Clasen committed
49
50
51
 *
 * Transitions between pages can be animated as slides or
 * fades. This can be controlled with gtk_stack_set_transition_type().
52
 * These animations respect the #GtkSettings:gtk-enable-animations
Matthias Clasen's avatar
Matthias Clasen committed
53
54
55
 * setting.
 *
 * The GtkStack widget was added in GTK+ 3.10.
Matthias Clasen's avatar
Matthias Clasen committed
56
57
58
59
 *
 * # CSS nodes
 *
 * GtkStack has a single CSS node named stack.
Matthias Clasen's avatar
Matthias Clasen committed
60
61
62
63
64
65
 */

/**
 * GtkStackTransitionType:
 * @GTK_STACK_TRANSITION_TYPE_NONE: No transition
 * @GTK_STACK_TRANSITION_TYPE_CROSSFADE: A cross-fade
66
67
68
69
 * @GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT: Slide from left to right
 * @GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT: Slide from right to left
 * @GTK_STACK_TRANSITION_TYPE_SLIDE_UP: Slide from bottom up
 * @GTK_STACK_TRANSITION_TYPE_SLIDE_DOWN: Slide from top down
70
71
 * @GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT: Slide from left or right according to the children order
 * @GTK_STACK_TRANSITION_TYPE_SLIDE_UP_DOWN: Slide from top down or bottom up according to the order
72
73
74
75
76
77
78
79
 * @GTK_STACK_TRANSITION_TYPE_OVER_UP: Cover the old page by sliding up. Since 3.12
 * @GTK_STACK_TRANSITION_TYPE_OVER_DOWN: Cover the old page by sliding down. Since: 3.12
 * @GTK_STACK_TRANSITION_TYPE_OVER_LEFT: Cover the old page by sliding to the left. Since: 3.12
 * @GTK_STACK_TRANSITION_TYPE_OVER_RIGHT: Cover the old page by sliding to the right. Since: 3.12
 * @GTK_STACK_TRANSITION_TYPE_UNDER_UP: Uncover the new page by sliding up. Since 3.12
 * @GTK_STACK_TRANSITION_TYPE_UNDER_DOWN: Uncover the new page by sliding down. Since: 3.12
 * @GTK_STACK_TRANSITION_TYPE_UNDER_LEFT: Uncover the new page by sliding to the left. Since: 3.12
 * @GTK_STACK_TRANSITION_TYPE_UNDER_RIGHT: Uncover the new page by sliding to the right. Since: 3.12
80
81
82
83
 * @GTK_STACK_TRANSITION_TYPE_OVER_UP_DOWN: Cover the old page sliding up or uncover the new page sliding down, according to order. Since: 3.12
 * @GTK_STACK_TRANSITION_TYPE_OVER_DOWN_UP: Cover the old page sliding down or uncover the new page sliding up, according to order. Since: 3.14
 * @GTK_STACK_TRANSITION_TYPE_OVER_LEFT_RIGHT: Cover the old page sliding left or uncover the new page sliding right, according to order. Since: 3.14
 * @GTK_STACK_TRANSITION_TYPE_OVER_RIGHT_LEFT: Cover the old page sliding right or uncover the new page sliding left, according to order. Since: 3.14
Matthias Clasen's avatar
Matthias Clasen committed
84
85
86
 *
 * These enumeration values describe the possible transitions
 * between pages in a #GtkStack widget.
87
88
 *
 * New values may be added to this enumeration over time.
Matthias Clasen's avatar
Matthias Clasen committed
89
90
 */

Matthias Clasen's avatar
Matthias Clasen committed
91
92
93
94
95
96
97
/* TODO:
 *  filter events out events to the last_child widget during transitions
 */

enum  {
  PROP_0,
  PROP_HOMOGENEOUS,
Matthias Clasen's avatar
Matthias Clasen committed
98
99
  PROP_HHOMOGENEOUS,
  PROP_VHOMOGENEOUS,
Matthias Clasen's avatar
Matthias Clasen committed
100
101
102
  PROP_VISIBLE_CHILD,
  PROP_VISIBLE_CHILD_NAME,
  PROP_TRANSITION_DURATION,
103
  PROP_TRANSITION_TYPE,
104
  PROP_TRANSITION_RUNNING,
105
  PROP_INTERPOLATE_SIZE,
106
  LAST_PROP
Matthias Clasen's avatar
Matthias Clasen committed
107
108
109
110
111
112
113
114
};

enum
{
  CHILD_PROP_0,
  CHILD_PROP_NAME,
  CHILD_PROP_TITLE,
  CHILD_PROP_ICON_NAME,
115
  CHILD_PROP_POSITION,
116
117
  CHILD_PROP_NEEDS_ATTENTION,
  LAST_CHILD_PROP
Matthias Clasen's avatar
Matthias Clasen committed
118
119
120
121
122
123
124
125
126
};

typedef struct _GtkStackChildInfo GtkStackChildInfo;

struct _GtkStackChildInfo {
  GtkWidget *widget;
  gchar *name;
  gchar *title;
  gchar *icon_name;
127
  gboolean needs_attention;
128
  GtkWidget *last_focus;
Matthias Clasen's avatar
Matthias Clasen committed
129
130
};

131
typedef struct {
Matthias Clasen's avatar
Matthias Clasen committed
132
133
134
135
136
137
138
  GList *children;

  GdkWindow* bin_window;
  GdkWindow* view_window;

  GtkStackChildInfo *visible_child;

Matthias Clasen's avatar
Matthias Clasen committed
139
140
  GtkCssGadget *gadget;

Matthias Clasen's avatar
Matthias Clasen committed
141
142
  gboolean hhomogeneous;
  gboolean vhomogeneous;
Matthias Clasen's avatar
Matthias Clasen committed
143
144

  GtkStackTransitionType transition_type;
145
  guint transition_duration;
Matthias Clasen's avatar
Matthias Clasen committed
146
147
148
149
150

  GtkStackChildInfo *last_visible_child;
  cairo_surface_t *last_visible_surface;
  GtkAllocation last_visible_surface_allocation;
  guint tick_id;
Matt Watson's avatar
Matt Watson committed
151
  GtkProgressTracker tracker;
152
  gboolean first_frame_skipped;
153

154
155
156
  gint last_visible_widget_width;
  gint last_visible_widget_height;

157
158
  gboolean interpolate_size;

159
  GtkStackTransitionType active_transition_type;
160

161
} GtkStackPrivate;
Matthias Clasen's avatar
Matthias Clasen committed
162

163
static GParamSpec *stack_props[LAST_PROP] = { NULL, };
164
static GParamSpec *stack_child_props[LAST_CHILD_PROP] = { NULL, };
165

Matthias Clasen's avatar
Matthias Clasen committed
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
static void     gtk_stack_add                            (GtkContainer  *widget,
                                                          GtkWidget     *child);
static void     gtk_stack_remove                         (GtkContainer  *widget,
                                                          GtkWidget     *child);
static void     gtk_stack_forall                         (GtkContainer  *container,
                                                          gboolean       include_internals,
                                                          GtkCallback    callback,
                                                          gpointer       callback_data);
static void     gtk_stack_compute_expand                 (GtkWidget     *widget,
                                                          gboolean      *hexpand,
                                                          gboolean      *vexpand);
static void     gtk_stack_size_allocate                  (GtkWidget     *widget,
                                                          GtkAllocation *allocation);
static gboolean gtk_stack_draw                           (GtkWidget     *widget,
                                                          cairo_t       *cr);
static void     gtk_stack_get_preferred_height           (GtkWidget     *widget,
                                                          gint          *minimum_height,
                                                          gint          *natural_height);
static void     gtk_stack_get_preferred_height_for_width (GtkWidget     *widget,
                                                          gint           width,
                                                          gint          *minimum_height,
                                                          gint          *natural_height);
static void     gtk_stack_get_preferred_width            (GtkWidget     *widget,
                                                          gint          *minimum_width,
                                                          gint          *natural_width);
static void     gtk_stack_get_preferred_width_for_height (GtkWidget     *widget,
                                                          gint           height,
                                                          gint          *minimum_width,
                                                          gint          *natural_width);
static void     gtk_stack_finalize                       (GObject       *obj);
static void     gtk_stack_get_property                   (GObject       *object,
                                                          guint          property_id,
                                                          GValue        *value,
                                                          GParamSpec    *pspec);
static void     gtk_stack_set_property                   (GObject       *object,
                                                          guint          property_id,
                                                          const GValue  *value,
                                                          GParamSpec    *pspec);
static void     gtk_stack_get_child_property             (GtkContainer  *container,
                                                          GtkWidget     *child,
                                                          guint          property_id,
                                                          GValue        *value,
                                                          GParamSpec    *pspec);
static void     gtk_stack_set_child_property             (GtkContainer  *container,
                                                          GtkWidget     *child,
                                                          guint          property_id,
                                                          const GValue  *value,
                                                          GParamSpec    *pspec);
static void     gtk_stack_unschedule_ticks               (GtkStack      *stack);
Matthias Clasen's avatar
Matthias Clasen committed
215
216
217
218
static gint     get_bin_window_x                         (GtkStack            *stack,
                                                          const GtkAllocation *allocation);
static gint     get_bin_window_y                         (GtkStack            *stack,
                                                          const GtkAllocation *allocation);
Matthias Clasen's avatar
Matthias Clasen committed
219

220
G_DEFINE_TYPE_WITH_PRIVATE (GtkStack, gtk_stack, GTK_TYPE_CONTAINER)
Matthias Clasen's avatar
Matthias Clasen committed
221

222
223
224
225
226
227
228
229
230
231
232
static void
gtk_stack_dispose (GObject *obj)
{
  GtkStack *stack = GTK_STACK (obj);
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);

  priv->visible_child = NULL;

  G_OBJECT_CLASS (gtk_stack_parent_class)->dispose (obj);
}

Matthias Clasen's avatar
Matthias Clasen committed
233
234
235
236
static void
gtk_stack_finalize (GObject *obj)
{
  GtkStack *stack = GTK_STACK (obj);
237
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
Matthias Clasen's avatar
Matthias Clasen committed
238
239
240
241
242
243

  gtk_stack_unschedule_ticks (stack);

  if (priv->last_visible_surface != NULL)
    cairo_surface_destroy (priv->last_visible_surface);

Matthias Clasen's avatar
Matthias Clasen committed
244
245
  g_clear_object (&priv->gadget);

Matthias Clasen's avatar
Matthias Clasen committed
246
247
248
249
250
  G_OBJECT_CLASS (gtk_stack_parent_class)->finalize (obj);
}

static void
gtk_stack_get_property (GObject   *object,
Matthias Clasen's avatar
Matthias Clasen committed
251
252
253
                        guint       property_id,
                        GValue     *value,
                        GParamSpec *pspec)
Matthias Clasen's avatar
Matthias Clasen committed
254
255
256
257
258
259
{
  GtkStack *stack = GTK_STACK (object);

  switch (property_id)
    {
    case PROP_HOMOGENEOUS:
Matthias Clasen's avatar
Matthias Clasen committed
260
261
262
263
264
265
266
      g_value_set_boolean (value, gtk_stack_get_homogeneous (stack));
      break;
    case PROP_HHOMOGENEOUS:
      g_value_set_boolean (value, gtk_stack_get_hhomogeneous (stack));
      break;
    case PROP_VHOMOGENEOUS:
      g_value_set_boolean (value, gtk_stack_get_vhomogeneous (stack));
Matthias Clasen's avatar
Matthias Clasen committed
267
268
      break;
    case PROP_VISIBLE_CHILD:
269
      g_value_set_object (value, gtk_stack_get_visible_child (stack));
Matthias Clasen's avatar
Matthias Clasen committed
270
271
272
273
274
      break;
    case PROP_VISIBLE_CHILD_NAME:
      g_value_set_string (value, gtk_stack_get_visible_child_name (stack));
      break;
    case PROP_TRANSITION_DURATION:
275
      g_value_set_uint (value, gtk_stack_get_transition_duration (stack));
Matthias Clasen's avatar
Matthias Clasen committed
276
277
      break;
    case PROP_TRANSITION_TYPE:
278
      g_value_set_enum (value, gtk_stack_get_transition_type (stack));
Matthias Clasen's avatar
Matthias Clasen committed
279
      break;
280
281
282
    case PROP_TRANSITION_RUNNING:
      g_value_set_boolean (value, gtk_stack_get_transition_running (stack));
      break;
283
284
285
    case PROP_INTERPOLATE_SIZE:
      g_value_set_boolean (value, gtk_stack_get_interpolate_size (stack));
      break;
Matthias Clasen's avatar
Matthias Clasen committed
286
287
288
289
290
291
292
293
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
gtk_stack_set_property (GObject     *object,
Matthias Clasen's avatar
Matthias Clasen committed
294
295
296
                        guint         property_id,
                        const GValue *value,
                        GParamSpec   *pspec)
Matthias Clasen's avatar
Matthias Clasen committed
297
298
299
300
301
302
303
304
{
  GtkStack *stack = GTK_STACK (object);

  switch (property_id)
    {
    case PROP_HOMOGENEOUS:
      gtk_stack_set_homogeneous (stack, g_value_get_boolean (value));
      break;
Matthias Clasen's avatar
Matthias Clasen committed
305
306
307
308
309
310
    case PROP_HHOMOGENEOUS:
      gtk_stack_set_hhomogeneous (stack, g_value_get_boolean (value));
      break;
    case PROP_VHOMOGENEOUS:
      gtk_stack_set_vhomogeneous (stack, g_value_get_boolean (value));
      break;
Matthias Clasen's avatar
Matthias Clasen committed
311
312
313
314
315
316
317
    case PROP_VISIBLE_CHILD:
      gtk_stack_set_visible_child (stack, g_value_get_object (value));
      break;
    case PROP_VISIBLE_CHILD_NAME:
      gtk_stack_set_visible_child_name (stack, g_value_get_string (value));
      break;
    case PROP_TRANSITION_DURATION:
318
      gtk_stack_set_transition_duration (stack, g_value_get_uint (value));
Matthias Clasen's avatar
Matthias Clasen committed
319
320
      break;
    case PROP_TRANSITION_TYPE:
321
      gtk_stack_set_transition_type (stack, g_value_get_enum (value));
Matthias Clasen's avatar
Matthias Clasen committed
322
      break;
323
324
325
    case PROP_INTERPOLATE_SIZE:
      gtk_stack_set_interpolate_size (stack, g_value_get_boolean (value));
      break;
Matthias Clasen's avatar
Matthias Clasen committed
326
327
328
329
330
331
332
333
334
335
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
gtk_stack_realize (GtkWidget *widget)
{
  GtkStack *stack = GTK_STACK (widget);
336
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
Matthias Clasen's avatar
Matthias Clasen committed
337
338
339
340
341
342
343
  GtkAllocation allocation;
  GdkWindowAttr attributes = { 0 };
  GdkWindowAttributesType attributes_mask;
  GtkStackChildInfo *info;
  GList *l;

  gtk_widget_set_realized (widget, TRUE);
344
  gtk_widget_set_window (widget, g_object_ref (gtk_widget_get_parent_window (widget)));
Matthias Clasen's avatar
Matthias Clasen committed
345

346
  gtk_css_gadget_get_content_allocation (priv->gadget, &allocation, NULL);
Matthias Clasen's avatar
Matthias Clasen committed
347
348
349
350
351
352
353
354
355

  attributes.x = allocation.x;
  attributes.y = allocation.y;
  attributes.width = allocation.width;
  attributes.height = allocation.height;
  attributes.window_type = GDK_WINDOW_CHILD;
  attributes.wclass = GDK_INPUT_OUTPUT;
  attributes.visual = gtk_widget_get_visual (widget);
  attributes.event_mask =
356
    gtk_widget_get_events (widget);
Matthias Clasen's avatar
Matthias Clasen committed
357
358
359
  attributes_mask = (GDK_WA_X | GDK_WA_Y) | GDK_WA_VISUAL;

  priv->view_window =
360
    gdk_window_new (gtk_widget_get_window (GTK_WIDGET (stack)),
Matthias Clasen's avatar
Matthias Clasen committed
361
362
363
364
                    &attributes, attributes_mask);
  gtk_widget_register_window (widget, priv->view_window);

  attributes.x = get_bin_window_x (stack, &allocation);
365
  attributes.y = get_bin_window_y (stack, &allocation);
Matthias Clasen's avatar
Matthias Clasen committed
366
367
368
  attributes.width = allocation.width;
  attributes.height = allocation.height;

369
370
371
372
373
374
  for (l = priv->children; l != NULL; l = l->next)
    {
      info = l->data;
      attributes.event_mask |= gtk_widget_get_events (info->widget);
    }

Matthias Clasen's avatar
Matthias Clasen committed
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
  priv->bin_window =
    gdk_window_new (priv->view_window, &attributes, attributes_mask);
  gtk_widget_register_window (widget, priv->bin_window);

  for (l = priv->children; l != NULL; l = l->next)
    {
      info = l->data;

      gtk_widget_set_parent_window (info->widget, priv->bin_window);
    }

  gdk_window_show (priv->bin_window);
}

static void
gtk_stack_unrealize (GtkWidget *widget)
{
  GtkStack *stack = GTK_STACK (widget);
393
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
Matthias Clasen's avatar
Matthias Clasen committed
394
395
396

  gtk_widget_unregister_window (widget, priv->bin_window);
  gdk_window_destroy (priv->bin_window);
397
398
399
  priv->bin_window = NULL;
  gtk_widget_unregister_window (widget, priv->view_window);
  gdk_window_destroy (priv->view_window);
Matthias Clasen's avatar
Matthias Clasen committed
400
401
402
403
404
  priv->view_window = NULL;

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

405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
static void
gtk_stack_map (GtkWidget *widget)
{
  GtkStack *stack = GTK_STACK (widget);
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);

  GTK_WIDGET_CLASS (gtk_stack_parent_class)->map (widget);

  gdk_window_show (priv->view_window);
}

static void
gtk_stack_unmap (GtkWidget *widget)
{
  GtkStack *stack = GTK_STACK (widget);
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);

  gdk_window_hide (priv->view_window);

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

Matthias Clasen's avatar
Matthias Clasen committed
427
428
429
430
431
432
433
434
435
static void
gtk_stack_class_init (GtkStackClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);

  object_class->get_property = gtk_stack_get_property;
  object_class->set_property = gtk_stack_set_property;
436
  object_class->dispose = gtk_stack_dispose;
Matthias Clasen's avatar
Matthias Clasen committed
437
438
439
440
441
442
  object_class->finalize = gtk_stack_finalize;

  widget_class->size_allocate = gtk_stack_size_allocate;
  widget_class->draw = gtk_stack_draw;
  widget_class->realize = gtk_stack_realize;
  widget_class->unrealize = gtk_stack_unrealize;
443
444
  widget_class->map = gtk_stack_map;
  widget_class->unmap = gtk_stack_unmap;
Matthias Clasen's avatar
Matthias Clasen committed
445
446
447
448
449
450
451
452
453
454
455
456
457
  widget_class->get_preferred_height = gtk_stack_get_preferred_height;
  widget_class->get_preferred_height_for_width = gtk_stack_get_preferred_height_for_width;
  widget_class->get_preferred_width = gtk_stack_get_preferred_width;
  widget_class->get_preferred_width_for_height = gtk_stack_get_preferred_width_for_height;
  widget_class->compute_expand = gtk_stack_compute_expand;

  container_class->add = gtk_stack_add;
  container_class->remove = gtk_stack_remove;
  container_class->forall = gtk_stack_forall;
  container_class->set_child_property = gtk_stack_set_child_property;
  container_class->get_child_property = gtk_stack_get_child_property;
  gtk_container_class_handle_border_width (container_class);

458
459
  stack_props[PROP_HOMOGENEOUS] =
      g_param_spec_boolean ("homogeneous", P_("Homogeneous"), P_("Homogeneous sizing"),
Matthias Clasen's avatar
Matthias Clasen committed
460
461
462
463
464
465
466
467
468
469
470
471
472
                            TRUE,
                            GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);

  /**
   * GtkStack:hhomogeneous:
   *
   * %TRUE if the stack allocates the same width for all children.
   *
   * Since: 3.16
   */
  stack_props[PROP_HHOMOGENEOUS] =
      g_param_spec_boolean ("hhomogeneous", P_("Horizontally homogeneous"), P_("Horizontally homogeneous sizing"),
                            TRUE,
473
                            GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
Matthias Clasen's avatar
Matthias Clasen committed
474
475
476
477
478
479
480
481
482
483

  /**
   * GtkStack:vhomogeneous:
   *
   * %TRUE if the stack allocates the same height for all children.
   *
   * Since: 3.16
   */
  stack_props[PROP_VHOMOGENEOUS] =
      g_param_spec_boolean ("vhomogeneous", P_("Vertically homogeneous"), P_("Vertically homogeneous sizing"),
484
                            TRUE,
485
                            GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
486
487
488
489
490
491
492
493
494
495
496
  stack_props[PROP_VISIBLE_CHILD] =
      g_param_spec_object ("visible-child", P_("Visible child"), P_("The widget currently visible in the stack"),
                           GTK_TYPE_WIDGET,
                           GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
  stack_props[PROP_VISIBLE_CHILD_NAME] =
      g_param_spec_string ("visible-child-name", P_("Name of visible child"), P_("The name of the widget currently visible in the stack"),
                           NULL,
                           GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
  stack_props[PROP_TRANSITION_DURATION] =
      g_param_spec_uint ("transition-duration", P_("Transition duration"), P_("The animation duration, in milliseconds"),
                         0, G_MAXUINT, 200,
497
                         GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
498
499
500
  stack_props[PROP_TRANSITION_TYPE] =
      g_param_spec_enum ("transition-type", P_("Transition type"), P_("The type of animation used to transition"),
                         GTK_TYPE_STACK_TRANSITION_TYPE, GTK_STACK_TRANSITION_TYPE_NONE,
501
                         GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
502
503
504
505
  stack_props[PROP_TRANSITION_RUNNING] =
      g_param_spec_boolean ("transition-running", P_("Transition running"), P_("Whether or not the transition is currently running"),
                            FALSE,
                            GTK_PARAM_READABLE);
506
507
508
  stack_props[PROP_INTERPOLATE_SIZE] =
      g_param_spec_boolean ("interpolate-size", P_("Interpolate size"), P_("Whether or not the size should smoothly change when changing between differently sized children"),
                            FALSE,
509
                            GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
510

511
512

  g_object_class_install_properties (object_class, LAST_PROP, stack_props);
Matthias Clasen's avatar
Matthias Clasen committed
513

514
  stack_child_props[CHILD_PROP_NAME] =
Matthias Clasen's avatar
Matthias Clasen committed
515
516
517
518
    g_param_spec_string ("name",
                         P_("Name"),
                         P_("The name of the child page"),
                         NULL,
519
                         GTK_PARAM_READWRITE);
Matthias Clasen's avatar
Matthias Clasen committed
520

521
  stack_child_props[CHILD_PROP_TITLE] =
Matthias Clasen's avatar
Matthias Clasen committed
522
523
524
525
    g_param_spec_string ("title",
                         P_("Title"),
                         P_("The title of the child page"),
                         NULL,
526
                         GTK_PARAM_READWRITE);
Matthias Clasen's avatar
Matthias Clasen committed
527

528
  stack_child_props[CHILD_PROP_ICON_NAME] =
Matthias Clasen's avatar
Matthias Clasen committed
529
530
531
532
    g_param_spec_string ("icon-name",
                         P_("Icon name"),
                         P_("The icon name of the child page"),
                         NULL,
533
                         GTK_PARAM_READWRITE);
Matthias Clasen's avatar
Matthias Clasen committed
534

535
  stack_child_props[CHILD_PROP_POSITION] =
Matthias Clasen's avatar
Matthias Clasen committed
536
537
538
    g_param_spec_int ("position",
                      P_("Position"),
                      P_("The index of the child in the parent"),
539
540
541
                      -1, G_MAXINT,
                      0,
                      GTK_PARAM_READWRITE);
542
543
544
545
546
547
548
549
550
551
552

  /**
   * GtkStack:needs-attention:
   *
   * Sets a flag specifying whether the child requires the user attention.
   * This is used by the #GtkStackSwitcher to change the appearance of the
   * corresponding button when a page needs attention and it is not the
   * current one.
   *
   * Since: 3.12
   */
553
  stack_child_props[CHILD_PROP_NEEDS_ATTENTION] =
554
555
556
557
    g_param_spec_boolean ("needs-attention",
                         P_("Needs Attention"),
                         P_("Whether this page needs attention"),
                         FALSE,
558
559
560
                         GTK_PARAM_READWRITE);

  gtk_container_class_install_child_properties (container_class, LAST_CHILD_PROP, stack_child_props);
Matthias Clasen's avatar
Matthias Clasen committed
561

Timm Bäder's avatar
Timm Bäder committed
562
  gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_STACK_ACCESSIBLE);
Matthias Clasen's avatar
Matthias Clasen committed
563
  gtk_widget_class_set_css_name (widget_class, "stack");
Matthias Clasen's avatar
Matthias Clasen committed
564
565
}

Matthias Clasen's avatar
Matthias Clasen committed
566
567
568
569
570
571
572
573
574
/**
 * gtk_stack_new:
 *
 * Creates a new #GtkStack container.
 *
 * Returns: a new #GtkStack
 *
 * Since: 3.10
 */
Matthias Clasen's avatar
Matthias Clasen committed
575
576
577
578
579
580
581
582
583
584
GtkWidget *
gtk_stack_new (void)
{
  return g_object_new (GTK_TYPE_STACK, NULL);
}

static GtkStackChildInfo *
find_child_info_for_widget (GtkStack  *stack,
                            GtkWidget *child)
{
585
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
Matthias Clasen's avatar
Matthias Clasen committed
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
  GtkStackChildInfo *info;
  GList *l;

  for (l = priv->children; l != NULL; l = l->next)
    {
      info = l->data;
      if (info->widget == child)
        return info;
    }

  return NULL;
}

static void
reorder_child (GtkStack  *stack,
               GtkWidget *child,
               gint       position)
{
604
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
Matthias Clasen's avatar
Matthias Clasen committed
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
  GList *l;
  GList *old_link = NULL;
  GList *new_link = NULL;
  GtkStackChildInfo *child_info = NULL;
  gint num = 0;

  l = priv->children;

  /* Loop to find the old position and link of child, new link of child and
   * total number of children. new_link will be NULL if the child should be
   * moved to the end (in case of position being < 0 || >= num)
   */
  while (l && (new_link == NULL || old_link == NULL))
    {
      /* Record the new position if found */
      if (position == num)
        new_link = l;

      if (old_link == NULL)
        {
          GtkStackChildInfo *info;
          info = l->data;

628
          /* Keep trying to find the current position and link location of the child */
Matthias Clasen's avatar
Matthias Clasen committed
629
630
631
632
633
634
635
          if (info->widget == child)
            {
              old_link = l;
              child_info = info;
            }
        }

636
      l = l->next;
Matthias Clasen's avatar
Matthias Clasen committed
637
638
639
640
641
      num++;
    }

  g_return_if_fail (old_link != NULL);

642
  if (old_link == new_link || (old_link->next == NULL && new_link == NULL))
Matthias Clasen's avatar
Matthias Clasen committed
643
644
645
646
647
    return;

  priv->children = g_list_delete_link (priv->children, old_link);
  priv->children = g_list_insert_before (priv->children, new_link, child_info);

648
  gtk_container_child_notify_by_pspec (GTK_CONTAINER (stack), child, stack_child_props[CHILD_PROP_POSITION]);
Matthias Clasen's avatar
Matthias Clasen committed
649
650
651
652
653
654
655
656
657
658
}

static void
gtk_stack_get_child_property (GtkContainer *container,
                              GtkWidget    *child,
                              guint         property_id,
                              GValue       *value,
                              GParamSpec   *pspec)
{
  GtkStack *stack = GTK_STACK (container);
659
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
Matthias Clasen's avatar
Matthias Clasen committed
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
  GtkStackChildInfo *info;

  info = find_child_info_for_widget (stack, child);
  if (info == NULL)
    {
      GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
      return;
    }

  switch (property_id)
    {
    case CHILD_PROP_NAME:
      g_value_set_string (value, info->name);
      break;

    case CHILD_PROP_TITLE:
      g_value_set_string (value, info->title);
      break;

    case CHILD_PROP_ICON_NAME:
      g_value_set_string (value, info->icon_name);
      break;

    case CHILD_PROP_POSITION:
684
      g_value_set_int (value, g_list_index (priv->children, info));
Matthias Clasen's avatar
Matthias Clasen committed
685
686
      break;

687
688
689
690
    case CHILD_PROP_NEEDS_ATTENTION:
      g_value_set_boolean (value, info->needs_attention);
      break;

Matthias Clasen's avatar
Matthias Clasen committed
691
692
693
694
695
696
697
698
699
700
701
702
703
704
    default:
      GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
      break;
    }
}

static void
gtk_stack_set_child_property (GtkContainer *container,
                              GtkWidget    *child,
                              guint         property_id,
                              const GValue *value,
                              GParamSpec   *pspec)
{
  GtkStack *stack = GTK_STACK (container);
705
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
Matthias Clasen's avatar
Matthias Clasen committed
706
  GtkStackChildInfo *info;
707
708
709
  GtkStackChildInfo *info2;
  gchar *name;
  GList *l;
Matthias Clasen's avatar
Matthias Clasen committed
710
711
712
713
714
715
716
717
718
719
720

  info = find_child_info_for_widget (stack, child);
  if (info == NULL)
    {
      GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
      return;
    }

  switch (property_id)
    {
    case CHILD_PROP_NAME:
721
722
723
724
      name = g_value_dup_string (value);
      for (l = priv->children; l != NULL; l = l->next)
        {
          info2 = l->data;
725
726
          if (info == info2)
            continue;
727
728
          if (g_strcmp0 (info2->name, name) == 0)
            {
729
              g_warning ("Duplicate child name in GtkStack: %s", name);
730
731
732
733
              break;
            }
        }

Matthias Clasen's avatar
Matthias Clasen committed
734
      g_free (info->name);
735
      info->name = name;
Matthias Clasen's avatar
Matthias Clasen committed
736

737
      gtk_container_child_notify_by_pspec (container, child, pspec);
Matthias Clasen's avatar
Matthias Clasen committed
738
739

      if (priv->visible_child == info)
740
741
        g_object_notify_by_pspec (G_OBJECT (stack),
                                  stack_props[PROP_VISIBLE_CHILD_NAME]);
Matthias Clasen's avatar
Matthias Clasen committed
742
743
744
745
746
747

      break;

    case CHILD_PROP_TITLE:
      g_free (info->title);
      info->title = g_value_dup_string (value);
748
      gtk_container_child_notify_by_pspec (container, child, pspec);
Matthias Clasen's avatar
Matthias Clasen committed
749
750
751
752
753
      break;

    case CHILD_PROP_ICON_NAME:
      g_free (info->icon_name);
      info->icon_name = g_value_dup_string (value);
754
      gtk_container_child_notify_by_pspec (container, child, pspec);
Matthias Clasen's avatar
Matthias Clasen committed
755
756
757
758
759
760
      break;

    case CHILD_PROP_POSITION:
      reorder_child (stack, child, g_value_get_int (value));
      break;

761
762
    case CHILD_PROP_NEEDS_ATTENTION:
      info->needs_attention = g_value_get_boolean (value);
763
      gtk_container_child_notify_by_pspec (container, child, pspec);
764
765
      break;

Matthias Clasen's avatar
Matthias Clasen committed
766
767
768
769
770
771
    default:
      GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec);
      break;
    }
}

772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
static inline gboolean
is_left_transition (GtkStackTransitionType transition_type)
{
  return (transition_type == GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT ||
          transition_type == GTK_STACK_TRANSITION_TYPE_OVER_LEFT);
}

static inline gboolean
is_right_transition (GtkStackTransitionType transition_type)
{
  return (transition_type == GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT ||
          transition_type == GTK_STACK_TRANSITION_TYPE_OVER_RIGHT);
}

static inline gboolean
is_up_transition (GtkStackTransitionType transition_type)
{
  return (transition_type == GTK_STACK_TRANSITION_TYPE_SLIDE_UP ||
          transition_type == GTK_STACK_TRANSITION_TYPE_OVER_UP);
}

static inline gboolean
is_down_transition (GtkStackTransitionType transition_type)
{
  return (transition_type == GTK_STACK_TRANSITION_TYPE_SLIDE_DOWN ||
          transition_type == GTK_STACK_TRANSITION_TYPE_OVER_DOWN);
}

/* Transitions that cause the bin window to move */
static inline gboolean
is_window_moving_transition (GtkStackTransitionType transition_type)
{
  return (transition_type == GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT ||
          transition_type == GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT ||
          transition_type == GTK_STACK_TRANSITION_TYPE_SLIDE_UP ||
          transition_type == GTK_STACK_TRANSITION_TYPE_SLIDE_DOWN ||
          transition_type == GTK_STACK_TRANSITION_TYPE_OVER_UP ||
          transition_type == GTK_STACK_TRANSITION_TYPE_OVER_DOWN ||
          transition_type == GTK_STACK_TRANSITION_TYPE_OVER_LEFT ||
          transition_type == GTK_STACK_TRANSITION_TYPE_OVER_RIGHT);
}

/* Transitions that change direction depending on the relative order of the
old and new child */
static inline gboolean
is_direction_dependent_transition (GtkStackTransitionType transition_type)
{
  return (transition_type == GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT ||
          transition_type == GTK_STACK_TRANSITION_TYPE_SLIDE_UP_DOWN ||
821
822
823
824
          transition_type == GTK_STACK_TRANSITION_TYPE_OVER_UP_DOWN ||
          transition_type == GTK_STACK_TRANSITION_TYPE_OVER_DOWN_UP ||
          transition_type == GTK_STACK_TRANSITION_TYPE_OVER_LEFT_RIGHT ||
          transition_type == GTK_STACK_TRANSITION_TYPE_OVER_RIGHT_LEFT);
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
}

/* Returns simple transition type for a direction dependent transition, given
whether the new child (the one being switched to) is first in the stacking order
(added earlier). */
static inline GtkStackTransitionType
get_simple_transition_type (gboolean               new_child_first,
                            GtkStackTransitionType transition_type)
{
  switch (transition_type)
    {
    case GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT:
      return new_child_first ? GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT : GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT;
    case GTK_STACK_TRANSITION_TYPE_SLIDE_UP_DOWN:
      return new_child_first ? GTK_STACK_TRANSITION_TYPE_SLIDE_DOWN : GTK_STACK_TRANSITION_TYPE_SLIDE_UP;
    case GTK_STACK_TRANSITION_TYPE_OVER_UP_DOWN:
      return new_child_first ? GTK_STACK_TRANSITION_TYPE_UNDER_DOWN : GTK_STACK_TRANSITION_TYPE_OVER_UP;
842
843
844
845
846
847
    case GTK_STACK_TRANSITION_TYPE_OVER_DOWN_UP:
      return new_child_first ? GTK_STACK_TRANSITION_TYPE_UNDER_UP : GTK_STACK_TRANSITION_TYPE_OVER_DOWN;
    case GTK_STACK_TRANSITION_TYPE_OVER_LEFT_RIGHT:
      return new_child_first ? GTK_STACK_TRANSITION_TYPE_UNDER_RIGHT : GTK_STACK_TRANSITION_TYPE_OVER_LEFT;
    case GTK_STACK_TRANSITION_TYPE_OVER_RIGHT_LEFT:
      return new_child_first ? GTK_STACK_TRANSITION_TYPE_UNDER_LEFT : GTK_STACK_TRANSITION_TYPE_OVER_RIGHT;
848
    default: ;
849
850
851
852
    }
  return transition_type;
}

Matthias Clasen's avatar
Matthias Clasen committed
853
static gint
Matthias Clasen's avatar
Matthias Clasen committed
854
855
get_bin_window_x (GtkStack            *stack,
                  const GtkAllocation *allocation)
Matthias Clasen's avatar
Matthias Clasen committed
856
{
857
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
Matthias Clasen's avatar
Matthias Clasen committed
858
859
  int x = 0;

Matt Watson's avatar
Matt Watson committed
860
  if (gtk_progress_tracker_get_state (&priv->tracker) != GTK_PROGRESS_STATE_AFTER)
Matthias Clasen's avatar
Matthias Clasen committed
861
    {
862
      if (is_left_transition (priv->active_transition_type))
Matt Watson's avatar
Matt Watson committed
863
        x = allocation->width * (1 - gtk_progress_tracker_get_ease_out_cubic (&priv->tracker, FALSE));
864
      if (is_right_transition (priv->active_transition_type))
Matt Watson's avatar
Matt Watson committed
865
        x = -allocation->width * (1 - gtk_progress_tracker_get_ease_out_cubic (&priv->tracker, FALSE));
Matthias Clasen's avatar
Matthias Clasen committed
866
867
868
869
870
    }

  return x;
}

871
static gint
Matthias Clasen's avatar
Matthias Clasen committed
872
873
get_bin_window_y (GtkStack            *stack,
                  const GtkAllocation *allocation)
874
{
875
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
876
877
  int y = 0;

Matt Watson's avatar
Matt Watson committed
878
  if (gtk_progress_tracker_get_state (&priv->tracker) != GTK_PROGRESS_STATE_AFTER)
879
    {
880
      if (is_up_transition (priv->active_transition_type))
Matt Watson's avatar
Matt Watson committed
881
        y = allocation->height * (1 - gtk_progress_tracker_get_ease_out_cubic (&priv->tracker, FALSE));
882
      if (is_down_transition(priv->active_transition_type))
Matt Watson's avatar
Matt Watson committed
883
        y = -allocation->height * (1 - gtk_progress_tracker_get_ease_out_cubic (&priv->tracker, FALSE));
884
885
886
887
888
    }

  return y;
}

Matt Watson's avatar
Matt Watson committed
889
890
static void
gtk_stack_progress_updated (GtkStack *stack)
Matthias Clasen's avatar
Matthias Clasen committed
891
{
892
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
Matthias Clasen's avatar
Matthias Clasen committed
893
894
895

  gtk_widget_queue_draw (GTK_WIDGET (stack));

896
897
898
  if (!priv->vhomogeneous || !priv->hhomogeneous)
    gtk_widget_queue_resize (GTK_WIDGET (stack));

Matthias Clasen's avatar
Matthias Clasen committed
899
  if (priv->bin_window != NULL &&
900
      is_window_moving_transition (priv->active_transition_type))
Matthias Clasen's avatar
Matthias Clasen committed
901
902
903
904
    {
      GtkAllocation allocation;
      gtk_widget_get_allocation (GTK_WIDGET (stack), &allocation);
      gdk_window_move (priv->bin_window,
905
                       get_bin_window_x (stack, &allocation), get_bin_window_y (stack, &allocation));
Matthias Clasen's avatar
Matthias Clasen committed
906
907
    }

Matt Watson's avatar
Matt Watson committed
908
  if (gtk_progress_tracker_get_state (&priv->tracker) == GTK_PROGRESS_STATE_AFTER)
Matthias Clasen's avatar
Matthias Clasen committed
909
910
911
912
913
914
915
    {
      if (priv->last_visible_surface != NULL)
        {
          cairo_surface_destroy (priv->last_visible_surface);
          priv->last_visible_surface = NULL;
        }

916
917
918
919
920
      if (priv->last_visible_child != NULL)
        {
          gtk_widget_set_child_visible (priv->last_visible_child->widget, FALSE);
          priv->last_visible_child = NULL;
        }
Matthias Clasen's avatar
Matthias Clasen committed
921
922
923
924
    }
}

static gboolean
925
gtk_stack_transition_cb (GtkWidget     *widget,
Matthias Clasen's avatar
Matthias Clasen committed
926
927
928
                         GdkFrameClock *frame_clock,
                         gpointer       user_data)
{
929
  GtkStack *stack = GTK_STACK (widget);
930
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
Matthias Clasen's avatar
Matthias Clasen committed
931

932
933
934
935
936
  if (priv->first_frame_skipped)
    gtk_progress_tracker_advance_frame (&priv->tracker,
                                        gdk_frame_clock_get_frame_time (frame_clock));
  else
    priv->first_frame_skipped = TRUE;
Matthias Clasen's avatar
Matthias Clasen committed
937
938

  /* Finish animation early if not mapped anymore */
Timm Bäder's avatar
Timm Bäder committed
939
  if (!gtk_widget_get_mapped (widget))
Matt Watson's avatar
Matt Watson committed
940
941
942
    gtk_progress_tracker_finish (&priv->tracker);

  gtk_stack_progress_updated (GTK_STACK (widget));
Matthias Clasen's avatar
Matthias Clasen committed
943

Matt Watson's avatar
Matt Watson committed
944
  if (gtk_progress_tracker_get_state (&priv->tracker) == GTK_PROGRESS_STATE_AFTER)
Matthias Clasen's avatar
Matthias Clasen committed
945
946
    {
      priv->tick_id = 0;
947
      g_object_notify_by_pspec (G_OBJECT (stack), stack_props[PROP_TRANSITION_RUNNING]);
Matthias Clasen's avatar
Matthias Clasen committed
948
949
950
951
952
953
954
955
956
957

      return FALSE;
    }

  return TRUE;
}

static void
gtk_stack_schedule_ticks (GtkStack *stack)
{
958
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
Matthias Clasen's avatar
Matthias Clasen committed
959
960
961
962

  if (priv->tick_id == 0)
    {
      priv->tick_id =
963
        gtk_widget_add_tick_callback (GTK_WIDGET (stack), gtk_stack_transition_cb, stack, NULL);
964
      g_object_notify_by_pspec (G_OBJECT (stack), stack_props[PROP_TRANSITION_RUNNING]);
Matthias Clasen's avatar
Matthias Clasen committed
965
966
967
968
969
970
    }
}

static void
gtk_stack_unschedule_ticks (GtkStack *stack)
{
971
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
Matthias Clasen's avatar
Matthias Clasen committed
972
973
974
975
976

  if (priv->tick_id != 0)
    {
      gtk_widget_remove_tick_callback (GTK_WIDGET (stack), priv->tick_id);
      priv->tick_id = 0;
977
      g_object_notify_by_pspec (G_OBJECT (stack), stack_props[PROP_TRANSITION_RUNNING]);
Matthias Clasen's avatar
Matthias Clasen committed
978
979
980
    }
}

981
982
983
984
985
986
static GtkStackTransitionType
effective_transition_type (GtkStack               *stack,
                           GtkStackTransitionType  transition_type)
{
  if (gtk_widget_get_direction (GTK_WIDGET (stack)) == GTK_TEXT_DIR_RTL)
    {
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
      switch (transition_type)
        {
        case GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT:
          return GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT;
        case GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT:
          return GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT;
        case GTK_STACK_TRANSITION_TYPE_OVER_LEFT:
          return GTK_STACK_TRANSITION_TYPE_OVER_RIGHT;
        case GTK_STACK_TRANSITION_TYPE_OVER_RIGHT:
          return GTK_STACK_TRANSITION_TYPE_OVER_LEFT;
        case GTK_STACK_TRANSITION_TYPE_UNDER_LEFT:
          return GTK_STACK_TRANSITION_TYPE_UNDER_RIGHT;
        case GTK_STACK_TRANSITION_TYPE_UNDER_RIGHT:
          return GTK_STACK_TRANSITION_TYPE_UNDER_LEFT;
1001
        default: ;
1002
        }
1003
1004
1005
1006
1007
    }

  return transition_type;
}

Matthias Clasen's avatar
Matthias Clasen committed
1008
static void
1009
1010
1011
gtk_stack_start_transition (GtkStack               *stack,
                            GtkStackTransitionType  transition_type,
                            guint                   transition_duration)
Matthias Clasen's avatar
Matthias Clasen committed
1012
{
1013
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
Matthias Clasen's avatar
Matthias Clasen committed
1014
1015
1016
  GtkWidget *widget = GTK_WIDGET (stack);

  if (gtk_widget_get_mapped (widget) &&
1017
      gtk_settings_get_enable_animations (gtk_widget_get_settings (widget)) &&
1018
1019
      transition_type != GTK_STACK_TRANSITION_TYPE_NONE &&
      transition_duration != 0 &&
Matthias Clasen's avatar
Matthias Clasen committed
1020
1021
      priv->last_visible_child != NULL)
    {
1022
      priv->active_transition_type = effective_transition_type (stack, transition_type);
1023
      priv->first_frame_skipped = FALSE;
Matthias Clasen's avatar
Matthias Clasen committed
1024
      gtk_stack_schedule_ticks (stack);
Matt Watson's avatar
Matt Watson committed
1025
1026
1027
1028
      gtk_progress_tracker_start (&priv->tracker,
                                  priv->transition_duration * 1000,
                                  0,
                                  1.0);
Matthias Clasen's avatar
Matthias Clasen committed
1029
1030
1031
1032
    }
  else
    {
      gtk_stack_unschedule_ticks (stack);
1033
      priv->active_transition_type = GTK_STACK_TRANSITION_TYPE_NONE;
Matt Watson's avatar
Matt Watson committed
1034
      gtk_progress_tracker_finish (&priv->tracker);
Matthias Clasen's avatar
Matthias Clasen committed
1035
    }
1036
1037

  gtk_stack_progress_updated (GTK_STACK (widget));
Matthias Clasen's avatar
Matthias Clasen committed
1038
1039
1040
}

static void
1041
1042
1043
1044
set_visible_child (GtkStack               *stack,
                   GtkStackChildInfo      *child_info,
                   GtkStackTransitionType  transition_type,
                   guint                   transition_duration)
Matthias Clasen's avatar
Matthias Clasen committed
1045
{
1046
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
Matthias Clasen's avatar
Matthias Clasen committed
1047
1048
1049
  GtkStackChildInfo *info;
  GtkWidget *widget = GTK_WIDGET (stack);
  GList *l;
1050
1051
1052
  GtkWidget *toplevel;
  GtkWidget *focus;
  gboolean contains_focus = FALSE;
Matthias Clasen's avatar
Matthias Clasen committed
1053

1054
  /* if we are being destroyed, do not bother with transitions
1055
1056
1057
   * and notifications
   */
  if (gtk_widget_in_destruction (widget))
1058
1059
    return;

Matthias Clasen's avatar
Matthias Clasen committed
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
  /* If none, pick first visible */
  if (child_info == NULL)
    {
      for (l = priv->children; l != NULL; l = l->next)
        {
          info = l->data;
          if (gtk_widget_get_visible (info->widget))
            {
              child_info = info;
              break;
            }
        }
    }

  if (child_info == priv->visible_child)
    return;

1077
1078
1079
1080
1081
1082
  toplevel = gtk_widget_get_toplevel (widget);
  if (GTK_IS_WINDOW (toplevel))
    {
      focus = gtk_window_get_focus (GTK_WINDOW (toplevel));
      if (focus &&
          priv->visible_child &&
1083
          priv->visible_child->widget &&
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
          gtk_widget_is_ancestor (focus, priv->visible_child->widget))
        {
          contains_focus = TRUE;

          if (priv->visible_child->last_focus)
            g_object_remove_weak_pointer (G_OBJECT (priv->visible_child->last_focus),
                                          (gpointer *)&priv->visible_child->last_focus);
          priv->visible_child->last_focus = focus;
          g_object_add_weak_pointer (G_OBJECT (priv->visible_child->last_focus),
                                     (gpointer *)&priv->visible_child->last_focus);
        }
    }

Matthias Clasen's avatar
Matthias Clasen committed
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
  if (priv->last_visible_child)
    gtk_widget_set_child_visible (priv->last_visible_child->widget, FALSE);
  priv->last_visible_child = NULL;

  if (priv->last_visible_surface != NULL)
    cairo_surface_destroy (priv->last_visible_surface);
  priv->last_visible_surface = NULL;

  if (priv->visible_child && priv->visible_child->widget)
    {
      if (gtk_widget_is_visible (widget))
1108
        {
1109
1110
          GtkAllocation allocation;

1111
          priv->last_visible_child = priv->visible_child;
1112
1113
1114
          gtk_widget_get_allocated_size (priv->last_visible_child->widget, &allocation, NULL);
          priv->last_visible_widget_width = allocation.width;
          priv->last_visible_widget_height = allocation.height;
1115
        }
Matthias Clasen's avatar
Matthias Clasen committed
1116
      else
1117
1118
1119
        {
          gtk_widget_set_child_visible (priv->visible_child->widget, FALSE);
        }
Matthias Clasen's avatar
Matthias Clasen committed
1120
1121
    }

Timm Bäder's avatar
Timm Bäder committed
1122
1123
1124
1125
  gtk_stack_accessible_update_visible_child (stack,
                                             priv->visible_child ? priv->visible_child->widget : NULL,
                                             child_info ? child_info->widget : NULL);

Matthias Clasen's avatar
Matthias Clasen committed
1126
1127
1128
  priv->visible_child = child_info;

  if (child_info)
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
    {
      gtk_widget_set_child_visible (child_info->widget, TRUE);

      if (contains_focus)
        {
          if (child_info->last_focus)
            gtk_widget_grab_focus (child_info->last_focus);
          else
            gtk_widget_child_focus (child_info->widget, GTK_DIR_TAB_FORWARD);
        }
    }
Matthias Clasen's avatar
Matthias Clasen committed
1140

1141
  if ((child_info == NULL || priv->last_visible_child == NULL) &&
1142
      is_direction_dependent_transition (transition_type))
1143
1144
1145
    {
      transition_type = GTK_STACK_TRANSITION_TYPE_NONE;
    }
1146
  else if (is_direction_dependent_transition (transition_type))
1147
1148
    {
      gboolean i_first = FALSE;
1149
      for (l = priv->children; l != NULL; l = l->next)
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
        {
	  if (child_info == l->data)
	    {
	      i_first = TRUE;
	      break;
	    }
	  if (priv->last_visible_child == l->data)
	    break;
        }

1160
      transition_type = get_simple_transition_type (i_first, transition_type);
1161
1162
    }

1163
1164
1165
1166
1167
  if (priv->hhomogeneous && priv->vhomogeneous)
    gtk_widget_queue_allocate (widget);
  else
    gtk_widget_queue_resize (widget);

1168
1169
1170
  g_object_notify_by_pspec (G_OBJECT (stack), stack_props[PROP_VISIBLE_CHILD]);
  g_object_notify_by_pspec (G_OBJECT (stack),
                            stack_props[PROP_VISIBLE_CHILD_NAME]);
Matthias Clasen's avatar
Matthias Clasen committed
1171

1172
  gtk_stack_start_transition (stack, transition_type, transition_duration);
Matthias Clasen's avatar
Matthias Clasen committed
1173
1174
1175
1176
1177
1178
1179
1180
}

static void
stack_child_visibility_notify_cb (GObject    *obj,
                                  GParamSpec *pspec,
                                  gpointer    user_data)
{
  GtkStack *stack = GTK_STACK (user_data);
1181
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
Matthias Clasen's avatar
Matthias Clasen committed
1182
1183
1184
1185
1186
1187
1188
  GtkWidget *child = GTK_WIDGET (obj);
  GtkStackChildInfo *child_info;

  child_info = find_child_info_for_widget (stack, child);

  if (priv->visible_child == NULL &&
      gtk_widget_get_visible (child))
1189
    set_visible_child (stack, child_info, priv->transition_type, priv->transition_duration);
Matthias Clasen's avatar
Matthias Clasen committed
1190
1191
  else if (priv->visible_child == child_info &&
           !gtk_widget_get_visible (child))
1192
    set_visible_child (stack, NULL, priv->transition_type, priv->transition_duration);
Matthias Clasen's avatar
Matthias Clasen committed
1193
1194
1195
1196
1197
1198
1199
1200

  if (child_info == priv->last_visible_child)
    {
      gtk_widget_set_child_visible (priv->last_visible_child->widget, FALSE);
      priv->last_visible_child = NULL;
    }
}

Matthias Clasen's avatar
Matthias Clasen committed
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
/**
 * gtk_stack_add_titled:
 * @stack: a #GtkStack
 * @child: the widget to add
 * @name: the name for @child
 * @title: a human-readable title for @child
 *
 * Adds a child to @stack.
 * The child is identified by the @name. The @title
 * will be used by #GtkStackSwitcher to represent
 * @child in a tab bar, so it should be short.
 *
 * Since: 3.10
 */
Matthias Clasen's avatar
Matthias Clasen committed
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
void
gtk_stack_add_titled (GtkStack   *stack,
                     GtkWidget   *child,
                     const gchar *name,
                     const gchar *title)
{
  g_return_if_fail (GTK_IS_STACK (stack));
  g_return_if_fail (GTK_IS_WIDGET (child));

  gtk_container_add_with_properties (GTK_CONTAINER (stack),
                                     child,
                                     "name", name,
                                     "title", title,
                                     NULL);
}

Matthias Clasen's avatar
Matthias Clasen committed
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
/**
 * gtk_stack_add_named:
 * @stack: a #GtkStack
 * @child: the widget to add
 * @name: the name for @child
 *
 * Adds a child to @stack.
 * The child is identified by the @name.
 *
 * Since: 3.10
 */
Matthias Clasen's avatar
Matthias Clasen committed
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
void
gtk_stack_add_named (GtkStack   *stack,
                    GtkWidget   *child,
                    const gchar *name)
{
  g_return_if_fail (GTK_IS_STACK (stack));
  g_return_if_fail (GTK_IS_WIDGET (child));

  gtk_container_add_with_properties (GTK_CONTAINER (stack),
                                     child,
                                     "name", name,
                                     NULL);
}

static void
gtk_stack_add (GtkContainer *container,
Timm Bäder's avatar
Timm Bäder committed
1258
               GtkWidget    *child)
Matthias Clasen's avatar
Matthias Clasen committed
1259
1260
{
  GtkStack *stack = GTK_STACK (container);
1261
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
Matthias Clasen's avatar
Matthias Clasen committed
1262
1263
1264
1265
1266
1267
1268
1269
1270
  GtkStackChildInfo *child_info;

  g_return_if_fail (child != NULL);

  child_info = g_slice_new (GtkStackChildInfo);
  child_info->widget = child;
  child_info->name = NULL;
  child_info->title = NULL;
  child_info->icon_name = NULL;
1271
  child_info->needs_attention = FALSE;
1272
  child_info->last_focus = NULL;
Matthias Clasen's avatar
Matthias Clasen committed
1273
1274
1275

  priv->children = g_list_append (priv->children, child_info);

1276
  gtk_widget_set_child_visible (child, FALSE);
Matthias Clasen's avatar
Matthias Clasen committed
1277
1278
1279
  gtk_widget_set_parent_window (child, priv->bin_window);
  gtk_widget_set_parent (child, GTK_WIDGET (stack));

1280
1281
1282
1283
1284
  if (priv->bin_window)
    gdk_window_set_events (priv->bin_window,
                           gdk_window_get_events (priv->bin_window) |
                           gtk_widget_get_events (child));

Matthias Clasen's avatar
Matthias Clasen committed
1285
1286
1287
  g_signal_connect (child, "notify::visible",
                    G_CALLBACK (stack_child_visibility_notify_cb), stack);

1288
  gtk_container_child_notify_by_pspec (container, child, stack_child_props[CHILD_PROP_POSITION]);
Matthias Clasen's avatar
Matthias Clasen committed
1289
1290
1291

  if (priv->visible_child == NULL &&
      gtk_widget_get_visible (child))
1292
    set_visible_child (stack, child_info, priv->transition_type, priv->transition_duration);
Matthias Clasen's avatar
Matthias Clasen committed
1293

Matthias Clasen's avatar
Matthias Clasen committed
1294
  if (priv->hhomogeneous || priv->vhomogeneous || priv->visible_child == child_info)
Matthias Clasen's avatar
Matthias Clasen committed
1295
1296
1297
1298
1299
1300
1301
1302
    gtk_widget_queue_resize (GTK_WIDGET (stack));
}

static void
gtk_stack_remove (GtkContainer *container,
                  GtkWidget    *child)
{
  GtkStack *stack = GTK_STACK (container);
1303
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
Matthias Clasen's avatar
Matthias Clasen committed
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
  GtkStackChildInfo *child_info;
  gboolean was_visible;

  child_info = find_child_info_for_widget (stack, child);
  if (child_info == NULL)
    return;

  priv->children = g_list_remove (priv->children, child_info);

  g_signal_handlers_disconnect_by_func (child,
                                        stack_child_visibility_notify_cb,
                                        stack);

  was_visible = gtk_widget_get_visible (child);

  child_info->widget = NULL;

  if (priv->visible_child == child_info)
1322
    set_visible_child (stack, NULL, priv->transition_type, priv->transition_duration);
Matthias Clasen's avatar
Matthias Clasen committed
1323
1324
1325
1326
1327
1328
1329
1330
1331

  if (priv->last_visible_child == child_info)
    priv->last_visible_child = NULL;

  gtk_widget_unparent (child);

  g_free (child_info->name);
  g_free (child_info->title);
  g_free (child_info->icon_name);
1332
1333
1334
1335
1336

  if (child_info->last_focus)
    g_object_remove_weak_pointer (G_OBJECT (child_info->last_focus),
                                  (gpointer *)&child_info->last_focus);

Matthias Clasen's avatar
Matthias Clasen committed
1337
1338
  g_slice_free (GtkStackChildInfo, child_info);

Matthias Clasen's avatar
Matthias Clasen committed
1339
  if ((priv->hhomogeneous || priv->vhomogeneous) && was_visible)
Matthias Clasen's avatar
Matthias Clasen committed
1340
1341
1342
    gtk_widget_queue_resize (GTK_WIDGET (stack));
}

1343
1344
1345
1346
1347
1348
1349
1350
1351
/**
 * gtk_stack_get_child_by_name:
 * @stack: a #GtkStack
 * @name: the name of the child to find
 *
 * Finds the child of the #GtkStack with the name given as
 * the argument. Returns %NULL if there is no child with this
 * name.
 *
1352
 * Returns: (transfer none) (nullable): the requested child of the #GtkStack
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
 *
 * Since: 3.12
 */
GtkWidget *
gtk_stack_get_child_by_name (GtkStack    *stack,
                             const gchar *name)
{
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
  GtkStackChildInfo *info;
  GList *l;

  g_return_val_if_fail (GTK_IS_STACK (stack), NULL);
  g_return_val_if_fail (name != NULL, NULL);

  for (l = priv->children; l != NULL; l = l->next)
    {
      info = l->data;
      if (info->name && strcmp (info->name, name) == 0)
        return info->widget;
    }

  return NULL;
}

Matthias Clasen's avatar
Matthias Clasen committed
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
/**
 * gtk_stack_set_homogeneous:
 * @stack: a #GtkStack
 * @homogeneous: %TRUE to make @stack homogeneous
 *
 * Sets the #GtkStack to be homogeneous or not. If it
 * is homogeneous, the #GtkStack will request the same
 * size for all its children. If it isn't, the stack
 * may change size when a different child becomes visible.
 *
Matthias Clasen's avatar
Matthias Clasen committed
1387
1388
1389
1390
 * Since 3.16, homogeneity can be controlled separately
 * for horizontal and vertical size, with the
 * #GtkStack:hhomogeneous and #GtkStack:vhomogeneous.
 *
Matthias Clasen's avatar
Matthias Clasen committed
1391
1392
 * Since: 3.10
 */
Matthias Clasen's avatar
Matthias Clasen committed
1393
1394
1395
1396
void
gtk_stack_set_homogeneous (GtkStack *stack,
                           gboolean  homogeneous)
{
1397
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);
Matthias Clasen's avatar
Matthias Clasen committed
1398
1399
1400
1401
1402

  g_return_if_fail (GTK_IS_STACK (stack));

  homogeneous = !!homogeneous;

Matthias Clasen's avatar
Matthias Clasen committed
1403
  if ((priv->hhomogeneous && priv->vhomogeneous) == homogeneous)
Matthias Clasen's avatar
Matthias Clasen committed
1404
1405
    return;

Matthias Clasen's avatar
Matthias Clasen committed
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
  g_object_freeze_notify (G_OBJECT (stack));

  if (priv->hhomogeneous != homogeneous)
    {
      priv->hhomogeneous = homogeneous;
      g_object_notify_by_pspec (G_OBJECT (stack), stack_props[PROP_HHOMOGENEOUS]);
    }

  if (priv->vhomogeneous != homogeneous)
    {
      priv->vhomogeneous = homogeneous;
      g_object_notify_by_pspec (G_OBJECT (stack), stack_props[PROP_VHOMOGENEOUS]);
    }
Matthias Clasen's avatar
Matthias Clasen committed
1419
1420
1421
1422

  if (gtk_widget_get_visible (GTK_WIDGET(stack)))
    gtk_widget_queue_resize (GTK_WIDGET (stack));

1423
  g_object_notify_by_pspec (G_OBJECT (stack), stack_props[PROP_HOMOGENEOUS]);
Matthias Clasen's avatar
Matthias Clasen committed
1424
  g_object_thaw_notify (G_OBJECT (stack));
Matthias Clasen's avatar
Matthias Clasen committed
1425
1426
}

Matthias Clasen's avatar
Matthias Clasen committed
1427
1428
1429
1430
1431
1432
1433
/**
 * gtk_stack_get_homogeneous:
 * @stack: a #GtkStack
 *
 * Gets whether @stack is homogeneous.
 * See gtk_stack_set_homogeneous().
 *
1434
 * Returns: whether @stack is homogeneous.
Matthias Clasen's avatar
Matthias Clasen committed
1435
1436
1437
 *
 * Since: 3.10
 */
Matthias Clasen's avatar
Matthias Clasen committed
1438
1439
1440
gboolean
gtk_stack_get_homogeneous (GtkStack *stack)
{
1441
1442
  GtkStackPrivate *priv = gtk_stack_get_instance_private (stack);

Matthias Clasen's avatar
Matthias Clasen committed
1443
1444
  g_return_val_if_fail (GTK_IS_STACK (stack), FALSE);

Matthias Clasen's avatar
Matthias Clasen committed
1445
1446
1447
1448
1449
1450
1451
1452
1453