gtklistbox.c 110 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"
Matthias Clasen's avatar
Matthias Clasen committed
21 22 23 24 25
#include "gtklistbox.h"
#include "gtkwidget.h"
#include "gtkmarshalers.h"
#include "gtkprivate.h"
#include "gtkintl.h"
Timm Bäder's avatar
Timm Bäder committed
26
#include "gtkwidgetprivate.h"
Matthias Clasen's avatar
Matthias Clasen committed
27

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

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

/**
 * SECTION:gtklistbox
 * @Short_description: A list container
 * @Title: GtkListBox
Alexander Larsson's avatar
Alexander Larsson committed
39
 * @See_also: #GtkScrolledWindow
Alexander Larsson's avatar
Alexander Larsson committed
40
 *
Alexander Larsson's avatar
Alexander Larsson committed
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
 * 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.
 *
56 57 58 59 60
 * #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
61
 * The GtkListBox widget was added in GTK+ 3.10.
Matthias Clasen's avatar
Matthias Clasen committed
62
 */
Alexander Larsson's avatar
Alexander Larsson committed
63

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

69 70
  GtkWidget *placeholder;

Alexander Larsson's avatar
Alexander Larsson committed
71 72 73 74 75 76 77 78
  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;

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

  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;

95 96
  GtkGesture *multipress_gesture;

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

  int n_visible_rows;
101
  gboolean in_widget;
102
} GtkListBoxPrivate;
Alexander Larsson's avatar
Alexander Larsson committed
103

104
typedef struct
Alexander Larsson's avatar
Alexander Larsson committed
105 106
{
  GSequenceIter *iter;
107
  GtkWidget *header;
Alexander Larsson's avatar
Alexander Larsson committed
108 109
  gint y;
  gint height;
110 111 112
  guint visible     :1;
  guint selected    :1;
  guint activatable :1;
113
  guint selectable  :1;
114
} GtkListBoxRowPrivate;
Alexander Larsson's avatar
Alexander Larsson committed
115 116 117 118 119 120 121

enum {
  ROW_SELECTED,
  ROW_ACTIVATED,
  ACTIVATE_CURSOR_ROW,
  TOGGLE_CURSOR_ROW,
  MOVE_CURSOR,
122 123 124
  SELECTED_ROWS_CHANGED,
  SELECT_ALL,
  UNSELECT_ALL,
Alexander Larsson's avatar
Alexander Larsson committed
125 126 127
  LAST_SIGNAL
};

128 129 130 131 132
enum {
  ROW__ACTIVATE,
  ROW__LAST_SIGNAL
};

133
enum {
Alexander Larsson's avatar
Alexander Larsson committed
134 135 136 137 138 139
  PROP_0,
  PROP_SELECTION_MODE,
  PROP_ACTIVATE_ON_SINGLE_CLICK,
  LAST_PROPERTY
};

140 141 142
enum {
  ROW_PROP_0,
  ROW_PROP_ACTIVATABLE,
143
  ROW_PROP_SELECTABLE,
144 145 146
  LAST_ROW_PROPERTY
};

147 148 149
#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)))

150 151 152 153 154 155
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))
156
G_DEFINE_TYPE_WITH_PRIVATE (GtkListBoxRow, gtk_list_box_row, GTK_TYPE_BIN)
Alexander Larsson's avatar
Alexander Larsson committed
157

Matthias Clasen's avatar
Matthias Clasen committed
158 159
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
160
                                                                       GSequenceIter       *iter);
Matthias Clasen's avatar
Matthias Clasen committed
161 162 163
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
164 165 166 167 168 169
                                                                       GtkListBoxRow       *row);
static void                 gtk_list_box_add_move_binding             (GtkBindingSet       *binding_set,
                                                                       guint                keyval,
                                                                       GdkModifierType      modmask,
                                                                       GtkMovementStep      step,
                                                                       gint                 count);
Matthias Clasen's avatar
Matthias Clasen committed
170
static void                 gtk_list_box_update_cursor                (GtkListBox          *box,
Alexander Larsson's avatar
Alexander Larsson committed
171
                                                                       GtkListBoxRow       *row);
Matthias Clasen's avatar
Matthias Clasen committed
172
static void                 gtk_list_box_select_and_activate          (GtkListBox          *box,
Alexander Larsson's avatar
Alexander Larsson committed
173
                                                                       GtkListBoxRow       *row);
Matthias Clasen's avatar
Matthias Clasen committed
174
static void                 gtk_list_box_update_prelight              (GtkListBox          *box,
Alexander Larsson's avatar
Alexander Larsson committed
175
                                                                       GtkListBoxRow       *row);
Matthias Clasen's avatar
Matthias Clasen committed
176
static void                 gtk_list_box_update_active                (GtkListBox          *box,
Alexander Larsson's avatar
Alexander Larsson committed
177
                                                                       GtkListBoxRow       *row);
178
static gboolean             gtk_list_box_enter_notify_event           (GtkWidget           *widget,
Alexander Larsson's avatar
Alexander Larsson committed
179
                                                                       GdkEventCrossing    *event);
180
static gboolean             gtk_list_box_leave_notify_event           (GtkWidget           *widget,
Alexander Larsson's avatar
Alexander Larsson committed
181
                                                                       GdkEventCrossing    *event);
182
static gboolean             gtk_list_box_motion_notify_event          (GtkWidget           *widget,
Alexander Larsson's avatar
Alexander Larsson committed
183
                                                                       GdkEventMotion      *event);
184 185
static void                 gtk_list_box_show                         (GtkWidget           *widget);
static gboolean             gtk_list_box_focus                        (GtkWidget           *widget,
Alexander Larsson's avatar
Alexander Larsson committed
186
                                                                       GtkDirectionType     direction);
Matthias Clasen's avatar
Matthias Clasen committed
187 188 189 190
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);
191
static gboolean             gtk_list_box_draw                         (GtkWidget           *widget,
Alexander Larsson's avatar
Alexander Larsson committed
192
                                                                       cairo_t             *cr);
193 194
static void                 gtk_list_box_realize                      (GtkWidget           *widget);
static void                 gtk_list_box_add                          (GtkContainer        *container,
Alexander Larsson's avatar
Alexander Larsson committed
195
                                                                       GtkWidget           *widget);
196
static void                 gtk_list_box_remove                       (GtkContainer        *container,
Alexander Larsson's avatar
Alexander Larsson committed
197
                                                                       GtkWidget           *widget);
198
static void                 gtk_list_box_forall_internal              (GtkContainer        *container,
Alexander Larsson's avatar
Alexander Larsson committed
199 200
                                                                       gboolean             include_internals,
                                                                       GtkCallback          callback,
Matthias Clasen's avatar
Matthias Clasen committed
201
                                                                       gpointer             callback_target);
202
static void                 gtk_list_box_compute_expand_internal      (GtkWidget           *widget,
Alexander Larsson's avatar
Alexander Larsson committed
203 204
                                                                       gboolean            *hexpand,
                                                                       gboolean            *vexpand);
205 206 207
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
208
                                                                       GtkAllocation       *allocation);
209
static void                 gtk_list_box_drag_leave                   (GtkWidget           *widget,
Alexander Larsson's avatar
Alexander Larsson committed
210 211
                                                                       GdkDragContext      *context,
                                                                       guint                time_);
Matthias Clasen's avatar
Matthias Clasen committed
212 213 214
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
215 216 217
                                                                       GtkMovementStep      step,
                                                                       gint                 count);
static void                 gtk_list_box_finalize                     (GObject             *obj);
218
static void                 gtk_list_box_parent_set                   (GtkWidget           *widget,
219
                                                                       GtkWidget           *prev_parent);
Alexander Larsson's avatar
Alexander Larsson committed
220

221 222 223 224 225 226 227 228 229 230 231 232 233 234
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
235

236 237 238 239 240 241 242 243 244
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);
Matthias Clasen's avatar
Matthias Clasen committed
245
static void                 gtk_list_box_selected_rows_changed          (GtkListBox          *box);
246

247 248 249 250 251 252 253 254 255 256 257
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);

258 259 260
static void gtk_list_box_update_row_styles (GtkListBox    *box);
static void gtk_list_box_update_row_style  (GtkListBox    *box,
                                            GtkListBoxRow *row);
261

Alexander Larsson's avatar
Alexander Larsson committed
262 263
static GParamSpec *properties[LAST_PROPERTY] = { NULL, };
static guint signals[LAST_SIGNAL] = { 0 };
264
static GParamSpec *row_properties[LAST_ROW_PROPERTY] = { NULL, };
265
static guint row_signals[ROW__LAST_SIGNAL] = { 0 };
Alexander Larsson's avatar
Alexander Larsson committed
266

Alexander Larsson's avatar
Alexander Larsson committed
267 268 269 270 271 272 273 274 275
/**
 * gtk_list_box_new:
 *
 * Creates a new #GtkListBox container.
 *
 * Returns: a new #GtkListBox
 *
 * Since: 3.10
 */
Alexander Larsson's avatar
Alexander Larsson committed
276 277 278 279 280 281 282
GtkWidget *
gtk_list_box_new (void)
{
  return g_object_new (GTK_TYPE_LIST_BOX, NULL);
}

static void
Matthias Clasen's avatar
Matthias Clasen committed
283
gtk_list_box_init (GtkListBox *box)
Alexander Larsson's avatar
Alexander Larsson committed
284
{
Matthias Clasen's avatar
Matthias Clasen committed
285 286
  GtkListBoxPrivate *priv = BOX_PRIV (box);
  GtkWidget *widget = GTK_WIDGET (box);
287
  GtkStyleContext *context;
Alexander Larsson's avatar
Alexander Larsson committed
288

Matthias Clasen's avatar
Matthias Clasen committed
289 290
  gtk_widget_set_has_window (widget, TRUE);
  gtk_widget_set_redraw_on_allocate (widget, TRUE);
Alexander Larsson's avatar
Alexander Larsson committed
291 292 293 294
  priv->selection_mode = GTK_SELECTION_SINGLE;
  priv->activate_single_click = TRUE;

  priv->children = g_sequence_new (NULL);
295
  priv->header_hash = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL);
296

Matthias Clasen's avatar
Matthias Clasen committed
297
  context = gtk_widget_get_style_context (widget);
298
  gtk_style_context_add_class (context, GTK_STYLE_CLASS_LIST);
299 300 301 302 303 304 305 306 307 308 309 310

  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
311 312 313 314 315 316 317 318
}

static void
gtk_list_box_get_property (GObject    *obj,
                           guint       property_id,
                           GValue     *value,
                           GParamSpec *pspec)
{
Matthias Clasen's avatar
Matthias Clasen committed
319
  GtkListBoxPrivate *priv = BOX_PRIV (obj);
Alexander Larsson's avatar
Alexander Larsson committed
320 321 322 323

  switch (property_id)
    {
    case PROP_SELECTION_MODE:
324
      g_value_set_enum (value, priv->selection_mode);
Alexander Larsson's avatar
Alexander Larsson committed
325 326
      break;
    case PROP_ACTIVATE_ON_SINGLE_CLICK:
327
      g_value_set_boolean (value, priv->activate_single_click);
Alexander Larsson's avatar
Alexander Larsson committed
328 329 330 331 332 333 334 335 336 337 338 339 340
      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)
{
Matthias Clasen's avatar
Matthias Clasen committed
341
  GtkListBox *box = GTK_LIST_BOX (obj);
Alexander Larsson's avatar
Alexander Larsson committed
342 343 344 345

  switch (property_id)
    {
    case PROP_SELECTION_MODE:
Matthias Clasen's avatar
Matthias Clasen committed
346
      gtk_list_box_set_selection_mode (box, g_value_get_enum (value));
Alexander Larsson's avatar
Alexander Larsson committed
347 348
      break;
    case PROP_ACTIVATE_ON_SINGLE_CLICK:
Matthias Clasen's avatar
Matthias Clasen committed
349
      gtk_list_box_set_activate_on_single_click (box, g_value_get_boolean (value));
Alexander Larsson's avatar
Alexander Larsson committed
350 351 352 353 354 355 356 357 358 359
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec);
      break;
    }
}

static void
gtk_list_box_finalize (GObject *obj)
{
Matthias Clasen's avatar
Matthias Clasen committed
360
  GtkListBoxPrivate *priv = BOX_PRIV (obj);
Alexander Larsson's avatar
Alexander Larsson committed
361 362 363 364 365

  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);
366 367
  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
368 369 370 371 372

  g_clear_object (&priv->adjustment);
  g_clear_object (&priv->drag_highlighted_row);

  g_sequence_free (priv->children);
373
  g_hash_table_unref (priv->header_hash);
Alexander Larsson's avatar
Alexander Larsson committed
374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390

  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;
391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413
  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;
  widget_class->compute_expand = gtk_list_box_compute_expand_internal;
  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;
  container_class->forall = gtk_list_box_forall_internal;
  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;
414 415 416
  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
417 418 419

  properties[PROP_SELECTION_MODE] =
    g_param_spec_enum ("selection-mode",
Matthias Clasen's avatar
Matthias Clasen committed
420 421
                       P_("Selection mode"),
                       P_("The selection mode"),
Alexander Larsson's avatar
Alexander Larsson committed
422 423
                       GTK_TYPE_SELECTION_MODE,
                       GTK_SELECTION_SINGLE,
424
                       G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
Alexander Larsson's avatar
Alexander Larsson committed
425 426 427

  properties[PROP_ACTIVATE_ON_SINGLE_CLICK] =
    g_param_spec_boolean ("activate-on-single-click",
Matthias Clasen's avatar
Matthias Clasen committed
428 429
                          P_("Activate on Single Click"),
                          P_("Activate row on a single click"),
Alexander Larsson's avatar
Alexander Larsson committed
430
                          TRUE,
431
                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
Alexander Larsson's avatar
Alexander Larsson committed
432 433 434

  g_object_class_install_properties (object_class, LAST_PROPERTY, properties);

Alexander Larsson's avatar
Alexander Larsson committed
435 436
  /**
   * GtkListBox::row-selected:
Matthias Clasen's avatar
Matthias Clasen committed
437
   * @box: the #GtkListBox
438
   * @row: (nullable): the selected row
Alexander Larsson's avatar
Alexander Larsson committed
439 440 441 442
   *
   * The ::row-selected signal is emitted when a new row is selected, or
   * (with a %NULL @row) when the selection is cleared.
   *
443 444 445
   * 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.
446
   *
Alexander Larsson's avatar
Alexander Larsson committed
447 448
   * Since: 3.10
   */
Alexander Larsson's avatar
Alexander Larsson committed
449 450 451 452 453 454 455 456
  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
457
                  GTK_TYPE_LIST_BOX_ROW);
Matthias Clasen's avatar
Matthias Clasen committed
458

459 460
  /**
   * GtkListBox::selected-rows-changed:
Matthias Clasen's avatar
Matthias Clasen committed
461
   * @box: the #GtkListBox on wich the signal is emitted
462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515
   *
   * 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
516 517
  /**
   * GtkListBox::row-activated:
Matthias Clasen's avatar
Matthias Clasen committed
518
   * @box: the #GtkListBox
Alexander Larsson's avatar
Alexander Larsson committed
519 520
   * @row: the activated row
   *
Matthias Clasen's avatar
Matthias Clasen committed
521
   * The ::row-activated signal is emitted when a row has been activated by the user.
Alexander Larsson's avatar
Alexander Larsson committed
522 523 524
   *
   * Since: 3.10
   */
Alexander Larsson's avatar
Alexander Larsson committed
525 526 527 528 529 530 531 532
  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
533
                  GTK_TYPE_LIST_BOX_ROW);
Alexander Larsson's avatar
Alexander Larsson committed
534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570
  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);
571
  gtk_list_box_add_move_binding (binding_set, GDK_KEY_Up, 0,
Alexander Larsson's avatar
Alexander Larsson committed
572
                                 GTK_MOVEMENT_DISPLAY_LINES, -1);
573
  gtk_list_box_add_move_binding (binding_set, GDK_KEY_KP_Up, 0,
Alexander Larsson's avatar
Alexander Larsson committed
574
                                 GTK_MOVEMENT_DISPLAY_LINES, -1);
575
  gtk_list_box_add_move_binding (binding_set, GDK_KEY_Down, 0,
Alexander Larsson's avatar
Alexander Larsson committed
576
                                 GTK_MOVEMENT_DISPLAY_LINES, 1);
577
  gtk_list_box_add_move_binding (binding_set, GDK_KEY_KP_Down, 0,
Alexander Larsson's avatar
Alexander Larsson committed
578 579 580 581 582 583 584 585 586
                                 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);
587

Alexander Larsson's avatar
Alexander Larsson committed
588 589
  gtk_binding_entry_add_signal (binding_set, GDK_KEY_space, GDK_CONTROL_MASK,
                                "toggle-cursor-row", 0, NULL);
590 591 592 593 594 595 596
  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
597 598 599 600
}

/**
 * gtk_list_box_get_selected_row:
Matthias Clasen's avatar
Matthias Clasen committed
601
 * @box: a #GtkListBox
Alexander Larsson's avatar
Alexander Larsson committed
602 603 604
 *
 * Gets the selected row.
 *
Matthias Clasen's avatar
Matthias Clasen committed
605 606 607 608 609
 * 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
610 611
 *
 * Since: 3.10
Matthias Clasen's avatar
Matthias Clasen committed
612
 */
Alexander Larsson's avatar
Alexander Larsson committed
613
GtkListBoxRow *
Matthias Clasen's avatar
Matthias Clasen committed
614
gtk_list_box_get_selected_row (GtkListBox *box)
Alexander Larsson's avatar
Alexander Larsson committed
615
{
Matthias Clasen's avatar
Matthias Clasen committed
616
  g_return_val_if_fail (GTK_IS_LIST_BOX (box), NULL);
Alexander Larsson's avatar
Alexander Larsson committed
617

Matthias Clasen's avatar
Matthias Clasen committed
618
  return BOX_PRIV (box)->selected_row;
Alexander Larsson's avatar
Alexander Larsson committed
619 620 621 622
}

/**
 * gtk_list_box_get_row_at_index:
Matthias Clasen's avatar
Matthias Clasen committed
623
 * @box: a #GtkListBox
Matthias Clasen's avatar
Matthias Clasen committed
624
 * @index_: the index of the row
Alexander Larsson's avatar
Alexander Larsson committed
625
 *
Matthias Clasen's avatar
Matthias Clasen committed
626
 * Gets the n-th child in the list (not counting headers).
627 628
 * If @_index is negative or larger than the number of items in the
 * list, %NULL is returned.
Alexander Larsson's avatar
Alexander Larsson committed
629
 *
630
 * Returns: (transfer none): the child #GtkWidget or %NULL
Alexander Larsson's avatar
Alexander Larsson committed
631 632
 *
 * Since: 3.10
Matthias Clasen's avatar
Matthias Clasen committed
633
 */
Alexander Larsson's avatar
Alexander Larsson committed
634
GtkListBoxRow *
Matthias Clasen's avatar
Matthias Clasen committed
635
gtk_list_box_get_row_at_index (GtkListBox *box,
Matthias Clasen's avatar
Matthias Clasen committed
636
                               gint        index_)
Alexander Larsson's avatar
Alexander Larsson committed
637 638 639
{
  GSequenceIter *iter;

Matthias Clasen's avatar
Matthias Clasen committed
640
  g_return_val_if_fail (GTK_IS_LIST_BOX (box), NULL);
641

Matthias Clasen's avatar
Matthias Clasen committed
642
  iter = g_sequence_get_iter_at_pos (BOX_PRIV (box)->children, index_);
643
  if (!g_sequence_iter_is_end (iter))
Alexander Larsson's avatar
Alexander Larsson committed
644 645 646 647 648 649 650
    return g_sequence_get (iter);

  return NULL;
}

/**
 * gtk_list_box_get_row_at_y:
Matthias Clasen's avatar
Matthias Clasen committed
651
 * @box: a #GtkListBox
Alexander Larsson's avatar
Alexander Larsson committed
652 653
 * @y: position
 *
Alexander Larsson's avatar
Alexander Larsson committed
654
 * Gets the row at the @y position.
Alexander Larsson's avatar
Alexander Larsson committed
655
 *
656
 * Returns: (transfer none): the row
Alexander Larsson's avatar
Alexander Larsson committed
657 658
 *
 * Since: 3.10
Matthias Clasen's avatar
Matthias Clasen committed
659
 */
Alexander Larsson's avatar
Alexander Larsson committed
660
GtkListBoxRow *
Matthias Clasen's avatar
Matthias Clasen committed
661
gtk_list_box_get_row_at_y (GtkListBox *box,
Matthias Clasen's avatar
Matthias Clasen committed
662
                           gint        y)
Alexander Larsson's avatar
Alexander Larsson committed
663 664 665 666 667
{
  GtkListBoxRow *row, *found_row;
  GtkListBoxRowPrivate *row_priv;
  GSequenceIter *iter;

Matthias Clasen's avatar
Matthias Clasen committed
668
  g_return_val_if_fail (GTK_IS_LIST_BOX (box), NULL);
Alexander Larsson's avatar
Alexander Larsson committed
669 670 671 672

  /* TODO: This should use g_sequence_search */

  found_row = NULL;
Matthias Clasen's avatar
Matthias Clasen committed
673
  for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
Alexander Larsson's avatar
Alexander Larsson committed
674 675 676 677
       !g_sequence_iter_is_end (iter);
       iter = g_sequence_iter_next (iter))
    {
      row = (GtkListBoxRow*) g_sequence_get (iter);
Matthias Clasen's avatar
Matthias Clasen committed
678
      row_priv = ROW_PRIV (row);
Alexander Larsson's avatar
Alexander Larsson committed
679 680 681 682 683 684 685 686 687 688 689 690
      if (y >= row_priv->y && y < (row_priv->y + row_priv->height))
        {
          found_row = row;
          break;
        }
    }

  return found_row;
}

/**
 * gtk_list_box_select_row:
Matthias Clasen's avatar
Matthias Clasen committed
691
 * @box: a #GtkListBox
Alexander Larsson's avatar
Alexander Larsson committed
692
 * @row: (allow-none): The row to select or %NULL
Alexander Larsson's avatar
Alexander Larsson committed
693 694 695 696
 *
 * Make @row the currently selected row.
 *
 * Since: 3.10
Alexander Larsson's avatar
Alexander Larsson committed
697 698
 */
void
Matthias Clasen's avatar
Matthias Clasen committed
699
gtk_list_box_select_row (GtkListBox    *box,
Matthias Clasen's avatar
Matthias Clasen committed
700
                         GtkListBoxRow *row)
Alexander Larsson's avatar
Alexander Larsson committed
701
{
702 703
  gboolean dirty = FALSE;

Matthias Clasen's avatar
Matthias Clasen committed
704
  g_return_if_fail (GTK_IS_LIST_BOX (box));
705
  g_return_if_fail (row == NULL || GTK_IS_LIST_BOX_ROW (row));
Alexander Larsson's avatar
Alexander Larsson committed
706

707
  if (row)
Matthias Clasen's avatar
Matthias Clasen committed
708
    gtk_list_box_select_row_internal (box, row);
709
  else
710 711 712 713 714 715 716
    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
717 718
}

719 720
/**   
 * gtk_list_box_unselect_row:
Matthias Clasen's avatar
Matthias Clasen committed
721
 * @box: a #GtkListBox
722 723
 * @row: the row to unselected
 *
Matthias Clasen's avatar
Matthias Clasen committed
724
 * Unselects a single row of @box, if the selection mode allows it.
725 726 727 728
 *
 * Since: 3.14
 */                       
void
Matthias Clasen's avatar
Matthias Clasen committed
729
gtk_list_box_unselect_row (GtkListBox    *box,
730 731
                           GtkListBoxRow *row)
{
Matthias Clasen's avatar
Matthias Clasen committed
732
  g_return_if_fail (GTK_IS_LIST_BOX (box));
733 734
  g_return_if_fail (GTK_IS_LIST_BOX_ROW (row));
  
Matthias Clasen's avatar
Matthias Clasen committed
735
  gtk_list_box_unselect_row_internal (box, row);
736 737 738 739
} 

/**
 * gtk_list_box_select_all:
Matthias Clasen's avatar
Matthias Clasen committed
740
 * @box: a #GtkListBox
741
 *
Matthias Clasen's avatar
Matthias Clasen committed
742
 * Select all children of @box, if the selection mode allows it.
743 744 745 746
 *
 * Since: 3.14
 */
void
Matthias Clasen's avatar
Matthias Clasen committed
747
gtk_list_box_select_all (GtkListBox *box)
748
{
Matthias Clasen's avatar
Matthias Clasen committed
749
  g_return_if_fail (GTK_IS_LIST_BOX (box));
750

Matthias Clasen's avatar
Matthias Clasen committed
751
  if (BOX_PRIV (box)->selection_mode != GTK_SELECTION_MULTIPLE)
752 753
    return;

Matthias Clasen's avatar
Matthias Clasen committed
754
  if (g_sequence_get_length (BOX_PRIV (box)->children) > 0)
755
    {
Matthias Clasen's avatar
Matthias Clasen committed
756 757
      gtk_list_box_select_all_between (box, NULL, NULL, FALSE);
      g_signal_emit (box, signals[SELECTED_ROWS_CHANGED], 0);
758
    }
759 760 761 762
}

/**
 * gtk_list_box_unselect_all:
Matthias Clasen's avatar
Matthias Clasen committed
763
 * @box: a #GtkListBox
764
 *
Matthias Clasen's avatar
Matthias Clasen committed
765
 * Unselect all children of @box, if the selection mode allows it.
766 767 768 769
 *
 * Since: 3.14
 */
void
Matthias Clasen's avatar
Matthias Clasen committed
770
gtk_list_box_unselect_all (GtkListBox *box)
771
{
772 773
  gboolean dirty = FALSE;

Matthias Clasen's avatar
Matthias Clasen committed
774
  g_return_if_fail (GTK_IS_LIST_BOX (box));
775

Matthias Clasen's avatar
Matthias Clasen committed
776
  if (BOX_PRIV (box)->selection_mode == GTK_SELECTION_BROWSE)
777 778
    return;

Matthias Clasen's avatar
Matthias Clasen committed
779
  dirty = gtk_list_box_unselect_all_internal (box);
780 781

  if (dirty)
782 783 784 785
    {
      g_signal_emit (box, signals[ROW_SELECTED], 0, NULL);
      g_signal_emit (box, signals[SELECTED_ROWS_CHANGED], 0);
    }
786 787 788
}

static void
Matthias Clasen's avatar
Matthias Clasen committed
789
gtk_list_box_selected_rows_changed (GtkListBox *box)
790
{
Matthias Clasen's avatar
Matthias Clasen committed
791
  _gtk_list_box_accessible_selection_changed (box);
792 793 794 795
}

/**
 * GtkListBoxForeachFunc:
Matthias Clasen's avatar
Matthias Clasen committed
796
 * @box: a #GtkListBox
797 798 799 800
 * @row: a #GtkListBoxRow
 * @user_data: (closure): user data
 *
 * A function used by gtk_list_box_selected_foreach().
Matthias Clasen's avatar
Matthias Clasen committed
801
 * It will be called on every selected child of the @box.
802 803 804 805 806 807
 *
 * Since: 3.14
 */

/**
 * gtk_list_box_selected_foreach:
Matthias Clasen's avatar
Matthias Clasen committed
808
 * @box: a #GtkListBox
809 810 811 812 813
 * @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.
 *
Matthias Clasen's avatar
Matthias Clasen committed
814
 * Note that the selection cannot be modified from within this function.
815 816 817 818
 *
 * Since: 3.14
 */
void
Matthias Clasen's avatar
Matthias Clasen committed
819
gtk_list_box_selected_foreach (GtkListBox            *box,
820 821 822 823 824 825
                               GtkListBoxForeachFunc  func,
                               gpointer               data)
{
  GtkListBoxRow *row;
  GSequenceIter *iter;

Matthias Clasen's avatar
Matthias Clasen committed
826
  g_return_if_fail (GTK_IS_LIST_BOX (box));
827

Matthias Clasen's avatar
Matthias Clasen committed
828
  for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
829 830 831 832 833
       !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))
Matthias Clasen's avatar
Matthias Clasen committed
834
        (*func) (box, row, data);
835 836 837 838
    }
}

/**
839
 * gtk_list_box_get_selected_rows:
Matthias Clasen's avatar
Matthias Clasen committed
840
 * @box: a #GtkListBox
841 842 843 844 845 846 847 848 849 850
 *
 * 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 *
851
gtk_list_box_get_selected_rows (GtkListBox *box)
852 853 854 855 856
{
  GtkListBoxRow *row;
  GSequenceIter *iter;
  GList *selected = NULL;

Matthias Clasen's avatar
Matthias Clasen committed
857
  g_return_val_if_fail (GTK_IS_LIST_BOX (box), NULL);
858

Matthias Clasen's avatar
Matthias Clasen committed
859
  for (iter = g_sequence_get_begin_iter (BOX_PRIV (box)->children);
860 861 862 863 864 865 866 867 868 869
       !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);
}
870 871 872

/**
 * gtk_list_box_set_placeholder:
Matthias Clasen's avatar
Matthias Clasen committed
873
 * @box: a #GtkListBox
874 875 876
 * @placeholder: (allow-none): a #GtkWidget or %NULL
 *
 * Sets the placeholder widget that is shown in the list when
Matthias Clasen's avatar
Matthias Clasen committed
877
 * it doesn't display any visible children.
878 879 880 881
 *
 * Since: 3.10
 */
void
Matthias Clasen's avatar
Matthias Clasen committed
882
gtk_list_box_set_placeholder (GtkListBox *box,
Matthias Clasen's avatar
Matthias Clasen committed
883
                              GtkWidget  *placeholder)
884
{
Matthias Clasen's avatar
Matthias Clasen committed
885
  GtkListBoxPrivate *priv = BOX_PRIV (box);
886

Matthias Clasen's avatar
Matthias Clasen committed
887
  g_return_if_fail (GTK_IS_LIST_BOX (box));
888 889 890 891

  if (priv->placeholder)
    {
      gtk_widget_unparent (priv->placeholder);
Matthias Clasen's avatar
Matthias Clasen committed
892
      gtk_widget_queue_resize (GTK_WIDGET (box));
893 894 895 896 897 898
    }

  priv->placeholder = placeholder;

  if (placeholder)
    {
Matthias Clasen's avatar
Matthias Clasen committed
899
      gtk_widget_set_parent (GTK_WIDGET (placeholder), GTK_WIDGET (box));
900 901 902 903 904 905
      gtk_widget_set_child_visible (GTK_WIDGET (placeholder),
                                    priv->n_visible_rows == 0);
    }
}


Alexander Larsson's avatar
Alexander Larsson committed
906 907
/**
 * gtk_list_box_set_adjustment:
Matthias Clasen's avatar
Matthias Clasen committed
908
 * @box: a #GtkListBox
Matthias Clasen's avatar
Matthias Clasen committed
909
 * @adjustment: (allow-none): the adjustment, or %NULL
Alexander Larsson's avatar
Alexander Larsson committed
910 911 912 913 914
 *
 * 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.
 *
Matthias Clasen's avatar
Matthias Clasen committed
915
 * In the normal case when the @box is packed inside
Alexander Larsson's avatar
Alexander Larsson committed
916 917 918 919 920 921
 * 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
922
void
Matthias Clasen's avatar
Matthias Clasen committed
923
gtk_list_box_set_adjustment (GtkListBox    *box,
Alexander Larsson's avatar
Alexander Larsson committed
924 925
                             GtkAdjustment *adjustment)
{
Matthias Clasen's avatar
Matthias Clasen committed
926
  GtkListBoxPrivate *priv = BOX_PRIV (box);
927

Matthias Clasen's avatar
Matthias Clasen committed
928
  g_return_if_fail (GTK_IS_LIST_BOX (box));
Alexander Larsson's avatar
Alexander Larsson committed
929

930
  g_object_ref_sink (adjustment);
Alexander Larsson's avatar
Alexander Larsson committed
931 932 933 934 935
  if (priv->adjustment)
    g_object_unref (priv->adjustment);
  priv->adjustment = adjustment;
}

Alexander Larsson's avatar
Alexander Larsson committed
936 937
/**
 * gtk_list_box_get_adjustment:
Matthias Clasen's avatar
Matthias Clasen committed
938
 * @box: a #GtkListBox
Alexander Larsson's avatar
Alexander Larsson committed
939 940 941 942
 *
 * Gets the adjustment (if any) that the widget uses to
 * for vertical scrolling.
 *
943
 * Returns: (transfer none): the adjustment
Alexander Larsson's avatar
Alexander Larsson committed
944 945 946
 *
 * Since: 3.10
 */
Alexander Larsson's avatar
Alexander Larsson committed
947
GtkAdjustment *
Matthias Clasen's avatar
Matthias Clasen committed
948
gtk_list_box_get_adjustment (GtkListBox *box)
Alexander Larsson's avatar
Alexander Larsson committed
949
{
Matthias Clasen's avatar
Matthias Clasen committed
950
  g_return_val_if_fail (GTK_IS_LIST_BOX (box), NULL);
Alexander Larsson's avatar
Alexander Larsson committed
951

Matthias Clasen's avatar
Matthias Clasen committed
952
  return BOX_PRIV (box)->adjustment;
Alexander Larsson's avatar
Alexander Larsson committed
953 954
}

955
static void
956 957
gtk_list_box_parent_set (GtkWidget *widget,
                         GtkWidget *prev_parent)
Alexander Larsson's avatar
Alexander Larsson committed
958
{
959 960
  GtkWidget *parent;
  GtkAdjustment *adjustment;
Alexander Larsson's avatar
Alexander Larsson committed
961

962
  parent = gtk_widget_get_parent (widget);
Alexander Larsson's avatar
Alexander Larsson committed
963

964 965 966 967 968 969
  if (parent && GTK_IS_SCROLLABLE (parent))
    {
      adjustment = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (parent));
      gtk_list_box_set_adjustment (GTK_LIST_BOX (widget), adjustment);
    }
}
Alexander Larsson's avatar
Alexander Larsson committed
970

Alexander Larsson's avatar
Alexander Larsson committed
971 972
/**
 * gtk_list_box_set_selection_mode:
Matthias Clasen's avatar
Matthias Clasen committed
973
 * @box: a #GtkListBox
Alexander Larsson's avatar
Alexander Larsson committed
974 975 976 977 978 979 980
 * @mode: The #GtkSelectionMode
 *
 * Sets how selection works in the listbox.
 * See #GtkSelectionMode for details.
 *
 * Since: 3.10
 */
Alexander Larsson's avatar
Alexander Larsson committed
981
void
Matthias Clasen's avatar
Matthias Clasen committed
982
gtk_list_box_set_selection_mode (GtkListBox       *box,
Matthias Clasen's avatar
Matthias Clasen committed
983
                                 GtkSelectionMode  mode)
Alexander Larsson's avatar
Alexander Larsson committed
984
{
Matthias Clasen's avatar
Matthias Clasen committed
985
  GtkListBoxPrivate *priv = BOX_PRIV (box);
986
  gboolean dirty = FALSE;
987

Matthias Clasen's avatar
Matthias Clasen committed
988
  g_return_if_fail (GTK_IS_LIST_BOX (box));
Alexander Larsson's avatar
Alexander Larsson committed
989

990 991
  if (priv->selection_mode == mode)
    return;
Alexander Larsson's avatar
Alexander Larsson committed
992

993 994
  if (mode == GTK_SELECTION_NONE ||
      priv->selection_mode == GTK_SELECTION_MULTIPLE)
Alexander Larsson's avatar
Alexander Larsson committed
995
    {
Matthias Clasen's avatar
Matthias Clasen committed
996
      dirty = gtk_list_box_unselect_all_internal (box);
997
      priv->selected_row = NULL;
Alexander Larsson's avatar
Alexander Larsson committed
998 999 1000 1001
    }

  priv->selection_mode = mode;

1002 1003
  gtk_list_box_update_row_styles (box);

Matthias Clasen's avatar
Matthias Clasen committed
1004
  g_object_notify_by_pspec (G_OBJECT (box), properties[PROP_SELECTION_MODE]);
1005 1006

  if (dirty)
1007 1008 1009 1010
    {
      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
1011 1012
}

Alexander Larsson's avatar
Alexander Larsson committed
1013 1014
/**
 * gtk_list_box_get_selection_mode:
Matthias Clasen's avatar
Matthias Clasen committed
1015
 * @box: a #GtkListBox
Alexander Larsson's avatar
Alexander Larsson committed
1016 1017 1018 1019 1020 1021 1022
 *
 * Gets the selection mode of the listbox.
 *
 * Returns: a #GtkSelectionMode
 *
 * Since: 3.10
 */
Alexander Larsson's avatar
Alexander Larsson committed
1023
GtkSelectionMode
Matthias Clasen's avatar
Matthias Clasen committed
1024
gtk_list_box_get_selection_mode (GtkListBox *box)
Alexander Larsson's avatar
Alexander Larsson committed
1025
{
Matthias Clasen's avatar
Matthias Clasen committed
1026
  g_return_val_if_fail (GTK_IS_LIST_BOX (box), GTK_SELECTION_NONE);
1027

Matthias Clasen's avatar
Matthias Clasen committed
1028
  return BOX_PRIV (box)->selection_mode;
Alexander Larsson's avatar
Alexander Larsson committed
1029 1030 1031 1032
}

/**
 * gtk_list_box_set_filter_func:
Matthias Clasen's avatar
Matthias Clasen committed
1033
 * @box: a #GtkListBox
Matthias Clasen's avatar
Matthias Clasen committed
1034
 * @filter_func: (closure user_data) (allow-none): callback that lets you filter which rows to show
Alexander Larsson's avatar
Alexander Larsson committed
1035 1036 1037
 * @user_data: user data passed to @filter_func
 * @destroy: destroy notifier for @user_data
 *
Matthias Clasen's avatar
Matthias Clasen committed
1038
 * By setting a filter function on the @box one can decide dynamically which
Alexander Larsson's avatar
Alexander Larsson committed
1039 1040 1041 1042 1043
 * 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
1044
 * when gtk_list_box_invalidate_filter() is called.
Alexander Larsson's avatar
Alexander Larsson committed
1045 1046
 *
 * Since: 3.10
Alexander Larsson's avatar
Alexander Larsson committed
1047 1048
 */
void
Matthias Clasen's avatar
Matthias Clasen committed
1049
gtk_list_box_set_filter_func (GtkListBox           *box,
Matthias Clasen's avatar
Matthias Clasen committed
1050 1051 1052
                              GtkListBoxFilterFunc  filter_func,
                              gpointer              user_data,
                              GDestroyNotify        destroy)
Alexander Larsson's avatar
Alexander Larsson committed
1053
{
Matthias Clasen's avatar
Matthias Clasen committed
1054
  GtkListBoxPrivate *priv = BOX_PRIV (box);
Alexander Larsson's avatar
Alexander Larsson committed
1055

Matthias Clasen's avatar
Matthias Clasen committed
1056
  g_return_if_fail (GTK_IS_LIST_BOX (box));
Alexander Larsson's avatar
Alexander Larsson committed
1057 1058 1059 1060 1061 1062 1063 1064

  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;

Matthias Clasen's avatar
Matthias Clasen committed
1065
  gtk_list_box_invalidate_filter (box);
Alexander Larsson's avatar
Alexander Larsson committed
1066 1067 1068
}

/**
1069
 * gtk_list_box_set_header_func:
Matthias Clasen's avatar
Matthias Clasen committed
1070
 * @box: a #GtkListBox
Matthias Clasen's avatar
Matthias Clasen committed
1071
 * @update_header: (closure user_data) (allow-none): callback that lets you add row headers
1072
 * @user_data: user data passed to @update_header
Alexander Larsson's avatar
Alexander Larsson committed
1073 1074
 * @destroy: destroy notifier for @user_data
 *
Matthias Clasen's avatar
Matthias Clasen committed
1075
 * By setting a header function on the @box one can dynamically add headers
Alexander Larsson's avatar
Alexander Larsson committed
1076 1077 1078 1079
 * 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.
 *
1080
 * The @update_header can look at the current header widget using gtk_list_box_row_get_header()
Alexander Larsson's avatar
Alexander Larsson committed
1081
 * and either update the state of the widget as needed, or set a new one using
1082
 * gtk_list_box_row_set_header(). If no header is needed, set the header to %NULL.
Alexander Larsson's avatar
Alexander Larsson committed
1083
 *
1084
 * Note that you may get many calls @update_header to this for a particular row when e.g.
1085
 * changing things that don’t affect the header. In this case it is important for performance
Matthias Clasen's avatar
Matthias Clasen committed
1086
 * to not blindly replace an existing header with an identical one.
Alexander Larsson's avatar
Alexander Larsson committed
1087
 *
1088
 * The @update_header function will be called for each row after the call, and it will
Alexander Larsson's avatar
Alexander Larsson committed
1089 1090 1091
 * 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
1092
 * gtk_list_box_invalidate_headers() is called.
Alexander Larsson's avatar
Alexander Larsson committed
1093 1094
 *
 * Since: 3.10
Alexander Larsson's avatar
Alexander Larsson committed
1095 1096
 */
void
Matthias Clasen's avatar
Matthias Clasen committed
1097
gtk_list_box_set_header_func (GtkListBox                 *box,
Matthias Clasen's avatar
Matthias Clasen committed
1098 1099 1100
                              GtkListBoxUpdateHeaderFunc  update_header,
                              gpointer                    user_data,
                              GDestroyNotify              destroy)
Alexander Larsson's avatar
Alexander Larsson committed
1101
{
Matthias Clasen's avatar
Matthias Clasen committed
1102
  GtkListBoxPrivate *priv = BOX_PRIV (box);
Alexander Larsson's avatar
Alexander Larsson committed
1103

Matthias Clasen's avatar
Matthias Clasen committed
1104
  g_return_if_fail (GTK_IS_LIST_BOX (box));
Alexander Larsson's avatar
Alexander Larsson committed
1105

1106 1107
  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
1108

1109 1110 1111
  priv->update_header_func = update_header;
  priv->update_header_func_target = user_data;
  priv->update_header_func_target_destroy_notify = destroy;
Matthias Clasen's avatar
Matthias Clasen committed
1112
  gtk_list_box_invalidate_headers (box);
Alexander Larsson's avatar
Alexander Larsson committed
1113 1114
}

Alexander Larsson's avatar
Alexander Larsson committed
1115
/**
1116
 * gtk_list_box_invalidate_filter:
Matthias Clasen's avatar
Matthias Clasen committed
1117
 * @box: a #GtkListBox
Alexander Larsson's avatar
Alexander Larsson committed
1118 1119
 *
 * Update the filtering for all rows. Call this when result
Matthias Clasen's avatar
Matthias Clasen committed
1120
 * of the filter function on the @box is changed due
Alexander Larsson's avatar
Alexander Larsson committed
1121 1122 1123 1124 1125 1126
 * 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
1127
void
Matthias Clasen's avatar
Matthias Clasen committed
1128
gtk_list_box_invalidate_filter (GtkListBox *box)
Alexander Larsson's avatar
Alexander Larsson committed
1129
{
Matthias Clasen's avatar
Matthias Clasen committed
1130
  g_return_if_fail (GTK_IS_LIST_BOX (box));
Alexander Larsson's avatar
Alexander Larsson committed
1131

Matthias Clasen's avatar
Matthias Clasen committed
1132 1133 1134
  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
1135 1136 1137 1138 1139
}

static gint
do_sort (GtkListBoxRow *a,
         GtkListBoxRow *b,
Matthias Clasen's avatar
Matthias Clasen committed
1140
         GtkListBox    *box)
Alexander Larsson's avatar
Alexander Larsson committed
1141
{
Matthias Clasen's avatar
Matthias Clasen committed
1142
  GtkListBoxPrivate *priv = BOX_PRIV (box);
Alexander Larsson's avatar
Alexander Larsson committed
1143 1144 1145 1146

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

Alexander Larsson's avatar
Alexander Larsson committed
1147
/**
1148
 * gtk_list_box_invalidate_sort:
Matthias Clasen's avatar
Matthias Clasen committed
1149
 * @box: a #GtkListBox
Alexander Larsson's avatar
Alexander Larsson committed
1150 1151
 *
 * Update the sorting for all rows. Call this when result
Matthias Clasen's avatar
Matthias Clasen committed
1152
 * of the sort function on the @box is changed due
Alexander Larsson's avatar
Alexander Larsson committed
1153 1154 1155 1156
 * to an external factor.
 *
 * Since: 3.10
 */
Alexander Larsson's avatar
Alexander Larsson committed
1157
void
Matthias Clasen's avatar
Matthias Clasen committed
1158
gtk_list_box_invalidate_sort (GtkListBox *box)
Alexander Larsson's avatar
Alexander Larsson committed
1159
{
Matthias Clasen's avatar
Matthias Clasen committed
1160
  GtkListBoxPrivate *priv = BOX_PRIV (box);
1161

Matthias Clasen's avatar
Matthias Clasen committed
1162
  g_return_if_fail (GTK_IS_LIST_BOX (box));
Alexander Larsson's avatar
Alexander Larsson committed
1163

Matthias Clasen's avatar
Matthias Clasen committed
1164
  g_sequence_sort (priv->children, (GCompareDataFunc)do_sort, box);
Alexander Larsson's avatar
Alexander Larsson committed
1165

Matthias Clasen's avatar
Matthias Clasen committed
1166 1167
  gtk_list_box_invalidate_headers (box);
  gtk_widget_queue_resize (GTK_WIDGET (box));
Alexander Larsson's avatar
Alexander Larsson committed
1168 1169
}

1170
static void
Matthias Clasen's avatar
Matthias Clasen committed
1171
gtk_list_box_do_reseparate (GtkListBox *box)
1172 1173 1174
{
  GSequenceIter *iter;

Matthias Clasen's avatar