gtklistbox.c 119 KB
Newer Older
Alexander Larsson's avatar
Alexander Larsson committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/*
 * Copyright (C) 2012 Alexander Larsson <alexl@redhat.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 */

#include "config.h"

20
#include "gtkadjustmentprivate.h"
21
#include "gtkcssnodeprivate.h"
Matthias Clasen's avatar
Matthias Clasen committed
22 23 24 25 26
#include "gtklistbox.h"
#include "gtkwidget.h"
#include "gtkmarshalers.h"
#include "gtkprivate.h"
#include "gtkintl.h"
Timm Bäder's avatar
Timm Bäder committed
27
#include "gtkwidgetprivate.h"
Matthias Clasen's avatar
Matthias Clasen committed
28

Alexander Larsson's avatar
Alexander Larsson committed
29 30 31 32 33
#include <float.h>
#include <math.h>
#include <string.h>

#include "a11y/gtklistboxaccessibleprivate.h"
34
#include "a11y/gtklistboxrowaccessible.h"
Alexander Larsson's avatar
Alexander Larsson committed
35 36 37 38 39

/**
 * SECTION:gtklistbox
 * @Short_description: A list container
 * @Title: GtkListBox
Alexander Larsson's avatar
Alexander Larsson committed
40
 * @See_also: #GtkScrolledWindow
Alexander Larsson's avatar
Alexander Larsson committed
41
 *
Alexander Larsson's avatar
Alexander Larsson committed
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
 * A GtkListBox is a vertical container that contains GtkListBoxRow
 * children. These rows can by dynamically sorted and filtered, and
 * headers can be added dynamically depending on the row content.
 * It also allows keyboard and mouse navigation and selection like
 * a typical list.
 *
 * Using GtkListBox is often an alternative to #GtkTreeView, especially
 * when the list contents has a more complicated layout than what is allowed
 * by a #GtkCellRenderer, or when the contents is interactive (i.e. has a
 * button in it).
 *
 * Although a #GtkListBox must have only #GtkListBoxRow children you can
 * add any kind of widget to it via gtk_container_add(), and a #GtkListBoxRow
 * widget will automatically be inserted between the list and the widget.
 *
57 58 59 60 61
 * #GtkListBoxRows can be marked as activatable or selectable. If a row
 * is activatable, #GtkListBox::row-activated will be emitted for it when
 * the user tries to activate it. If it is selectable, the row will be marked
 * as selected when the user tries to select it.
 *
Alexander Larsson's avatar
Alexander Larsson committed
62
 * The GtkListBox widget was added in GTK+ 3.10.
Matthias Clasen's avatar
Matthias Clasen committed
63
 */
Alexander Larsson's avatar
Alexander Larsson committed
64

65
typedef struct
Alexander Larsson's avatar
Alexander Larsson committed
66 67
{
  GSequence *children;
68
  GHashTable *header_hash;
Alexander Larsson's avatar
Alexander Larsson committed
69

70 71
  GtkWidget *placeholder;

Alexander Larsson's avatar
Alexander Larsson committed
72 73 74 75 76 77 78 79
  GtkListBoxSortFunc sort_func;
  gpointer sort_func_target;
  GDestroyNotify sort_func_target_destroy_notify;

  GtkListBoxFilterFunc filter_func;
  gpointer filter_func_target;
  GDestroyNotify filter_func_target_destroy_notify;

80 81 82
  GtkListBoxUpdateHeaderFunc update_header_func;
  gpointer update_header_func_target;
  GDestroyNotify update_header_func_target_destroy_notify;
Alexander Larsson's avatar
Alexander Larsson committed
83 84 85 86 87 88 89 90 91 92 93 94 95

  GtkListBoxRow *selected_row;
  GtkListBoxRow *prelight_row;
  GtkListBoxRow *cursor_row;

  gboolean active_row_active;
  GtkListBoxRow *active_row;

  GtkSelectionMode selection_mode;

  GtkAdjustment *adjustment;
  gboolean activate_single_click;

96 97
  GtkGesture *multipress_gesture;

Alexander Larsson's avatar
Alexander Larsson committed
98 99
  /* DnD */
  GtkListBoxRow *drag_highlighted_row;
100 101

  int n_visible_rows;
102
  gboolean in_widget;
103 104 105 106 107

  GListModel *bound_model;
  GtkListBoxCreateWidgetFunc create_widget_func;
  gpointer create_widget_func_data;
  GDestroyNotify create_widget_func_data_destroy;
108
} GtkListBoxPrivate;
Alexander Larsson's avatar
Alexander Larsson committed
109

110
typedef struct
Alexander Larsson's avatar
Alexander Larsson committed
111 112
{
  GSequenceIter *iter;
113
  GtkWidget *header;
Alexander Larsson's avatar
Alexander Larsson committed
114 115
  gint y;
  gint height;
116 117 118
  guint visible     :1;
  guint selected    :1;
  guint activatable :1;
119
  guint selectable  :1;
120
} GtkListBoxRowPrivate;
Alexander Larsson's avatar
Alexander Larsson committed
121 122 123 124 125 126 127

enum {
  ROW_SELECTED,
  ROW_ACTIVATED,
  ACTIVATE_CURSOR_ROW,
  TOGGLE_CURSOR_ROW,
  MOVE_CURSOR,
128 129 130
  SELECTED_ROWS_CHANGED,
  SELECT_ALL,
  UNSELECT_ALL,
Alexander Larsson's avatar
Alexander Larsson committed
131 132 133
  LAST_SIGNAL
};

134 135 136 137 138
enum {
  ROW__ACTIVATE,
  ROW__LAST_SIGNAL
};

139
enum {
Alexander Larsson's avatar
Alexander Larsson committed
140 141 142 143 144 145
  PROP_0,
  PROP_SELECTION_MODE,
  PROP_ACTIVATE_ON_SINGLE_CLICK,
  LAST_PROPERTY
};

146 147 148
enum {
  ROW_PROP_0,
  ROW_PROP_ACTIVATABLE,
149
  ROW_PROP_SELECTABLE,
150 151 152
  LAST_ROW_PROPERTY
};

153 154 155
#define BOX_PRIV(box) ((GtkListBoxPrivate*)gtk_list_box_get_instance_private ((GtkListBox*)(box)))
#define ROW_PRIV(row) ((GtkListBoxRowPrivate*)gtk_list_box_row_get_instance_private ((GtkListBoxRow*)(row)))

156 157 158 159 160 161
static void     gtk_list_box_buildable_interface_init     (GtkBuildableIface *iface);

G_DEFINE_TYPE_WITH_CODE (GtkListBox, gtk_list_box, GTK_TYPE_CONTAINER,
                         G_ADD_PRIVATE (GtkListBox)
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
                                                gtk_list_box_buildable_interface_init))
162
G_DEFINE_TYPE_WITH_PRIVATE (GtkListBoxRow, gtk_list_box_row, GTK_TYPE_BIN)
Alexander Larsson's avatar
Alexander Larsson committed
163

164 165
static void                 gtk_list_box_apply_filter_all             (GtkListBox          *box);
static void                 gtk_list_box_update_header                (GtkListBox          *box,
Alexander Larsson's avatar
Alexander Larsson committed
166
                                                                       GSequenceIter       *iter);
167 168 169
static GSequenceIter *      gtk_list_box_get_next_visible             (GtkListBox          *box,
                                                                       GSequenceIter       *iter);
static void                 gtk_list_box_apply_filter                 (GtkListBox          *box,
Alexander Larsson's avatar
Alexander Larsson committed
170 171 172 173 174 175
                                                                       GtkListBoxRow       *row);
static void                 gtk_list_box_add_move_binding             (GtkBindingSet       *binding_set,
                                                                       guint                keyval,
                                                                       GdkModifierType      modmask,
                                                                       GtkMovementStep      step,
                                                                       gint                 count);
176
static void                 gtk_list_box_update_cursor                (GtkListBox          *box,
177 178
                                                                       GtkListBoxRow       *row,
                                                                       gboolean             grab_focus);
179
static void                 gtk_list_box_select_and_activate          (GtkListBox          *box,
Alexander Larsson's avatar
Alexander Larsson committed
180
                                                                       GtkListBoxRow       *row);
181
static void                 gtk_list_box_update_prelight              (GtkListBox          *box,
Alexander Larsson's avatar
Alexander Larsson committed
182
                                                                       GtkListBoxRow       *row);
183
static void                 gtk_list_box_update_active                (GtkListBox          *box,
Alexander Larsson's avatar
Alexander Larsson committed
184
                                                                       GtkListBoxRow       *row);
185
static gboolean             gtk_list_box_enter_notify_event           (GtkWidget           *widget,
Alexander Larsson's avatar
Alexander Larsson committed
186
                                                                       GdkEventCrossing    *event);
187
static gboolean             gtk_list_box_leave_notify_event           (GtkWidget           *widget,
Alexander Larsson's avatar
Alexander Larsson committed
188
                                                                       GdkEventCrossing    *event);
189
static gboolean             gtk_list_box_motion_notify_event          (GtkWidget           *widget,
Alexander Larsson's avatar
Alexander Larsson committed
190
                                                                       GdkEventMotion      *event);
191 192
static void                 gtk_list_box_show                         (GtkWidget           *widget);
static gboolean             gtk_list_box_focus                        (GtkWidget           *widget,
Alexander Larsson's avatar
Alexander Larsson committed
193
                                                                       GtkDirectionType     direction);
194 195 196 197
static GSequenceIter*       gtk_list_box_get_previous_visible         (GtkListBox          *box,
                                                                       GSequenceIter       *iter);
static GtkListBoxRow       *gtk_list_box_get_first_focusable          (GtkListBox          *box);
static GtkListBoxRow       *gtk_list_box_get_last_focusable           (GtkListBox          *box);
198
static gboolean             gtk_list_box_draw                         (GtkWidget           *widget,
Alexander Larsson's avatar
Alexander Larsson committed
199
                                                                       cairo_t             *cr);
200 201
static void                 gtk_list_box_realize                      (GtkWidget           *widget);
static void                 gtk_list_box_add                          (GtkContainer        *container,
Alexander Larsson's avatar
Alexander Larsson committed
202
                                                                       GtkWidget           *widget);
203
static void                 gtk_list_box_remove                       (GtkContainer        *container,
Alexander Larsson's avatar
Alexander Larsson committed
204
                                                                       GtkWidget           *widget);
205
static void                 gtk_list_box_forall                       (GtkContainer        *container,
Alexander Larsson's avatar
Alexander Larsson committed
206 207
                                                                       gboolean             include_internals,
                                                                       GtkCallback          callback,
Matthias Clasen's avatar
Matthias Clasen committed
208
                                                                       gpointer             callback_target);
209
static void                 gtk_list_box_compute_expand               (GtkWidget           *widget,
Alexander Larsson's avatar
Alexander Larsson committed
210 211
                                                                       gboolean            *hexpand,
                                                                       gboolean            *vexpand);
212 213 214
static GType                gtk_list_box_child_type                   (GtkContainer        *container);
static GtkSizeRequestMode   gtk_list_box_get_request_mode             (GtkWidget           *widget);
static void                 gtk_list_box_size_allocate                (GtkWidget           *widget,
Alexander Larsson's avatar
Alexander Larsson committed
215
                                                                       GtkAllocation       *allocation);
216
static void                 gtk_list_box_drag_leave                   (GtkWidget           *widget,
Alexander Larsson's avatar
Alexander Larsson committed
217 218
                                                                       GdkDragContext      *context,
                                                                       guint                time_);
219 220 221
static void                 gtk_list_box_activate_cursor_row          (GtkListBox          *box);
static void                 gtk_list_box_toggle_cursor_row            (GtkListBox          *box);
static void                 gtk_list_box_move_cursor                  (GtkListBox          *box,
Alexander Larsson's avatar
Alexander Larsson committed
222 223 224
                                                                       GtkMovementStep      step,
                                                                       gint                 count);
static void                 gtk_list_box_finalize                     (GObject             *obj);
225
static void                 gtk_list_box_parent_set                   (GtkWidget           *widget,
226
                                                                       GtkWidget           *prev_parent);
Alexander Larsson's avatar
Alexander Larsson committed
227

228 229 230 231 232 233 234 235 236 237 238 239 240 241
static void                 gtk_list_box_get_preferred_height           (GtkWidget           *widget,
                                                                         gint                *minimum_height,
                                                                         gint                *natural_height);
static void                 gtk_list_box_get_preferred_height_for_width (GtkWidget           *widget,
                                                                         gint                 width,
                                                                         gint                *minimum_height,
                                                                         gint                *natural_height);
static void                 gtk_list_box_get_preferred_width            (GtkWidget           *widget,
                                                                         gint                *minimum_width,
                                                                         gint                *natural_width);
static void                 gtk_list_box_get_preferred_width_for_height (GtkWidget           *widget,
                                                                         gint                 height,
                                                                         gint                *minimum_width,
                                                                         gint                *natural_width);
Alexander Larsson's avatar
Alexander Larsson committed
242

243 244 245 246 247 248 249 250 251
static void                 gtk_list_box_select_row_internal            (GtkListBox          *box,
                                                                         GtkListBoxRow       *row);
static void                 gtk_list_box_unselect_row_internal          (GtkListBox          *box,
                                                                         GtkListBoxRow       *row);
static void                 gtk_list_box_select_all_between             (GtkListBox          *box,
                                                                         GtkListBoxRow       *row1,
                                                                         GtkListBoxRow       *row2,
                                                                         gboolean             modify);
static gboolean             gtk_list_box_unselect_all_internal          (GtkListBox          *box);
252
static void                 gtk_list_box_selected_rows_changed          (GtkListBox          *box);
253

254 255 256 257 258 259 260 261 262 263 264
static void gtk_list_box_multipress_gesture_pressed  (GtkGestureMultiPress *gesture,
                                                      guint                 n_press,
                                                      gdouble               x,
                                                      gdouble               y,
                                                      GtkListBox           *box);
static void gtk_list_box_multipress_gesture_released (GtkGestureMultiPress *gesture,
                                                      guint                 n_press,
                                                      gdouble               x,
                                                      gdouble               y,
                                                      GtkListBox           *box);

265 266 267
static void gtk_list_box_update_row_styles (GtkListBox    *box);
static void gtk_list_box_update_row_style  (GtkListBox    *box,
                                            GtkListBoxRow *row);
268

269 270 271 272 273 274
static void                 gtk_list_box_bound_model_changed            (GListModel          *list,
                                                                         guint                position,
                                                                         guint                removed,
                                                                         guint                added,
                                                                         gpointer             user_data);

275
static void                 gtk_list_box_check_model_compat             (GtkListBox          *box);
Alexander Larsson's avatar
Alexander Larsson committed
276 277
static GParamSpec *properties[LAST_PROPERTY] = { NULL, };
static guint signals[LAST_SIGNAL] = { 0 };
278
static GParamSpec *row_properties[LAST_ROW_PROPERTY] = { NULL, };
279
static guint row_signals[ROW__LAST_SIGNAL] = { 0 };
Alexander Larsson's avatar
Alexander Larsson committed
280

Alexander Larsson's avatar
Alexander Larsson committed
281 282 283 284 285 286 287 288 289
/**
 * gtk_list_box_new:
 *
 * Creates a new #GtkListBox container.
 *
 * Returns: a new #GtkListBox
 *
 * Since: 3.10
 */
Alexander Larsson's avatar
Alexander Larsson committed
290 291 292 293 294 295 296
GtkWidget *
gtk_list_box_new (void)
{
  return g_object_new (GTK_TYPE_LIST_BOX, NULL);
}

static void
297
gtk_list_box_init (GtkListBox *box)
Alexander Larsson's avatar
Alexander Larsson committed
298
{
299 300
  GtkListBoxPrivate *priv = BOX_PRIV (box);
  GtkWidget *widget = GTK_WIDGET (box);
301
  GtkStyleContext *context;
Alexander Larsson's avatar
Alexander Larsson committed
302

303 304
  gtk_widget_set_has_window (widget, TRUE);
  gtk_widget_set_redraw_on_allocate (widget, TRUE);
Alexander Larsson's avatar
Alexander Larsson committed
305 306 307 308
  priv->selection_mode = GTK_SELECTION_SINGLE;
  priv->activate_single_click = TRUE;

  priv->children = g_sequence_new (NULL);
309
  priv->header_hash = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL);
310

311
  context = gtk_widget_get_style_context (widget);
312
  gtk_style_context_add_class (context, GTK_STYLE_CLASS_LIST);
313 314 315 316 317 318 319 320 321 322 323 324

  priv->multipress_gesture = gtk_gesture_multi_press_new (widget);
  gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (priv->multipress_gesture),
                                              GTK_PHASE_BUBBLE);
  gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (priv->multipress_gesture),
                                     FALSE);
  gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (priv->multipress_gesture),
                                 GDK_BUTTON_PRIMARY);
  g_signal_connect (priv->multipress_gesture, "pressed",
                    G_CALLBACK (gtk_list_box_multipress_gesture_pressed), box);
  g_signal_connect (priv->multipress_gesture, "released",
                    G_CALLBACK (gtk_list_box_multipress_gesture_released), box);
Alexander Larsson's avatar
Alexander Larsson committed
325 326 327 328 329 330 331 332
}

static void
gtk_list_box_get_property (GObject    *obj,
                           guint       property_id,
                           GValue     *value,
                           GParamSpec *pspec)
{
333
  GtkListBoxPrivate *priv = BOX_PRIV (obj);
Alexander Larsson's avatar
Alexander Larsson committed
334 335 336 337

  switch (property_id)
    {
    case PROP_SELECTION_MODE:
338
      g_value_set_enum (value, priv->selection_mode);
Alexander Larsson's avatar
Alexander Larsson committed
339 340
      break;
    case PROP_ACTIVATE_ON_SINGLE_CLICK:
341
      g_value_set_boolean (value, priv->activate_single_click);
Alexander Larsson's avatar
Alexander Larsson committed
342 343 344 345 346 347 348 349 350 351 352 353 354
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec);
      break;
    }
}

static void
gtk_list_box_set_property (GObject      *obj,
                           guint         property_id,
                           const GValue *value,
                           GParamSpec   *pspec)
{
355
  GtkListBox *box = GTK_LIST_BOX (obj);
Alexander Larsson's avatar
Alexander Larsson committed
356 357 358 359

  switch (property_id)
    {
    case PROP_SELECTION_MODE:
360
      gtk_list_box_set_selection_mode (box, g_value_get_enum (value));
Alexander Larsson's avatar
Alexander Larsson committed
361 362
      break;
    case PROP_ACTIVATE_ON_SINGLE_CLICK:
363
      gtk_list_box_set_activate_on_single_click (box, g_value_get_boolean (value));
Alexander Larsson's avatar
Alexander Larsson committed
364 365 366 367 368 369 370 371 372 373
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec);
      break;
    }
}

static void
gtk_list_box_finalize (GObject *obj)
{
374
  GtkListBoxPrivate *priv = BOX_PRIV (obj);
Alexander Larsson's avatar
Alexander Larsson committed
375 376 377 378 379

  if (priv->sort_func_target_destroy_notify != NULL)
    priv->sort_func_target_destroy_notify (priv->sort_func_target);
  if (priv->filter_func_target_destroy_notify != NULL)
    priv->filter_func_target_destroy_notify (priv->filter_func_target);
380 381
  if (priv->update_header_func_target_destroy_notify != NULL)
    priv->update_header_func_target_destroy_notify (priv->update_header_func_target);
Alexander Larsson's avatar
Alexander Larsson committed
382 383 384

  g_clear_object (&priv->adjustment);
  g_clear_object (&priv->drag_highlighted_row);
385
  g_clear_object (&priv->multipress_gesture);
Alexander Larsson's avatar
Alexander Larsson committed
386 387

  g_sequence_free (priv->children);
388
  g_hash_table_unref (priv->header_hash);
Alexander Larsson's avatar
Alexander Larsson committed
389

390 391 392 393 394 395 396 397 398
  if (priv->bound_model)
    {
      if (priv->create_widget_func_data_destroy)
        priv->create_widget_func_data_destroy (priv->create_widget_func_data);

      g_signal_handlers_disconnect_by_func (priv->bound_model, gtk_list_box_bound_model_changed, obj);
      g_clear_object (&priv->bound_model);
    }

Alexander Larsson's avatar
Alexander Larsson committed
399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414
  G_OBJECT_CLASS (gtk_list_box_parent_class)->finalize (obj);
}

static void
gtk_list_box_class_init (GtkListBoxClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
  GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass);
  GtkBindingSet *binding_set;

  gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_LIST_BOX_ACCESSIBLE);

  object_class->get_property = gtk_list_box_get_property;
  object_class->set_property = gtk_list_box_set_property;
  object_class->finalize = gtk_list_box_finalize;
415 416 417 418 419 420 421
  widget_class->enter_notify_event = gtk_list_box_enter_notify_event;
  widget_class->leave_notify_event = gtk_list_box_leave_notify_event;
  widget_class->motion_notify_event = gtk_list_box_motion_notify_event;
  widget_class->show = gtk_list_box_show;
  widget_class->focus = gtk_list_box_focus;
  widget_class->draw = gtk_list_box_draw;
  widget_class->realize = gtk_list_box_realize;
422
  widget_class->compute_expand = gtk_list_box_compute_expand;
423 424 425 426 427 428 429 430 431 432
  widget_class->get_request_mode = gtk_list_box_get_request_mode;
  widget_class->get_preferred_height = gtk_list_box_get_preferred_height;
  widget_class->get_preferred_height_for_width = gtk_list_box_get_preferred_height_for_width;
  widget_class->get_preferred_width = gtk_list_box_get_preferred_width;
  widget_class->get_preferred_width_for_height = gtk_list_box_get_preferred_width_for_height;
  widget_class->size_allocate = gtk_list_box_size_allocate;
  widget_class->drag_leave = gtk_list_box_drag_leave;
  widget_class->parent_set = gtk_list_box_parent_set;
  container_class->add = gtk_list_box_add;
  container_class->remove = gtk_list_box_remove;
433
  container_class->forall = gtk_list_box_forall;
434 435 436 437
  container_class->child_type = gtk_list_box_child_type;
  klass->activate_cursor_row = gtk_list_box_activate_cursor_row;
  klass->toggle_cursor_row = gtk_list_box_toggle_cursor_row;
  klass->move_cursor = gtk_list_box_move_cursor;
438 439 440
  klass->select_all = gtk_list_box_select_all;
  klass->unselect_all = gtk_list_box_unselect_all;
  klass->selected_rows_changed = gtk_list_box_selected_rows_changed;
Alexander Larsson's avatar
Alexander Larsson committed
441 442 443

  properties[PROP_SELECTION_MODE] =
    g_param_spec_enum ("selection-mode",
Matthias Clasen's avatar
Matthias Clasen committed
444 445
                       P_("Selection mode"),
                       P_("The selection mode"),
Alexander Larsson's avatar
Alexander Larsson committed
446 447
                       GTK_TYPE_SELECTION_MODE,
                       GTK_SELECTION_SINGLE,
448
                       G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
Alexander Larsson's avatar
Alexander Larsson committed
449 450 451

  properties[PROP_ACTIVATE_ON_SINGLE_CLICK] =
    g_param_spec_boolean ("activate-on-single-click",
Matthias Clasen's avatar
Matthias Clasen committed
452 453
                          P_("Activate on Single Click"),
                          P_("Activate row on a single click"),
Alexander Larsson's avatar
Alexander Larsson committed
454
                          TRUE,
455
                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
Alexander Larsson's avatar
Alexander Larsson committed
456 457 458

  g_object_class_install_properties (object_class, LAST_PROPERTY, properties);

Alexander Larsson's avatar
Alexander Larsson committed
459 460
  /**
   * GtkListBox::row-selected:
461
   * @box: the #GtkListBox
462
   * @row: (nullable): the selected row
Alexander Larsson's avatar
Alexander Larsson committed
463 464 465 466
   *
   * The ::row-selected signal is emitted when a new row is selected, or
   * (with a %NULL @row) when the selection is cleared.
   *
467 468 469
   * When the @box is using #GTK_SELECTION_MULTIPLE, this signal will not
   * give you the full picture of selection changes, and you should use
   * the #GtkListBox::selected-rows-changed signal instead.
470
   *
Alexander Larsson's avatar
Alexander Larsson committed
471 472
   * Since: 3.10
   */
Alexander Larsson's avatar
Alexander Larsson committed
473 474 475 476 477 478 479 480
  signals[ROW_SELECTED] =
    g_signal_new ("row-selected",
                  GTK_TYPE_LIST_BOX,
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GtkListBoxClass, row_selected),
                  NULL, NULL,
                  g_cclosure_marshal_VOID__OBJECT,
                  G_TYPE_NONE, 1,
Alexander Larsson's avatar
Alexander Larsson committed
481
                  GTK_TYPE_LIST_BOX_ROW);
Matthias Clasen's avatar
Matthias Clasen committed
482

483 484
  /**
   * GtkListBox::selected-rows-changed:
485
   * @box: the #GtkListBox on wich the signal is emitted
486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539
   *
   * The ::selected-rows-changed signal is emitted when the
   * set of selected rows changes.
   *
   * Since: 3.14
   */
  signals[SELECTED_ROWS_CHANGED] = g_signal_new ("selected-rows-changed",
                                                 GTK_TYPE_LIST_BOX,
                                                 G_SIGNAL_RUN_FIRST,
                                                 G_STRUCT_OFFSET (GtkListBoxClass, selected_rows_changed),
                                                 NULL, NULL,
                                                 g_cclosure_marshal_VOID__VOID,
                                                 G_TYPE_NONE, 0);

  /**
   * GtkListBox::select-all:
   * @box: the #GtkListBox on which the signal is emitted
   *
   * The ::select-all signal is a [keybinding signal][GtkBindingSignal]
   * which gets emitted to select all children of the box, if the selection
   * mode permits it.
   *
   * The default bindings for this signal is Ctrl-a.
   *
   * Since: 3.14
   */
  signals[SELECT_ALL] = g_signal_new ("select-all",
                                      GTK_TYPE_LIST_BOX,
                                      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                                      G_STRUCT_OFFSET (GtkListBoxClass, select_all),
                                      NULL, NULL,
                                      g_cclosure_marshal_VOID__VOID,
                                      G_TYPE_NONE, 0);

  /**
   * GtkListBox::unselect-all:
   * @box: the #GtkListBox on which the signal is emitted
   * 
   * The ::unselect-all signal is a [keybinding signal][GtkBindingSignal]
   * which gets emitted to unselect all children of the box, if the selection
   * mode permits it.
   *
   * The default bindings for this signal is Ctrl-Shift-a.
   *
   * Since: 3.14
   */
  signals[UNSELECT_ALL] = g_signal_new ("unselect-all",
                                      GTK_TYPE_LIST_BOX,
                                      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                                      G_STRUCT_OFFSET (GtkListBoxClass, unselect_all),
                                      NULL, NULL,
                                      g_cclosure_marshal_VOID__VOID,
                                      G_TYPE_NONE, 0);

Alexander Larsson's avatar
Alexander Larsson committed
540 541
  /**
   * GtkListBox::row-activated:
542
   * @box: the #GtkListBox
Alexander Larsson's avatar
Alexander Larsson committed
543 544
   * @row: the activated row
   *
545
   * The ::row-activated signal is emitted when a row has been activated by the user.
Alexander Larsson's avatar
Alexander Larsson committed
546 547 548
   *
   * Since: 3.10
   */
Alexander Larsson's avatar
Alexander Larsson committed
549 550 551 552 553 554 555 556
  signals[ROW_ACTIVATED] =
    g_signal_new ("row-activated",
                  GTK_TYPE_LIST_BOX,
                  G_SIGNAL_RUN_LAST,
                  G_STRUCT_OFFSET (GtkListBoxClass, row_activated),
                  NULL, NULL,
                  g_cclosure_marshal_VOID__OBJECT,
                  G_TYPE_NONE, 1,
Alexander Larsson's avatar
Alexander Larsson committed
557
                  GTK_TYPE_LIST_BOX_ROW);
Alexander Larsson's avatar
Alexander Larsson committed
558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594
  signals[ACTIVATE_CURSOR_ROW] =
    g_signal_new ("activate-cursor-row",
                  GTK_TYPE_LIST_BOX,
                  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                  G_STRUCT_OFFSET (GtkListBoxClass, activate_cursor_row),
                  NULL, NULL,
                  g_cclosure_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);
  signals[TOGGLE_CURSOR_ROW] =
    g_signal_new ("toggle-cursor-row",
                  GTK_TYPE_LIST_BOX,
                  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                  G_STRUCT_OFFSET (GtkListBoxClass, toggle_cursor_row),
                  NULL, NULL,
                  g_cclosure_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);
  signals[MOVE_CURSOR] =
    g_signal_new ("move-cursor",
                  GTK_TYPE_LIST_BOX,
                  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
                  G_STRUCT_OFFSET (GtkListBoxClass, move_cursor),
                  NULL, NULL,
                  _gtk_marshal_VOID__ENUM_INT,
                  G_TYPE_NONE, 2,
                  GTK_TYPE_MOVEMENT_STEP, G_TYPE_INT);

  widget_class->activate_signal = signals[ACTIVATE_CURSOR_ROW];

  binding_set = gtk_binding_set_by_class (klass);
  gtk_list_box_add_move_binding (binding_set, GDK_KEY_Home, 0,
                                 GTK_MOVEMENT_BUFFER_ENDS, -1);
  gtk_list_box_add_move_binding (binding_set, GDK_KEY_KP_Home, 0,
                                 GTK_MOVEMENT_BUFFER_ENDS, -1);
  gtk_list_box_add_move_binding (binding_set, GDK_KEY_End, 0,
                                 GTK_MOVEMENT_BUFFER_ENDS, 1);
  gtk_list_box_add_move_binding (binding_set, GDK_KEY_KP_End, 0,
                                 GTK_MOVEMENT_BUFFER_ENDS, 1);
595
  gtk_list_box_add_move_binding (binding_set, GDK_KEY_Up, 0,
Alexander Larsson's avatar
Alexander Larsson committed
596
                                 GTK_MOVEMENT_DISPLAY_LINES, -1);
597
  gtk_list_box_add_move_binding (binding_set, GDK_KEY_KP_Up, 0,
Alexander Larsson's avatar
Alexander Larsson committed
598
                                 GTK_MOVEMENT_DISPLAY_LINES, -1);
599
  gtk_list_box_add_move_binding (binding_set, GDK_KEY_Down, 0,
Alexander Larsson's avatar
Alexander Larsson committed
600
                                 GTK_MOVEMENT_DISPLAY_LINES, 1);
601
  gtk_list_box_add_move_binding (binding_set, GDK_KEY_KP_Down, 0,
Alexander Larsson's avatar
Alexander Larsson committed
602 603 604 605 606 607 608 609 610
                                 GTK_MOVEMENT_DISPLAY_LINES, 1);
  gtk_list_box_add_move_binding (binding_set, GDK_KEY_Page_Up, 0,
                                 GTK_MOVEMENT_PAGES, -1);
  gtk_list_box_add_move_binding (binding_set, GDK_KEY_KP_Page_Up, 0,
                                 GTK_MOVEMENT_PAGES, -1);
  gtk_list_box_add_move_binding (binding_set, GDK_KEY_Page_Down, 0,
                                 GTK_MOVEMENT_PAGES, 1);
  gtk_list_box_add_move_binding (binding_set, GDK_KEY_KP_Page_Down, 0,
                                 GTK_MOVEMENT_PAGES, 1);
611

Alexander Larsson's avatar
Alexander Larsson committed
612 613
  gtk_binding_entry_add_signal (binding_set, GDK_KEY_space, GDK_CONTROL_MASK,
                                "toggle-cursor-row", 0, NULL);
614 615 616 617 618 619 620
  gtk_binding_entry_add_signal (binding_set, GDK_KEY_KP_Space, GDK_CONTROL_MASK,
                                "toggle-cursor-row", 0, NULL);

  gtk_binding_entry_add_signal (binding_set, GDK_KEY_a, GDK_CONTROL_MASK,
                                "select-all", 0);
  gtk_binding_entry_add_signal (binding_set, GDK_KEY_a, GDK_CONTROL_MASK | GDK_SHIFT_MASK,
                                "unselect-all", 0);
Alexander Larsson's avatar
Alexander Larsson committed
621 622 623 624
}

/**
 * gtk_list_box_get_selected_row:
625
 * @box: a #GtkListBox
Alexander Larsson's avatar
Alexander Larsson committed
626 627 628
 *
 * Gets the selected row.
 *
629 630 631 632 633
 * Note that the box may allow multiple selection, in which
 * case you should use gtk_list_box_selected_foreach() to
 * find all selected rows.
 *
 * Returns: (transfer none): the selected row
Alexander Larsson's avatar
Alexander Larsson committed
634 635
 *
 * Since: 3.10
Matthias Clasen's avatar
Matthias Clasen committed
636
 */
Alexander Larsson's avatar
Alexander Larsson committed
637
GtkListBoxRow *
638
gtk_list_box_get_selected_row (GtkListBox *box)
Alexander Larsson's avatar
Alexander Larsson committed
639
{
640
  g_return_val_if_fail (GTK_IS_LIST_BOX (box), NULL);
Alexander Larsson's avatar
Alexander Larsson committed
641

642
  return BOX_PRIV (box)->selected_row;
Alexander Larsson's avatar
Alexander Larsson committed
643 644 645 646
}

/**
 * gtk_list_box_get_row_at_index:
647
 * @box: a #GtkListBox
Matthias Clasen's avatar
Matthias Clasen committed
648
 * @index_: the index of the row
Alexander Larsson's avatar
Alexander Larsson committed
649
 *
650
 * Gets the n-th child in the list (not counting headers).
651 652
 * If @_index is negative or larger than the number of items in the
 * list, %NULL is returned.
Alexander Larsson's avatar
Alexander Larsson committed
653
 *
654
 * Returns: (transfer none): the child #GtkWidget or %NULL
Alexander Larsson's avatar
Alexander Larsson committed
655 656
 *
 * Since: 3.10
Matthias Clasen's avatar
Matthias Clasen committed
657
 */
Alexander Larsson's avatar
Alexander Larsson committed
658
GtkListBoxRow *
659
gtk_list_box_get_row_at_index (GtkListBox *box,
Matthias Clasen's avatar
Matthias Clasen committed
660
                               gint        index_)
Alexander Larsson's avatar
Alexander Larsson committed
661 662 663
{
  GSequenceIter *iter;

664
  g_return_val_if_fail (GTK_IS_LIST_BOX (box), NULL);
665

666
  iter = g_sequence_get_iter_at_pos (BOX_PRIV (box)->children, index_);
667
  if (!g_sequence_iter_is_end (iter))
Alexander Larsson's avatar
Alexander Larsson committed
668 669 670 671 672 673 674
    return g_sequence_get (iter);

  return NULL;
}

/**
 * gtk_list_box_get_row_at_y:
675
 * @box: a #GtkListBox
Alexander Larsson's avatar
Alexander Larsson committed
676 677
 * @y: position
 *
Alexander Larsson's avatar
Alexander Larsson committed
678
 * Gets the row at the @y position.
Alexander Larsson's avatar
Alexander Larsson committed
679
 *
680
 * Returns: (transfer none): the row
Alexander Larsson's avatar
Alexander Larsson committed
681 682
 *
 * Since: 3.10
Matthias Clasen's avatar
Matthias Clasen committed
683
 */
Alexander Larsson's avatar
Alexander Larsson committed
684
GtkListBoxRow *
685
gtk_list_box_get_row_at_y (GtkListBox *box,
Matthias Clasen's avatar
Matthias Clasen committed
686
                           gint        y)
Alexander Larsson's avatar
Alexander Larsson committed
687 688 689 690 691
{
  GtkListBoxRow *row, *found_row;
  GtkListBoxRowPrivate *row_priv;
  GSequenceIter *iter;

692
  g_return_val_if_fail (GTK_IS_LIST_BOX (box), NULL);
Alexander Larsson's avatar
Alexander Larsson committed
693 694 695 696

  /* TODO: This should use g_sequence_search */

  found_row = NULL;
697
  for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
Alexander Larsson's avatar
Alexander Larsson committed
698 699 700 701
       !g_sequence_iter_is_end (iter);
       iter = g_sequence_iter_next (iter))
    {
      row = (GtkListBoxRow*) g_sequence_get (iter);
702
      row_priv = ROW_PRIV (row);
Alexander Larsson's avatar
Alexander Larsson committed
703 704 705 706 707 708 709 710 711 712 713 714
      if (y >= row_priv->y && y < (row_priv->y + row_priv->height))
        {
          found_row = row;
          break;
        }
    }

  return found_row;
}

/**
 * gtk_list_box_select_row:
715
 * @box: a #GtkListBox
Alexander Larsson's avatar
Alexander Larsson committed
716
 * @row: (allow-none): The row to select or %NULL
Alexander Larsson's avatar
Alexander Larsson committed
717 718 719 720
 *
 * Make @row the currently selected row.
 *
 * Since: 3.10
Alexander Larsson's avatar
Alexander Larsson committed
721 722
 */
void
723
gtk_list_box_select_row (GtkListBox    *box,
Matthias Clasen's avatar
Matthias Clasen committed
724
                         GtkListBoxRow *row)
Alexander Larsson's avatar
Alexander Larsson committed
725
{
726 727
  gboolean dirty = FALSE;

728
  g_return_if_fail (GTK_IS_LIST_BOX (box));
729
  g_return_if_fail (row == NULL || GTK_IS_LIST_BOX_ROW (row));
Alexander Larsson's avatar
Alexander Larsson committed
730

731
  if (row)
732
    gtk_list_box_select_row_internal (box, row);
733
  else
734 735 736 737 738 739 740
    dirty = gtk_list_box_unselect_all_internal (box);

  if (dirty)
    {
      g_signal_emit (box, signals[ROW_SELECTED], 0, NULL);
      g_signal_emit (box, signals[SELECTED_ROWS_CHANGED], 0);
    }
Alexander Larsson's avatar
Alexander Larsson committed
741 742
}

743 744
/**   
 * gtk_list_box_unselect_row:
745
 * @box: a #GtkListBox
746 747
 * @row: the row to unselected
 *
748
 * Unselects a single row of @box, if the selection mode allows it.
749 750 751 752
 *
 * Since: 3.14
 */                       
void
753
gtk_list_box_unselect_row (GtkListBox    *box,
754 755
                           GtkListBoxRow *row)
{
756
  g_return_if_fail (GTK_IS_LIST_BOX (box));
757 758
  g_return_if_fail (GTK_IS_LIST_BOX_ROW (row));
  
759
  gtk_list_box_unselect_row_internal (box, row);
760 761 762 763
} 

/**
 * gtk_list_box_select_all:
764
 * @box: a #GtkListBox
765
 *
766
 * Select all children of @box, if the selection mode allows it.
767 768 769 770
 *
 * Since: 3.14
 */
void
771
gtk_list_box_select_all (GtkListBox *box)
772
{
773
  g_return_if_fail (GTK_IS_LIST_BOX (box));
774

775
  if (BOX_PRIV (box)->selection_mode != GTK_SELECTION_MULTIPLE)
776 777
    return;

778
  if (g_sequence_get_length (BOX_PRIV (box)->children) > 0)
779
    {
780 781
      gtk_list_box_select_all_between (box, NULL, NULL, FALSE);
      g_signal_emit (box, signals[SELECTED_ROWS_CHANGED], 0);
782
    }
783 784 785 786
}

/**
 * gtk_list_box_unselect_all:
787
 * @box: a #GtkListBox
788
 *
789
 * Unselect all children of @box, if the selection mode allows it.
790 791 792 793
 *
 * Since: 3.14
 */
void
794
gtk_list_box_unselect_all (GtkListBox *box)
795
{
796 797
  gboolean dirty = FALSE;

798
  g_return_if_fail (GTK_IS_LIST_BOX (box));
799

800
  if (BOX_PRIV (box)->selection_mode == GTK_SELECTION_BROWSE)
801 802
    return;

803
  dirty = gtk_list_box_unselect_all_internal (box);
804 805

  if (dirty)
806 807 808 809
    {
      g_signal_emit (box, signals[ROW_SELECTED], 0, NULL);
      g_signal_emit (box, signals[SELECTED_ROWS_CHANGED], 0);
    }
810 811 812
}

static void
813
gtk_list_box_selected_rows_changed (GtkListBox *box)
814
{
815
  _gtk_list_box_accessible_selection_changed (box);
816 817 818 819
}

/**
 * GtkListBoxForeachFunc:
820
 * @box: a #GtkListBox
821 822 823 824
 * @row: a #GtkListBoxRow
 * @user_data: (closure): user data
 *
 * A function used by gtk_list_box_selected_foreach().
825
 * It will be called on every selected child of the @box.
826 827 828 829 830 831
 *
 * Since: 3.14
 */

/**
 * gtk_list_box_selected_foreach:
832
 * @box: a #GtkListBox
833 834 835 836 837
 * @func: (scope call): the function to call for each selected child
 * @data: user data to pass to the function
 *
 * Calls a function for each selected child.
 *
838
 * Note that the selection cannot be modified from within this function.
839 840 841 842
 *
 * Since: 3.14
 */
void
843
gtk_list_box_selected_foreach (GtkListBox            *box,
844 845 846 847 848 849
                               GtkListBoxForeachFunc  func,
                               gpointer               data)
{
  GtkListBoxRow *row;
  GSequenceIter *iter;

850
  g_return_if_fail (GTK_IS_LIST_BOX (box));
851

852
  for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
853 854 855 856 857
       !g_sequence_iter_is_end (iter);
       iter = g_sequence_iter_next (iter))
    {
      row = g_sequence_get (iter);
      if (gtk_list_box_row_is_selected (row))
858
        (*func) (box, row, data);
859 860 861 862
    }
}

/**
863
 * gtk_list_box_get_selected_rows:
864
 * @box: a #GtkListBox
865 866 867 868 869 870 871 872 873 874
 *
 * Creates a list of all selected children.
 *
 * Returns: (element-type GtkListBoxRow) (transfer container):
 *     A #GList containing the #GtkWidget for each selected child.
 *     Free with g_list_free() when done.
 *
 * Since: 3.14
 */
GList *
875
gtk_list_box_get_selected_rows (GtkListBox *box)
876 877 878 879 880
{
  GtkListBoxRow *row;
  GSequenceIter *iter;
  GList *selected = NULL;

881
  g_return_val_if_fail (GTK_IS_LIST_BOX (box), NULL);
882

883
  for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
884 885 886 887 888 889 890 891 892 893
       !g_sequence_iter_is_end (iter);
       iter = g_sequence_iter_next (iter))
    {
      row = g_sequence_get (iter);
      if (gtk_list_box_row_is_selected (row))
        selected = g_list_prepend (selected, row);
    }

  return g_list_reverse (selected);
}
894 895 896

/**
 * gtk_list_box_set_placeholder:
897
 * @box: a #GtkListBox
898 899 900
 * @placeholder: (allow-none): a #GtkWidget or %NULL
 *
 * Sets the placeholder widget that is shown in the list when
901
 * it doesn't display any visible children.
902 903 904 905
 *
 * Since: 3.10
 */
void
906
gtk_list_box_set_placeholder (GtkListBox *box,
Matthias Clasen's avatar
Matthias Clasen committed
907
                              GtkWidget  *placeholder)
908
{
909
  GtkListBoxPrivate *priv = BOX_PRIV (box);
910

911
  g_return_if_fail (GTK_IS_LIST_BOX (box));
912 913 914 915

  if (priv->placeholder)
    {
      gtk_widget_unparent (priv->placeholder);
916
      gtk_widget_queue_resize (GTK_WIDGET (box));
917 918 919 920 921 922
    }

  priv->placeholder = placeholder;

  if (placeholder)
    {
923
      gtk_widget_set_parent (GTK_WIDGET (placeholder), GTK_WIDGET (box));
924 925 926 927 928 929
      gtk_widget_set_child_visible (GTK_WIDGET (placeholder),
                                    priv->n_visible_rows == 0);
    }
}


Alexander Larsson's avatar
Alexander Larsson committed
930 931
/**
 * gtk_list_box_set_adjustment:
932
 * @box: a #GtkListBox
Matthias Clasen's avatar
Matthias Clasen committed
933
 * @adjustment: (allow-none): the adjustment, or %NULL
Alexander Larsson's avatar
Alexander Larsson committed
934 935 936 937 938
 *
 * Sets the adjustment (if any) that the widget uses to
 * for vertical scrolling. For instance, this is used
 * to get the page size for PageUp/Down key handling.
 *
939
 * In the normal case when the @box is packed inside
Alexander Larsson's avatar
Alexander Larsson committed
940 941 942 943 944 945
 * a #GtkScrolledWindow the adjustment from that will
 * be picked up automatically, so there is no need
 * to manually do that.
 *
 * Since: 3.10
 */
Alexander Larsson's avatar
Alexander Larsson committed
946
void
947
gtk_list_box_set_adjustment (GtkListBox    *box,
Alexander Larsson's avatar
Alexander Larsson committed
948 949
                             GtkAdjustment *adjustment)
{
950
  GtkListBoxPrivate *priv = BOX_PRIV (box);
951

952
  g_return_if_fail (GTK_IS_LIST_BOX (box));
953
  g_return_if_fail (adjustment == NULL || GTK_IS_ADJUSTMENT (adjustment));
Alexander Larsson's avatar
Alexander Larsson committed
954

955 956
  if (adjustment)
    g_object_ref_sink (adjustment);
Alexander Larsson's avatar
Alexander Larsson committed
957 958 959 960 961
  if (priv->adjustment)
    g_object_unref (priv->adjustment);
  priv->adjustment = adjustment;
}

Alexander Larsson's avatar
Alexander Larsson committed
962 963
/**
 * gtk_list_box_get_adjustment:
964
 * @box: a #GtkListBox
Alexander Larsson's avatar
Alexander Larsson committed
965 966 967 968
 *
 * Gets the adjustment (if any) that the widget uses to
 * for vertical scrolling.
 *
969
 * Returns: (transfer none): the adjustment
Alexander Larsson's avatar
Alexander Larsson committed
970 971 972
 *
 * Since: 3.10
 */
Alexander Larsson's avatar
Alexander Larsson committed
973
GtkAdjustment *
974
gtk_list_box_get_adjustment (GtkListBox *box)
Alexander Larsson's avatar
Alexander Larsson committed
975
{
976
  g_return_val_if_fail (GTK_IS_LIST_BOX (box), NULL);
Alexander Larsson's avatar
Alexander Larsson committed
977

978
  return BOX_PRIV (box)->adjustment;
Alexander Larsson's avatar
Alexander Larsson committed
979 980
}

981 982 983 984 985 986 987 988 989 990 991
static void
adjustment_changed (GObject    *object,
                    GParamSpec *pspec,
                    gpointer    data)
{
  GtkAdjustment *adjustment;

  adjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (object));
  gtk_list_box_set_adjustment (GTK_LIST_BOX (data), adjustment);
}

992
static void
993 994
gtk_list_box_parent_set (GtkWidget *widget,
                         GtkWidget *prev_parent)
Alexander Larsson's avatar
Alexander Larsson committed
995
{
996
  GtkWidget *parent;
Alexander Larsson's avatar
Alexander Larsson committed
997

998
  parent = gtk_widget_get_parent (widget);
Alexander Larsson's avatar
Alexander Larsson committed
999

1000 1001 1002 1003
  if (prev_parent && GTK_IS_SCROLLABLE (prev_parent))
    g_signal_handlers_disconnect_by_func (prev_parent,
                                          G_CALLBACK (adjustment_changed), widget);

1004 1005
  if (parent && GTK_IS_SCROLLABLE (parent))
    {
1006 1007 1008
      adjustment_changed (G_OBJECT (parent), NULL, widget);
      g_signal_connect (parent, "notify::vadjustment",
                        G_CALLBACK (adjustment_changed), widget);
1009
    }
1010 1011
  else
    gtk_list_box_set_adjustment (GTK_LIST_BOX (widget), NULL);
1012
}
Alexander Larsson's avatar
Alexander Larsson committed
1013

Alexander Larsson's avatar
Alexander Larsson committed
1014 1015
/**
 * gtk_list_box_set_selection_mode:
1016
 * @box: a #GtkListBox
Alexander Larsson's avatar
Alexander Larsson committed
1017 1018 1019 1020 1021 1022 1023
 * @mode: The #GtkSelectionMode
 *
 * Sets how selection works in the listbox.
 * See #GtkSelectionMode for details.
 *
 * Since: 3.10
 */
Alexander Larsson's avatar
Alexander Larsson committed
1024
void
1025
gtk_list_box_set_selection_mode (GtkListBox       *box,
Matthias Clasen's avatar
Matthias Clasen committed
1026
                                 GtkSelectionMode  mode)
Alexander Larsson's avatar
Alexander Larsson committed
1027
{
1028
  GtkListBoxPrivate *priv = BOX_PRIV (box);
1029
  gboolean dirty = FALSE;
1030

1031
  g_return_if_fail (GTK_IS_LIST_BOX (box));
Alexander Larsson's avatar
Alexander Larsson committed
1032

1033 1034
  if (priv->selection_mode == mode)
    return;
Alexander Larsson's avatar
Alexander Larsson committed
1035

1036 1037
  if (mode == GTK_SELECTION_NONE ||
      priv->selection_mode == GTK_SELECTION_MULTIPLE)
1038
    dirty = gtk_list_box_unselect_all_internal (box);
Alexander Larsson's avatar
Alexander Larsson committed
1039 1040 1041

  priv->selection_mode = mode;

1042 1043
  gtk_list_box_update_row_styles (box);

1044
  g_object_notify_by_pspec (G_OBJECT (box), properties[PROP_SELECTION_MODE]);
1045 1046

  if (dirty)
1047 1048 1049 1050
    {
      g_signal_emit (box, signals[ROW_SELECTED], 0, NULL);
      g_signal_emit (box, signals[SELECTED_ROWS_CHANGED], 0);
    }
Alexander Larsson's avatar
Alexander Larsson committed
1051 1052
}

Alexander Larsson's avatar
Alexander Larsson committed
1053 1054
/**
 * gtk_list_box_get_selection_mode:
1055
 * @box: a #GtkListBox
Alexander Larsson's avatar
Alexander Larsson committed
1056 1057 1058 1059 1060 1061 1062
 *
 * Gets the selection mode of the listbox.
 *
 * Returns: a #GtkSelectionMode
 *
 * Since: 3.10
 */
Alexander Larsson's avatar
Alexander Larsson committed
1063
GtkSelectionMode
1064
gtk_list_box_get_selection_mode (GtkListBox *box)
Alexander Larsson's avatar
Alexander Larsson committed
1065
{
1066
  g_return_val_if_fail (GTK_IS_LIST_BOX (box), GTK_SELECTION_NONE);
1067

1068
  return BOX_PRIV (box)->selection_mode;
Alexander Larsson's avatar
Alexander Larsson committed
1069 1070 1071 1072
}

/**
 * gtk_list_box_set_filter_func:
1073
 * @box: a #GtkListBox
Matthias Clasen's avatar
Matthias Clasen committed
1074
 * @filter_func: (closure user_data) (allow-none): callback that lets you filter which rows to show
Alexander Larsson's avatar
Alexander Larsson committed
1075 1076 1077
 * @user_data: user data passed to @filter_func
 * @destroy: destroy notifier for @user_data
 *
1078
 * By setting a filter function on the @box one can decide dynamically which
Alexander Larsson's avatar
Alexander Larsson committed
1079 1080 1081 1082 1083
 * of the rows to show. For instance, to implement a search function on a list that
 * filters the original list to only show the matching rows.
 *
 * The @filter_func will be called for each row after the call, and it will
 * continue to be called each time a row changes (via gtk_list_box_row_changed()) or
1084
 * when gtk_list_box_invalidate_filter() is called.
Alexander Larsson's avatar
Alexander Larsson committed
1085
 *
1086 1087 1088
 * Note that using a filter function is incompatible with using a model
 * (see gtk_list_box_bind_model()).
 *
Alexander Larsson's avatar
Alexander Larsson committed
1089
 * Since: 3.10
Alexander Larsson's avatar
Alexander Larsson committed
1090 1091
 */
void
1092
gtk_list_box_set_filter_func (GtkListBox           *box,
Matthias Clasen's avatar
Matthias Clasen committed
1093 1094 1095
                              GtkListBoxFilterFunc  filter_func,
                              gpointer              user_data,
                              GDestroyNotify        destroy)
Alexander Larsson's avatar
Alexander Larsson committed
1096
{
1097
  GtkListBoxPrivate *priv = BOX_PRIV (box);
Alexander Larsson's avatar
Alexander Larsson committed
1098

1099
  g_return_if_fail (GTK_IS_LIST_BOX (box));
Alexander Larsson's avatar
Alexander Larsson committed
1100 1101 1102 1103 1104 1105 1106 1107

  if (priv->filter_func_target_destroy_notify != NULL)
    priv->filter_func_target_destroy_notify (priv->filter_func_target);

  priv->filter_func = filter_func;
  priv->filter_func_target = user_data;
  priv->filter_func_target_destroy_notify = destroy;

1108 1109
  gtk_list_box_check_model_compat (box);

1110
  gtk_list_box_invalidate_filter (box);
Alexander Larsson's avatar
Alexander Larsson committed
1111 1112 1113
}

/**
1114
 * gtk_list_box_set_header_func:
1115
 * @box: a #GtkListBox
Matthias Clasen's avatar
Matthias Clasen committed
1116
 * @update_header: (closure user_data) (allow-none): callback that lets you add row headers
1117
 * @user_data: user data passed to @update_header
Alexander Larsson's avatar
Alexander Larsson committed
1118 1119
 * @destroy: destroy notifier for @user_data
 *
1120
 * By setting a header function on the @box one can dynamically add headers
Alexander Larsson's avatar
Alexander Larsson committed
1121 1122 1123 1124
 * in front of rows, depending on the contents of the row and its position in the list.
 * For instance, one could use it to add headers in front of the first item of a
 * new kind, in a list sorted by the kind.
 *
1125
 * The @update_header can look at the current header widget using gtk_list_box_row_get_header()
Alexander Larsson's avatar
Alexander Larsson committed
1126
 * and either update the state of the widget as needed, or set a new one using
1127
 * gtk_list_box_row_set_header(). If no header is needed, set the header to %NULL.
Alexander Larsson's avatar
Alexander Larsson committed
1128
 *
1129
 * Note that you may get many calls @update_header to this for a particular row when e.g.
1130
 * changing things that don’t affect the header. In this case it is important for performance
Matthias Clasen's avatar
Matthias Clasen committed
1131
 * to not blindly replace an existing header with an identical one.
Alexander Larsson's avatar
Alexander Larsson committed
1132
 *
1133
 * The @update_header function will be called for each row after the call, and it will
Alexander Larsson's avatar
Alexander Larsson committed
1134 1135 1136
 * continue to be called each time a row changes (via gtk_list_box_row_changed()) and when
 * the row before changes (either by gtk_list_box_row_changed() on the previous row, or when
 * the previous row becomes a different row). It is also called for all rows when
1137
 * gtk_list_box_invalidate_headers() is called.
Alexander Larsson's avatar
Alexander Larsson committed
1138 1139
 *
 * Since: 3.10
Alexander Larsson's avatar
Alexander Larsson committed
1140 1141
 */
void
1142
gtk_list_box_set_header_func (GtkListBox                 *box,
Matthias Clasen's avatar
Matthias Clasen committed
1143 1144 1145
                              GtkListBoxUpdateHeaderFunc  update_header,
                              gpointer                    user_data,
                              GDestroyNotify              destroy)
Alexander Larsson's avatar
Alexander Larsson committed
1146
{
1147
  GtkListBoxPrivate *priv = BOX_PRIV (box);
Alexander Larsson's avatar
Alexander Larsson committed
1148

1149
  g_return_if_fail (GTK_IS_LIST_BOX (box));
Alexander Larsson's avatar
Alexander Larsson committed
1150

1151 1152
  if (priv->update_header_func_target_destroy_notify != NULL)
    priv->update_header_func_target_destroy_notify (priv->update_header_func_target);
Alexander Larsson's avatar
Alexander Larsson committed
1153

1154 1155 1156
  priv->update_header_func = update_header;
  priv->update_header_func_target = user_data;
  priv->update_header_func_target_destroy_notify = destroy;
1157
  gtk_list_box_invalidate_headers (box);
Alexander Larsson's avatar
Alexander Larsson committed
1158 1159
}

Alexander Larsson's avatar
Alexander Larsson committed
1160
/**
1161
 * gtk_list_box_invalidate_filter:
1162
 * @box: a #GtkListBox
Alexander Larsson's avatar
Alexander Larsson committed
1163 1164
 *
 * Update the filtering for all rows. Call this when result
1165
 * of the filter function on the @box is changed due
Alexander Larsson's avatar
Alexander Larsson committed
1166 1167 1168 1169 1170 1171
 * to an external factor. For instance, this would be used
 * if the filter function just looked for a specific search
 * string and the entry with the search string has changed.
 *
 * Since: 3.10
 */
Alexander Larsson's avatar
Alexander Larsson committed
1172
void
1173
gtk_list_box_invalidate_filter (GtkListBox *box)
Alexander Larsson's avatar
Alexander Larsson committed
1174
{
1175
  g_return_if_fail (GTK_IS_LIST_BOX (box));
Alexander Larsson's avatar
Alexander Larsson committed
1176

1177 1178 1179
  gtk_list_box_apply_filter_all (box);
  gtk_list_box_invalidate_headers (box);
  gtk_widget_queue_resize (GTK_WIDGET (box));
Alexander Larsson's avatar
Alexander Larsson committed
1180 1181 1182 1183 1184
}

static gint
do_sort (GtkListBoxRow *a,
         GtkListBoxRow *b,
1185
         GtkListBox    *box)
Alexander Larsson's avatar
Alexander Larsson committed
1186
{
1187
  GtkListBoxPrivate *priv = BOX_PRIV (box);
Alexander Larsson's avatar
Alexander Larsson committed
1188 1189 1190 1191

  return priv->sort_func (a, b, priv->sort_func_target);
}

1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204
static void
gtk_list_box_css_node_foreach (gpointer data,
                               gpointer user_data)
{
  GtkWidget **previous = user_data;
  GtkWidget *row = data;
  GtkCssNode *row_node;
  GtkCssNode *prev_node;

  if (*previous)
    {
      prev_node = gtk_widget_get_css_node (*previous);
      row_node = gtk_widget_get_css_node (row);
1205 1206 1207
      gtk_css_node_insert_after (gtk_css_node_get_parent (row_node),
                                 row_node,
                                 prev_node);
1208 1209 1210 1211 1212
    }

  *previous = row;
}

Alexander Larsson's avatar
Alexander Larsson committed
1213
/**
1214
 * gtk_list_box_invalidate_sort:
1215
 * @box: a #GtkListBox
Alexander Larsson's avatar
Alexander Larsson committed
1216 1217
 *
 * Update the sorting for all rows. Call this when result
1218
 * of the sort function on the @box is changed due
Alexander Larsson's avatar
Alexander Larsson committed
1219 1220 1221 1222
 * to an external factor.
 *
 * Since: 3.10
 */
Alexander Larsson's avatar
Alexander Larsson committed
1223
void
1224
gtk_list_box_invalidate_sort (GtkListBox *box)
Alexander Larsson's avatar
Alexander Larsson committed
1225
{
1226
  GtkListBoxPrivate *priv = BOX_PRIV (box);
1227
  GtkWidget *previous = NULL;
1228

1229
  g_return_if_fail (GTK_IS_LIST_BOX (box));
Alexander Larsson's avatar
Alexander Larsson committed
1230

1231
  g_sequence_sort (priv->children, (GCompareDataFunc)do_sort, box);
1232
  g_sequence_foreach (priv->children, gtk_list_box_css_node_foreach, &previous);
Alexander Larsson's avatar
Alexander Larsson committed
1233

1234 1235
  gtk_list_box_invalidate_headers (box);
  gtk_widget_queue_resize (GTK_WIDGET (box));
Alexander Larsson's avatar
Alexander Larsson committed
1236 1237
}

1238
static void
1239
gtk_list_box_do_reseparate (GtkListBox *box)
1240 1241 1242
{
  GSequenceIter *iter;

1243
  for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
1244 1245
       !g_sequence_iter_is_end (iter);
       iter = g_sequence_iter_next (iter))
1246
    gtk_list_box_update_header (box, iter);
1247

1248
  gtk_widget_queue_resize (GTK_WIDGET (box));
1249 1250 1251
}


Alexander Larsson's avatar
Alexander Larsson committed
1252
/**
1253
 * gtk_list_box_invalidate_headers:
1254
 * @box: a #GtkListBox
Alexander Larsson's avatar
Alexander Larsson committed
1255 1256
 *
 * Update the separators for all rows. Call this when result
1257
 * of the header function on the @box is changed due
Alexander Larsson's avatar
Alexander Larsson committed
1258 1259 1260 1261
 * to an external factor.
 *
 * Since: 3.10
 */
Alexander Larsson's avatar
Alexander Larsson committed
1262
void
1263
gtk_list_box_invalidate_headers (GtkListBox *box)
Alexander Larsson's avatar
Alexander Larsson committed
1264
{
1265
  g_return_if_fail (GTK_IS_LIST_BOX (box));
Alexander Larsson's avatar
Alexander Larsson committed
1266

1267
  if (!gtk_widget_get_visible (GTK_WIDGET (box)))
1268
    return;
Alexander Larsson's avatar
Alexander Larsson committed
1269

1270
  gtk_list_box_do_reseparate (box);
Alexander Larsson's avatar
Alexander Larsson committed
1271 1272 1273 1274
}

/**
 * gtk_list_box_set_sort_func:
1275
 * @box: a #GtkListBox
Matthias Clasen's avatar
Matthias Clasen committed
1276
 * @sort_func: (closure user_data) (allow-none): the sort function
Alexander Larsson's avatar
Alexander Larsson committed
1277 1278 1279
 * @user_data: user data passed to @sort_func
 * @destroy: destroy notifier for @user_data
 *
1280
 * By setting a sort function on the @box one can dynamically reorder the rows
Alexander Larsson's avatar
Alexander Larsson committed
1281 1282 1283 1284
 * of the list, based on the contents of the rows.
 *
 * The @sort_func will be called for each row after the call, and will continue to
 * be called each time a row changes (via gtk_list_box_row_changed()) and when
1285
 * gtk_list_box_invalidate_sort() is called.
Alexander Larsson's avatar
Alexander Larsson committed
1286
 *
1287 1288 1289
 * Note that using a sort function is incompatible with using a model
 * (see gtk_list_box_bind_model()).
 *
Alexander Larsson's avatar
Alexander Larsson committed
1290
 * Since: 3.10
Alexander Larsson's avatar
Alexander Larsson committed
1291 1292
 */
void
1293
gtk_list_box_set_sort_func (GtkListBox         *box,
Matthias Clasen's avatar
Matthias Clasen committed
1294 1295 1296
                            GtkListBoxSortFunc  sort_func,
                            gpointer            user_data,
                            GDestroyNotify      destroy)
Alexander Larsson's avatar
Alexander Larsson committed
1297
{
1298
  GtkListBoxPrivate *priv = BOX_PRIV (box);
Alexander Larsson's avatar
Alexander Larsson committed
1299

1300
  g_return_if_fail (GTK_IS_LIST_BOX (box));
Alexander Larsson's avatar
Alexander Larsson committed
1301 1302 1303 1304 1305 1306 1307

  if (priv->sort_func_target_destroy_notify != NULL)
    priv->sort_func_target_destroy_notify (priv->sort_func_target);

  priv->sort_func = sort_func;
  priv->sort_func_target = user_data;
  priv->sort_func_target_destroy_notify = destroy;
1308

1309 1310
  gtk_list_box_check_model_compat (box);

1311
  gtk_list_box_invalidate_sort (box);
Alexander Larsson's avatar
Alexander Larsson committed
1312 1313 1314
}

static void
1315
gtk_list_box_got_row_changed (GtkListBox    *box,
Matthias Clasen's avatar
Matthias Clasen committed
1316
                              GtkListBoxRow *row)
Alexander Larsson's avatar
Alexander Larsson committed
1317
{
1318 1319
  GtkListBoxPrivate *priv = BOX_PRIV (box);
  GtkListBoxRowPrivate *row_priv = ROW_PRIV (row);
Alexander Larsson's avatar
Alexander Larsson committed
1320 1321
  GSequenceIter *prev_next, *next;

1322
  g_return_if_fail (GTK_IS_LIST_BOX (box));
1323 1324
  g_return_if_fail (GTK_IS_LIST_BOX_ROW (row));

1325
  prev_next = gtk_list_box_get_next_visible (box, row_priv->iter);
Alexander Larsson's avatar
Alexander Larsson committed
1326 1327
  if (priv->sort_func != NULL)
    {
1328
      g_sequence_sort_changed (row_priv->iter,
Alexander Larsson's avatar
Alexander Larsson committed
1329
                               (GCompareDataFunc)do_sort,
1330 1331
                               box);
      gtk_widget_queue_resize (GTK_WIDGET (box));
Alexander Larsson's avatar
Alexander Larsson committed
1332
    }
1333 1334
  gtk_list_box_apply_filter (box, row);
  if (gtk_widget_get_visible (GTK_WIDGET (box)))
Alexander Larsson's avatar
Alexander Larsson committed
1335
    {
1336 1337 1338 1339
      next = gtk_list_box_get_next_visible (box, row_priv->iter);
      gtk_list_box_update_header (box, row_priv->iter);
      gtk_list_box_update_header (box, next);
      gtk_list_box_update_header (box, prev_next);
Alexander Larsson's avatar
Alexander Larsson committed
1340 1341 1342
    }
}

Alexander Larsson's avatar
Alexander Larsson committed