X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtkliststore.c;h=467bac0f15dc4da7b0a98336a01fe22f63952e7c;hb=ea043cab5718304d9b6170afa2d3f959fc99c718;hp=1d39e8ee833d44e56c96c9f5c4ffa3bfb4e492e4;hpb=287d91e5f994d059232731133870ba9e95c8e418;p=~andy%2Fgtk diff --git a/gtk/gtkliststore.c b/gtk/gtkliststore.c index 1d39e8ee8..467bac0f1 100644 --- a/gtk/gtkliststore.c +++ b/gtk/gtkliststore.c @@ -12,9 +12,7 @@ * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. + * License along with this library. If not, see . */ #include "config.h" @@ -31,9 +29,162 @@ #include "gtkbuilderprivate.h" +/** + * SECTION:gtkliststore + * @Short_description: A list-like data structure that can be used with the GtkTreeView + * @Title: GtkListStore + * @See_also:#GtkTreeModel, #GtkTreeStore + * + * The #GtkListStore object is a list model for use with a #GtkTreeView + * widget. It implements the #GtkTreeModel interface, and consequentialy, + * can use all of the methods available there. It also implements the + * #GtkTreeSortable interface so it can be sorted by the view. + * Finally, it also implements the tree drag and + * drop interfaces. + * + * The #GtkListStore can accept most GObject types as a column type, though + * it can't accept all custom types. Internally, it will keep a copy of + * data passed in (such as a string or a boxed pointer). Columns that + * accept #GObjects are handled a little differently. The + * #GtkListStore will keep a reference to the object instead of copying the + * value. As a result, if the object is modified, it is up to the + * application writer to call gtk_tree_model_row_changed() to emit the + * #GtkTreeModel::row_changed signal. This most commonly affects lists with + * #GdkPixbufs stored. + * + * + * Creating a simple list store. + * + * enum { + * COLUMN_STRING, + * COLUMN_INT, + * COLUMN_BOOLEAN, + * N_COLUMNS + * }; + * + * { + * GtkListStore *list_store; + * GtkTreePath *path; + * GtkTreeIter iter; + * gint i; + * + * list_store = gtk_list_store_new (N_COLUMNS, + * G_TYPE_STRING, + * G_TYPE_INT, + * G_TYPE_BOOLEAN); + * + * for (i = 0; i < 10; i++) + * { + * gchar *some_data; + * + * some_data = get_some_data (i); + * + * // Add a new row to the model + * gtk_list_store_append (list_store, &iter); + * gtk_list_store_set (list_store, &iter, + * COLUMN_STRING, some_data, + * COLUMN_INT, i, + * COLUMN_BOOLEAN, FALSE, + * -1); + * + * /* As the store will keep a copy of the string internally, we + * * free some_data. + * */ + * g_free (some_data); + * } + * + * // Modify a particular row + * path = gtk_tree_path_new_from_string ("4"); + * gtk_tree_model_get_iter (GTK_TREE_MODEL (list_store), + * &iter, + * path); + * gtk_tree_path_free (path); + * gtk_list_store_set (list_store, &iter, + * COLUMN_BOOLEAN, TRUE, + * -1); + * } + * + * + * + * + * Performance Considerations + * Internally, the #GtkListStore was implemented with a linked list with a + * tail pointer prior to GTK+ 2.6. As a result, it was fast at data + * insertion and deletion, and not fast at random data access. The + * #GtkListStore sets the #GTK_TREE_MODEL_ITERS_PERSIST flag, which means + * that #GtkTreeIters can be cached while the row exists. Thus, if + * access to a particular row is needed often and your code is expected to + * run on older versions of GTK+, it is worth keeping the iter around. + * + * + * Atomic Operations + * It is important to note that only the methods + * gtk_list_store_insert_with_values() and gtk_list_store_insert_with_valuesv() + * are atomic, in the sense that the row is being appended to the store and the + * values filled in in a single operation with regard to #GtkTreeModel signaling. + * In contrast, using e.g. gtk_list_store_append() and then gtk_list_store_set() + * will first create a row, which triggers the #GtkTreeModel::row-inserted signal + * on #GtkListStore. The row, however, is still empty, and any signal handler + * connecting to #GtkTreeModel::row-inserted on this particular store should be prepared + * for the situation that the row might be empty. This is especially important + * if you are wrapping the #GtkListStore inside a #GtkTreeModelFilter and are + * using a #GtkTreeModelFilterVisibleFunc. Using any of the non-atomic operations + * to append rows to the #GtkListStore will cause the + * #GtkTreeModelFilterVisibleFunc to be visited with an empty row first; the + * function must be prepared for that. + * + * + * GtkListStore as GtkBuildable + * + * The GtkListStore implementation of the GtkBuildable interface allows + * to specify the model columns with a <columns> element that may + * contain multiple <column> elements, each specifying one model + * column. The "type" attribute specifies the data type for the column. + * + * Additionally, it is possible to specify content for the list store + * in the UI definition, with the <data> element. It can contain + * multiple <row> elements, each specifying to content for one + * row of the list model. Inside a <row>, the <col> elements + * specify the content for individual cells. + * + * Note that it is probably more common to define your models + * in the code, and one might consider it a layering violation + * to specify the content of a list store in a UI definition, + * data, not presentation, + * and common wisdom is to separate the two, as far as possible. + * + * + * + * A UI Definition fragment for a list store + * + * + * + * + * + * + * + * + * John + * Doe + * 25 + * + * + * Johan + * Dahlin + * 50 + * + * + * + * ]]> + * + * + * + */ + + struct _GtkListStorePrivate { - GtkSortType order; GtkTreeIterCompareFunc default_sort_func; GDestroyNotify default_sort_destroy; @@ -45,6 +196,8 @@ struct _GtkListStorePrivate gint sort_column_id; gint length; + GtkSortType order; + guint columns_dirty : 1; gpointer default_sort_data; @@ -52,8 +205,6 @@ struct _GtkListStorePrivate }; #define GTK_LIST_STORE_IS_SORTED(list) (((GtkListStore*)(list))->priv->sort_column_id != GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID) -#define VALID_ITER(iter, list_store) ((iter)!= NULL && (iter)->user_data != NULL && list_store->priv->stamp == (iter)->stamp && !g_sequence_iter_is_end ((iter)->user_data) && g_sequence_iter_get_sequence ((iter)->user_data) == list_store->priv->seq) - static void gtk_list_store_tree_model_init (GtkTreeModelIface *iface); static void gtk_list_store_drag_source_init(GtkTreeDragSourceIface *iface); static void gtk_list_store_drag_dest_init (GtkTreeDragDestIface *iface); @@ -247,21 +398,32 @@ gtk_list_store_init (GtkListStore *list_store) priv->length = 0; } +static gboolean +iter_is_valid (GtkTreeIter *iter, + GtkListStore *list_store) +{ + return iter != NULL && + iter->user_data != NULL && + list_store->priv->stamp == iter->stamp && + !g_sequence_iter_is_end (iter->user_data) && + g_sequence_iter_get_sequence (iter->user_data) == list_store->priv->seq; +} + /** * gtk_list_store_new: * @n_columns: number of columns in the list store - * @Varargs: all #GType types for the columns, from first to last + * @...: all #GType types for the columns, from first to last * * Creates a new list store as with @n_columns columns each of the types passed - * in. Note that only types derived from standard GObject fundamental types - * are supported. + * in. Note that only types derived from standard GObject fundamental types + * are supported. * - * As an example, gtk_tree_store_new (3, G_TYPE_INT, G_TYPE_STRING, + * As an example, gtk_list_store_new (3, G_TYPE_INT, G_TYPE_STRING, * GDK_TYPE_PIXBUF); will create a new #GtkListStore with three columns, of type * int, string and #GdkPixbuf respectively. * * Return value: a new #GtkListStore - **/ + */ GtkListStore * gtk_list_store_new (gint n_columns, ...) @@ -281,11 +443,13 @@ gtk_list_store_new (gint n_columns, { GType type = va_arg (args, GType); if (! _gtk_tree_data_list_check_type (type)) - { - g_warning ("%s: Invalid type %s\n", G_STRLOC, g_type_name (type)); - g_object_unref (retval); - return NULL; - } + { + g_warning ("%s: Invalid type %s\n", G_STRLOC, g_type_name (type)); + g_object_unref (retval); + va_end (args); + + return NULL; + } gtk_list_store_set_column_type (retval, i, type); } @@ -303,7 +467,7 @@ gtk_list_store_new (gint n_columns, * * Non-vararg creation function. Used primarily by language bindings. * - * Return value: (transfer none): a new #GtkListStore + * Return value: (transfer full): a new #GtkListStore * Rename to: gtk_list_store_new **/ GtkListStore * @@ -481,7 +645,10 @@ gtk_list_store_get_iter (GtkTreeModel *tree_model, i = gtk_tree_path_get_indices (path)[0]; if (i >= g_sequence_get_length (seq)) - return FALSE; + { + iter->stamp = 0; + return FALSE; + } iter->stamp = priv->stamp; iter->user_data = g_sequence_get_iter_at_pos (seq, i); @@ -520,7 +687,7 @@ gtk_list_store_get_value (GtkTreeModel *tree_model, gint tmp_column = column; g_return_if_fail (column < priv->n_columns); - g_return_if_fail (VALID_ITER (iter, list_store)); + g_return_if_fail (iter_is_valid (iter, list_store)); list = g_sequence_get (iter->user_data); @@ -669,14 +836,13 @@ gtk_list_store_real_set_value (GtkListStore *list_store, GtkTreeDataList *list; GtkTreeDataList *prev; gint old_column = column; - GValue real_value = {0, }; + GValue real_value = G_VALUE_INIT; gboolean converted = FALSE; gboolean retval = FALSE; if (! g_type_is_a (G_VALUE_TYPE (value), priv->column_headers[column])) { - if (! (g_value_type_compatible (G_VALUE_TYPE (value), priv->column_headers[column]) && - g_value_type_compatible (priv->column_headers[column], G_VALUE_TYPE (value)))) + if (! (g_value_type_transformable (G_VALUE_TYPE (value), priv->column_headers[column]))) { g_warning ("%s: Unable to convert from %s to %s\n", G_STRLOC, @@ -684,6 +850,8 @@ gtk_list_store_real_set_value (GtkListStore *list_store, g_type_name (priv->column_headers[column])); return retval; } + + g_value_init (&real_value, priv->column_headers[column]); if (!g_value_transform (value, &real_value)) { g_warning ("%s: Unable to make conversion from %s to %s\n", @@ -773,10 +941,10 @@ gtk_list_store_set_value (GtkListStore *list_store, gint column, GValue *value) { - GtkListStorePrivate *priv = list_store->priv; + GtkListStorePrivate *priv; g_return_if_fail (GTK_IS_LIST_STORE (list_store)); - g_return_if_fail (VALID_ITER (iter, list_store)); + g_return_if_fail (iter_is_valid (iter, list_store)); g_return_if_fail (G_IS_VALUE (value)); priv = list_store->priv; g_return_if_fail (column >= 0 && column < priv->n_columns); @@ -867,7 +1035,7 @@ gtk_list_store_set_valist_internal (GtkListStore *list_store, while (column != -1) { - GValue value = { 0, }; + GValue value = G_VALUE_INIT; gchar *error = NULL; if (column < 0 || column >= priv->n_columns) @@ -935,7 +1103,7 @@ gtk_list_store_set_valuesv (GtkListStore *list_store, gboolean maybe_need_sort = FALSE; g_return_if_fail (GTK_IS_LIST_STORE (list_store)); - g_return_if_fail (VALID_ITER (iter, list_store)); + g_return_if_fail (iter_is_valid (iter, list_store)); priv = list_store->priv; @@ -977,7 +1145,7 @@ gtk_list_store_set_valist (GtkListStore *list_store, gboolean maybe_need_sort = FALSE; g_return_if_fail (GTK_IS_LIST_STORE (list_store)); - g_return_if_fail (VALID_ITER (iter, list_store)); + g_return_if_fail (iter_is_valid (iter, list_store)); priv = list_store->priv; @@ -1003,7 +1171,7 @@ gtk_list_store_set_valist (GtkListStore *list_store, * gtk_list_store_set: * @list_store: a #GtkListStore * @iter: row iterator - * @Varargs: pairs of column number and value, terminated with -1 + * @...: pairs of column number and value, terminated with -1 * * Sets the value of one or more cells in the row referenced by @iter. * The variable argument list should contain integer column numbers, @@ -1014,7 +1182,7 @@ gtk_list_store_set_valist (GtkListStore *list_store, * * The value will be referenced by the store if it is a %G_TYPE_OBJECT, and it * will be copied if it is a %G_TYPE_STRING or %G_TYPE_BOXED. - **/ + */ void gtk_list_store_set (GtkListStore *list_store, GtkTreeIter *iter, @@ -1047,7 +1215,7 @@ gtk_list_store_remove (GtkListStore *list_store, GSequenceIter *ptr, *next; g_return_val_if_fail (GTK_IS_LIST_STORE (list_store), FALSE); - g_return_val_if_fail (VALID_ITER (iter, list_store), FALSE); + g_return_val_if_fail (iter_is_valid (iter, list_store), FALSE); priv = list_store->priv; @@ -1081,12 +1249,12 @@ gtk_list_store_remove (GtkListStore *list_store, * gtk_list_store_insert: * @list_store: A #GtkListStore * @iter: (out): An unset #GtkTreeIter to set to the new row - * @position: position to insert the new row + * @position: position to insert the new row, or -1 for last * * Creates a new row at @position. @iter will be changed to point to this new - * row. If @position is larger than the number of rows on the list, then the - * new row will be appended to the list. The row will be empty after this - * function is called. To fill in values, you need to call + * row. If @position is -1 or is larger than the number of rows on the list, + * then the new row will be appended to the list. The row will be empty after + * this function is called. To fill in values, you need to call * gtk_list_store_set() or gtk_list_store_set_value(). * **/ @@ -1103,7 +1271,6 @@ gtk_list_store_insert (GtkListStore *list_store, g_return_if_fail (GTK_IS_LIST_STORE (list_store)); g_return_if_fail (iter != NULL); - g_return_if_fail (position >= 0); priv = list_store->priv; @@ -1112,7 +1279,7 @@ gtk_list_store_insert (GtkListStore *list_store, seq = priv->seq; length = g_sequence_get_length (seq); - if (position > length) + if (position > length || position < 0) position = length; ptr = g_sequence_get_iter_at_pos (seq, position); @@ -1121,7 +1288,7 @@ gtk_list_store_insert (GtkListStore *list_store, iter->stamp = priv->stamp; iter->user_data = ptr; - g_assert (VALID_ITER (iter, list_store)); + g_assert (iter_is_valid (iter, list_store)); priv->length++; @@ -1157,7 +1324,7 @@ gtk_list_store_insert_before (GtkListStore *list_store, priv = list_store->priv; if (sibling) - g_return_if_fail (VALID_ITER (sibling, list_store)); + g_return_if_fail (iter_is_valid (sibling, list_store)); if (!sibling) after = g_sequence_get_end_iter (priv->seq); @@ -1193,7 +1360,7 @@ gtk_list_store_insert_after (GtkListStore *list_store, priv = list_store->priv; if (sibling) - g_return_if_fail (VALID_ITER (sibling, list_store)); + g_return_if_fail (iter_is_valid (sibling, list_store)); if (!sibling) after = g_sequence_get_begin_iter (priv->seq); @@ -1237,14 +1404,10 @@ void gtk_list_store_append (GtkListStore *list_store, GtkTreeIter *iter) { - GtkListStorePrivate *priv; - g_return_if_fail (GTK_IS_LIST_STORE (list_store)); g_return_if_fail (iter != NULL); - priv = list_store->priv; - - gtk_list_store_insert (list_store, iter, g_sequence_get_length (priv->seq)); + gtk_list_store_insert (list_store, iter, -1); } static void @@ -1304,20 +1467,10 @@ gboolean gtk_list_store_iter_is_valid (GtkListStore *list_store, GtkTreeIter *iter) { - GtkListStorePrivate *priv; - g_return_val_if_fail (GTK_IS_LIST_STORE (list_store), FALSE); g_return_val_if_fail (iter != NULL, FALSE); - priv = list_store->priv; - - if (!VALID_ITER (iter, list_store)) - return FALSE; - - if (g_sequence_iter_get_sequence (iter->user_data) != priv->seq) - return FALSE; - - return TRUE; + return iter_is_valid (iter, list_store); } static gboolean real_gtk_list_store_row_draggable (GtkTreeDragSource *drag_source, @@ -1533,11 +1686,12 @@ gtk_list_store_reorder_func (GSequenceIter *a, } /** - * gtk_list_store_reorder: (skip) + * gtk_list_store_reorder: * @store: A #GtkListStore. - * @new_order: (array): an array of integers mapping the new position of each child - * to its old position before the re-ordering, - * i.e. @new_order[newpos] = oldpos. + * @new_order: (array zero-terminated=1): an array of integers mapping the new + * position of each child to its old position before the re-ordering, + * i.e. @new_order[newpos] = oldpos. It must have + * exactly as many items as the list store's length. * * Reorders @store to follow the order indicated by @new_order. Note that * this function only works with unsorted stores. @@ -1650,8 +1804,8 @@ gtk_list_store_swap (GtkListStore *store, g_return_if_fail (GTK_IS_LIST_STORE (store)); g_return_if_fail (!GTK_LIST_STORE_IS_SORTED (store)); - g_return_if_fail (VALID_ITER (a, store)); - g_return_if_fail (VALID_ITER (b, store)); + g_return_if_fail (iter_is_valid (a, store)); + g_return_if_fail (iter_is_valid (b, store)); priv = store->priv; @@ -1716,9 +1870,9 @@ gtk_list_store_move_before (GtkListStore *store, g_return_if_fail (GTK_IS_LIST_STORE (store)); g_return_if_fail (!GTK_LIST_STORE_IS_SORTED (store)); - g_return_if_fail (VALID_ITER (iter, store)); + g_return_if_fail (iter_is_valid (iter, store)); if (position) - g_return_if_fail (VALID_ITER (position, store)); + g_return_if_fail (iter_is_valid (position, store)); if (position) pos = g_sequence_iter_get_position (position->user_data); @@ -1749,9 +1903,9 @@ gtk_list_store_move_after (GtkListStore *store, g_return_if_fail (GTK_IS_LIST_STORE (store)); g_return_if_fail (!GTK_LIST_STORE_IS_SORTED (store)); - g_return_if_fail (VALID_ITER (iter, store)); + g_return_if_fail (iter_is_valid (iter, store)); if (position) - g_return_if_fail (VALID_ITER (position, store)); + g_return_if_fail (iter_is_valid (position, store)); if (position) pos = g_sequence_iter_get_position (position->user_data) + 1; @@ -1799,8 +1953,8 @@ gtk_list_store_compare_func (GSequenceIter *a, iter_b.stamp = priv->stamp; iter_b.user_data = (gpointer)b; - g_assert (VALID_ITER (&iter_a, list_store)); - g_assert (VALID_ITER (&iter_b, list_store)); + g_assert (iter_is_valid (&iter_a, list_store)); + g_assert (iter_is_valid (&iter_b, list_store)); retval = (* func) (GTK_TREE_MODEL (list_store), &iter_a, &iter_b, data); @@ -2011,18 +2165,19 @@ gtk_list_store_has_default_sort_func (GtkTreeSortable *sortable) /** * gtk_list_store_insert_with_values: * @list_store: A #GtkListStore - * @iter: (out) (allow-none): An unset #GtkTreeIter to set to the new row, or %NULL. - * @position: position to insert the new row - * @Varargs: pairs of column number and value, terminated with -1 + * @iter: (out) (allow-none): An unset #GtkTreeIter to set to the new row, or %NULL + * @position: position to insert the new row, or -1 to append after existing + * rows + * @...: pairs of column number and value, terminated with -1 + * + * Creates a new row at @position. @iter will be changed to point to this new + * row. If @position is -1, or larger than the number of rows in the list, then + * the new row will be appended to the list. The row will be filled with the + * values given to this function. * - * Creates a new row at @position. @iter will be changed to point to this new - * row. If @position is larger than the number of rows on the list, then the - * new row will be appended to the list. The row will be filled with the - * values given to this function. - * * Calling - * gtk_list_store_insert_with_values(list_store, iter, position...) - * has the same effect as calling + * gtk_list_store_insert_with_values (list_store, iter, position...) + * has the same effect as calling * |[ * gtk_list_store_insert (list_store, iter, position); * gtk_list_store_set (list_store, iter, ...); @@ -2030,7 +2185,7 @@ gtk_list_store_has_default_sort_func (GtkTreeSortable *sortable) * with the difference that the former will only emit a row_inserted signal, * while the latter will emit row_inserted, row_changed and, if the list store * is sorted, rows_reordered. Since emitting the rows_reordered signal - * repeatedly can affect the performance of the program, + * repeatedly can affect the performance of the program, * gtk_list_store_insert_with_values() should generally be preferred when * inserting rows in a sorted list store. * @@ -2065,7 +2220,7 @@ gtk_list_store_insert_with_values (GtkListStore *list_store, seq = priv->seq; length = g_sequence_get_length (seq); - if (position > length) + if (position > length || position < 0) position = length; ptr = g_sequence_get_iter_at_pos (seq, position); @@ -2074,7 +2229,7 @@ gtk_list_store_insert_with_values (GtkListStore *list_store, iter->stamp = priv->stamp; iter->user_data = ptr; - g_assert (VALID_ITER (iter, list_store)); + g_assert (iter_is_valid (iter, list_store)); priv->length++; @@ -2101,7 +2256,7 @@ gtk_list_store_insert_with_values (GtkListStore *list_store, * gtk_list_store_insert_with_valuesv: * @list_store: A #GtkListStore * @iter: (out) (allow-none): An unset #GtkTreeIter to set to the new row, or %NULL. - * @position: position to insert the new row + * @position: position to insert the new row, or -1 for last * @columns: (array length=n_values): an array of column numbers * @values: (array length=n_values): an array of GValues * @n_values: the length of the @columns and @values arrays @@ -2145,7 +2300,7 @@ gtk_list_store_insert_with_valuesv (GtkListStore *list_store, seq = priv->seq; length = g_sequence_get_length (seq); - if (position > length) + if (position > length || position < 0) position = length; ptr = g_sequence_get_iter_at_pos (seq, position); @@ -2154,7 +2309,7 @@ gtk_list_store_insert_with_valuesv (GtkListStore *list_store, iter->stamp = priv->stamp; iter->user_data = ptr; - g_assert (VALID_ITER (iter, list_store)); + g_assert (iter_is_valid (iter, list_store)); priv->length++; @@ -2282,10 +2437,12 @@ list_store_start_element (GMarkupParseContext *context, else if (strcmp (element_name, "row") == 0) ; else if (strcmp (element_name, "column") == 0) - for (i = 0; names[i]; i++) - if (strcmp (names[i], "type") == 0) - data->column_type_names = g_slist_prepend (data->column_type_names, - g_strdup (values[i])); + { + for (i = 0; names[i]; i++) + if (strcmp (names[i], "type") == 0) + data->column_type_names = g_slist_prepend (data->column_type_names, + g_strdup (values[i])); + } else if (strcmp (element_name, "columns") == 0) ; else if (strcmp (element_name, "data") == 0)