/* * Copyright © 2018 Benjamin Otte * * 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.1 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 . * * Authors: Benjamin Otte */ #include "config.h" #include "gtkselectionmodel.h" #include "gtkbitset.h" #include "gtkintl.h" #include "gtkmarshalers.h" /** * SECTION:gtkselectionmodel * @Title: GtkSelectionModel * @Short_description: An extension of the list model interface that handles selections * @See_also: #GListModel, #GtkSingleSelection * * #GtkSelectionModel is an interface that extends the #GListModel interface by * adding support for selections. This support is then used by widgets using list * models to add the ability to select and unselect various items. * * GTK provides default implementations of the most common selection modes such * as #GtkSingleSelection, so you will only need to implement this interface if * you want detailed control about how selections should be handled. * * A #GtkSelectionModel supports a single boolean per item indicating if an item * is selected or not. This can be queried via gtk_selection_model_is_selected(). * When the selected state of one or more items changes, the model will emit the * #GtkSelectionModel::selection-changed signal by calling the * gtk_selection_model_selection_changed() function. The positions given in that * signal may have their selection state changed, though that is not a requirement. * If new items added to the model via the #GListModel::items-changed signal are * selected or not is up to the implementation. * * Note that items added via #GListModel::items-changed may already be selected * and no #GtkSelectionModel::selection-changed will be emitted for them. So to * track which items are selected, it is necessary to listen to both signals. * * Additionally, the interface can expose functionality to select and unselect * items. If these functions are implemented, GTK's list widgets will allow users * to select and unselect items. However, #GtkSelectionModels are free to only * implement them partially or not at all. In that case the widgets will not * support the unimplemented operations. * * When selecting or unselecting is supported by a model, the return values of * the selection functions do *not* indicate if selection or unselection happened. * They are only meant to indicate complete failure, like when this mode of * selecting is not supported by the model. * * Selections may happen asynchronously, so the only reliable way to find out * when an item was selected is to listen to the signals that indicate selection. */ G_DEFINE_INTERFACE (GtkSelectionModel, gtk_selection_model, G_TYPE_LIST_MODEL) enum { SELECTION_CHANGED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = { 0 }; static gboolean gtk_selection_model_default_is_selected (GtkSelectionModel *model, guint position) { GtkBitset *bitset; gboolean selected; bitset = gtk_selection_model_get_selection_in_range (model, position, 1); selected = gtk_bitset_contains (bitset, position); gtk_bitset_unref (bitset); return selected; } static GtkBitset * gtk_selection_model_default_get_selection_in_range (GtkSelectionModel *model, guint position, guint n_items) { GtkBitset *bitset; guint i; bitset = gtk_bitset_new_empty (); for (i = position; i < position + n_items; i++) { if (gtk_selection_model_is_selected (model, i)) gtk_bitset_add (bitset, i); } return bitset; } static gboolean gtk_selection_model_default_select_item (GtkSelectionModel *model, guint position, gboolean unselect_rest) { GtkBitset *selected; GtkBitset *mask; gboolean result; selected = gtk_bitset_new_empty (); gtk_bitset_add (selected, position); if (unselect_rest) { mask = gtk_bitset_new_empty (); gtk_bitset_add_range (mask, 0, g_list_model_get_n_items (G_LIST_MODEL (model))); } else { mask = gtk_bitset_ref (selected); } result = gtk_selection_model_set_selection (model, selected, mask); gtk_bitset_unref (selected); gtk_bitset_unref (mask); return result; } static gboolean gtk_selection_model_default_unselect_item (GtkSelectionModel *model, guint position) { GtkBitset *selected; GtkBitset *mask; gboolean result; selected = gtk_bitset_new_empty (); mask = gtk_bitset_new_empty (); gtk_bitset_add (mask, position); result = gtk_selection_model_set_selection (model, selected, mask); gtk_bitset_unref (selected); gtk_bitset_unref (mask); return result; } static gboolean gtk_selection_model_default_select_range (GtkSelectionModel *model, guint position, guint n_items, gboolean unselect_rest) { GtkBitset *selected; GtkBitset *mask; gboolean result; selected = gtk_bitset_new_empty (); gtk_bitset_add_range (selected, position, n_items); if (unselect_rest) { mask = gtk_bitset_new_empty (); gtk_bitset_add_range (mask, 0, g_list_model_get_n_items (G_LIST_MODEL (model))); } else { mask = gtk_bitset_ref (selected); } result = gtk_selection_model_set_selection (model, selected, mask); gtk_bitset_unref (selected); gtk_bitset_unref (mask); return result; } static gboolean gtk_selection_model_default_unselect_range (GtkSelectionModel *model, guint position, guint n_items) { GtkBitset *selected; GtkBitset *mask; gboolean result; selected = gtk_bitset_new_empty (); mask = gtk_bitset_new_empty (); gtk_bitset_add_range (mask, position, n_items); result = gtk_selection_model_set_selection (model, selected, mask); gtk_bitset_unref (selected); gtk_bitset_unref (mask); return result; } static gboolean gtk_selection_model_default_select_all (GtkSelectionModel *model) { return gtk_selection_model_select_range (model, 0, g_list_model_get_n_items (G_LIST_MODEL (model)), FALSE); } static gboolean gtk_selection_model_default_unselect_all (GtkSelectionModel *model) { return gtk_selection_model_unselect_range (model, 0, g_list_model_get_n_items (G_LIST_MODEL (model))); } static gboolean gtk_selection_model_default_set_selection (GtkSelectionModel *model, GtkBitset *selected, GtkBitset *mask) { return FALSE; } static void gtk_selection_model_default_init (GtkSelectionModelInterface *iface) { iface->is_selected = gtk_selection_model_default_is_selected; iface->get_selection_in_range = gtk_selection_model_default_get_selection_in_range; iface->select_item = gtk_selection_model_default_select_item; iface->unselect_item = gtk_selection_model_default_unselect_item; iface->select_range = gtk_selection_model_default_select_range; iface->unselect_range = gtk_selection_model_default_unselect_range; iface->select_all = gtk_selection_model_default_select_all; iface->unselect_all = gtk_selection_model_default_unselect_all; iface->set_selection = gtk_selection_model_default_set_selection; /** * GtkSelectionModel::selection-changed * @model: a #GtkSelectionModel * @position: The first item that may have changed * @n_items: number of items with changes * * Emitted when the selection state of some of the items in @model changes. * * Note that this signal does not specify the new selection state of the items, * they need to be queried manually. * It is also not necessary for a model to change the selection state of any of * the items in the selection model, though it would be rather useless to emit * such a signal. */ signals[SELECTION_CHANGED] = g_signal_new ("selection-changed", GTK_TYPE_SELECTION_MODEL, G_SIGNAL_RUN_LAST, 0, NULL, NULL, _gtk_marshal_VOID__UINT_UINT, G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT); g_signal_set_va_marshaller (signals[SELECTION_CHANGED], GTK_TYPE_SELECTION_MODEL, _gtk_marshal_VOID__UINT_UINTv); } /** * gtk_selection_model_is_selected: * @model: a #GtkSelectionModel * @position: the position of the item to query * * Checks if the given item is selected. * * Returns: %TRUE if the item is selected **/ gboolean gtk_selection_model_is_selected (GtkSelectionModel *model, guint position) { GtkSelectionModelInterface *iface; g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), 0); iface = GTK_SELECTION_MODEL_GET_IFACE (model); return iface->is_selected (model, position); } /** * gtk_selection_model_get_selection: * @model: a #GtkSelectionModel * * Gets the set containing all currently selected items in the model. * * This function may be slow, so if you are only interested in single item, * consider using gtk_selection_model_is_selected() or if you are only * interested in a few consider gtk_selection_model_get_selection_in_range(). * * Returns: (transfer full): a #GtkBitset containing all the values currently * selected in @model. If no items are selected, the bitset is empty. * The bitset must not be modified. **/ GtkBitset * gtk_selection_model_get_selection (GtkSelectionModel *model) { g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), gtk_bitset_new_empty ()); return gtk_selection_model_get_selection_in_range (model, 0, g_list_model_get_n_items (G_LIST_MODEL (model))); } /** * gtk_selection_model_get_selection_in_range: * @model: a #GtkSelectionModel * @position: start of the queired range * @n_items: number of items in the queried range * * Gets a set containing a set where the values in the range `[position, * position + n_items)` match the selected state of the items in that range. * All values outside that range are undefined. * * This function is an optimization for gtk_selection_model_get_selection() when * you are only interested in part of the model's selected state. A common use * case is in response to the #GtkSelectionModel::selection-changed signal. * * Returns: A #GtkBitset that matches the selection state for the given state * with all other values being undefined. * The bitset must not be modified. **/ GtkBitset * gtk_selection_model_get_selection_in_range (GtkSelectionModel *model, guint position, guint n_items) { GtkSelectionModelInterface *iface; g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), gtk_bitset_new_empty ()); if (n_items == 0) return gtk_bitset_new_empty (); iface = GTK_SELECTION_MODEL_GET_IFACE (model); return iface->get_selection_in_range (model, position, n_items); } /** * gtk_selection_model_select_item: * @model: a #GtkSelectionModel * @position: the position of the item to select * @unselect_rest: whether previously selected items should be unselected * * Requests to select an item in the model. * * Returns: %TRUE if this action was supported and no fallback should be * tried. This does not mean the item was selected. */ gboolean gtk_selection_model_select_item (GtkSelectionModel *model, guint position, gboolean unselect_rest) { GtkSelectionModelInterface *iface; g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), FALSE); iface = GTK_SELECTION_MODEL_GET_IFACE (model); return iface->select_item (model, position, unselect_rest); } /** * gtk_selection_model_unselect_item: * @model: a #GtkSelectionModel * @position: the position of the item to unselect * * Requests to unselect an item in the model. * * Returns: %TRUE if this action was supported and no fallback should be * tried. This does not mean the item was unselected. */ gboolean gtk_selection_model_unselect_item (GtkSelectionModel *model, guint position) { GtkSelectionModelInterface *iface; g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), FALSE); iface = GTK_SELECTION_MODEL_GET_IFACE (model); return iface->unselect_item (model, position); } /** * gtk_selection_model_select_range: * @model: a #GtkSelectionModel * @position: the first item to select * @n_items: the number of items to select * @unselect_rest: whether previously selected items should be unselected * * Requests to select a range of items in the model. * * Returns: %TRUE if this action was supported and no fallback should be * tried. This does not mean the range was selected. */ gboolean gtk_selection_model_select_range (GtkSelectionModel *model, guint position, guint n_items, gboolean unselect_rest) { GtkSelectionModelInterface *iface; g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), FALSE); iface = GTK_SELECTION_MODEL_GET_IFACE (model); return iface->select_range (model, position, n_items, unselect_rest); } /** * gtk_selection_model_unselect_range: * @model: a #GtkSelectionModel * @position: the first item to unselect * @n_items: the number of items to unselect * * Requests to unselect a range of items in the model. * * Returns: %TRUE if this action was supported and no fallback should be * tried. This does not mean the range was unselected. */ gboolean gtk_selection_model_unselect_range (GtkSelectionModel *model, guint position, guint n_items) { GtkSelectionModelInterface *iface; g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), FALSE); iface = GTK_SELECTION_MODEL_GET_IFACE (model); return iface->unselect_range (model, position, n_items); } /** * gtk_selection_model_select_all: * @model: a #GtkSelectionModel * * Requests to select all items in the model. * * Returns: %TRUE if this action was supported and no fallback should be * tried. This does not mean that all items are now selected. */ gboolean gtk_selection_model_select_all (GtkSelectionModel *model) { GtkSelectionModelInterface *iface; g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), FALSE); iface = GTK_SELECTION_MODEL_GET_IFACE (model); return iface->select_all (model); } /** * gtk_selection_model_unselect_all: * @model: a #GtkSelectionModel * * Requests to unselect all items in the model. * * Returns: %TRUE if this action was supported and no fallback should be * tried. This does not mean that all items are now unselected. */ gboolean gtk_selection_model_unselect_all (GtkSelectionModel *model) { GtkSelectionModelInterface *iface; g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), FALSE); iface = GTK_SELECTION_MODEL_GET_IFACE (model); return iface->unselect_all (model); } /** * gtk_selection_model_set_selection: * @model: a #GtkSelectionModel * @selected: bitmask specifying if items should be selected or * unselected * @mask: bitmask specifying which items should be updated * * This is the most advanced selection updating method that allows * the most fine-grained control over selection changes. * If you can, you should try the simpler versions, as implementations * are more likely to implement support for those. * * Requests that the selection state of all positions set in @mask be * updated to the respecitve value in the @selected bitmask. * * In pseudocode, it would look something like this: * * |[ * for (i = 0; i < n_items; i++) * { * // don't change values not in the mask * if (!gtk_bitset_contains (mask, i)) * continue; * * if (gtk_bitset_contains (selected, i)) * select_item (i); * else * unselect_item (i); * } * * gtk_selection_model_selection_changed (model, first_changed_item, n_changed_items); * ]| * * @mask and @selected must not be modified. They may refer to the same bitset, * which would mean that every item in the set should be selected. * * Returns: %TRUE if this action was supported and no fallback should be * tried. This does not mean that all items were updated according * to the inputs. **/ gboolean gtk_selection_model_set_selection (GtkSelectionModel *model, GtkBitset *selected, GtkBitset *mask) { GtkSelectionModelInterface *iface; g_return_val_if_fail (GTK_IS_SELECTION_MODEL (model), FALSE); g_return_val_if_fail (selected != NULL, FALSE); g_return_val_if_fail (mask != NULL, FALSE); iface = GTK_SELECTION_MODEL_GET_IFACE (model); return iface->set_selection (model, selected, mask); } /** * gtk_selection_model_selection_changed: * @model: a #GtkSelectionModel * @position: the first changed item * @n_items: the number of changed items * * Helper function for implementations of #GtkSelectionModel. * Call this when a the selection changes to emit the * #GtkSelectionModel::selection-changed signal. */ void gtk_selection_model_selection_changed (GtkSelectionModel *model, guint position, guint n_items) { g_return_if_fail (GTK_IS_SELECTION_MODEL (model)); g_return_if_fail (n_items > 0); g_return_if_fail (position + n_items <= g_list_model_get_n_items (G_LIST_MODEL (model))); g_signal_emit (model, signals[SELECTION_CHANGED], 0, position, n_items); }