diff options
| author | Hans Petter Jansson <hpj@novell.com> | 2005-01-22 09:59:42 +0000 |
|---|---|---|
| committer | Hans Petter <hansp@src.gnome.org> | 2005-01-22 09:59:42 +0000 |
| commit | 2af6e3d199aced9099102f50cebd8dcb38bbdbd4 (patch) | |
| tree | 8fbbe483680b360add3117fcb6866507b3d98d21 /libedataserverui | |
| parent | d7f969661b5e19a47ffb3da03bee59fa580ba3bd (diff) | |
| download | evolution-data-server-2af6e3d199aced9099102f50cebd8dcb38bbdbd4.tar.gz | |
Add newly implemented ETreeModelGenerator to build.
2005-01-22 Hans Petter Jansson <hpj@novell.com>
* Makefile.am (libedataserverui_1_2_la_SOURCES)
(libedataserveruiinclude_HEADERS): Add newly implemented
ETreeModelGenerator to build.
* e-tree-model-generator.[ch]: Implement ETreeModelGenerator, a model
wrapper similar to GtkTreeModelFilter, but with the capability to map
each child row to an arbitrary number of rows, including zero, and pass
a permutation identifier to the user's modify callback.
* e-name-selector-dialog.c (contact_column_formatter): Implement a
data formatter for the contact list. This makes it show both names
and e-mail addresses.
(e_name_selector_dialog_init): Set up data formatter.
(sort_iter_to_contact_store_iter): Replace GtkTreeModelFilter with
ETreeModelGenerator, and return the email permutation along with the
contact iter.
(add_destination): Take the email permutation as an argument, and set
it in the contact instead of defaulting to the first one.
(contact_activated): Pass on email_n.
(transfer_button_clicked): Ditto.
(setup_name_selector_model): Use ETreeModelGenerator.
(deep_free_list): Implement.
* e-name-selector-entry.c (completion_match_selected): Add the
conversion for the e-mail generator, and set the selected e-mail
permutation in the destination.
(deep_free_list): Implement.
(contact_layout_formatter): Implement a data formatter for the
completion list, so we can show both names and e-mail addresses.
(generate_contact_rows): Implement an e-mail permutation generator.
(setup_contact_store): Set up the e-mail generator.
(e_name_selector_entry_init): Init the generator pointer. Set up the
data formatter.
* e-name-selector-model.c (e_name_selector_model_init): Replace
the GtkTreeModelFilter with an ETreeModelGenerator.
(deep_free_list): Implement.
(generate_contact_rows): Replace filter_show_not_in_destinations ()
with this function, which both filters and generates per-email
permutations.
(override_email_address): Override the contents of the
E_CONTACT_EMAIL_1 string field with the relevant address.
(e_name_selector_model_peek_contact_filter): Now returns a generator.
Diffstat (limited to 'libedataserverui')
| -rw-r--r-- | libedataserverui/ChangeLog | 13 | ||||
| -rw-r--r-- | libedataserverui/Makefile.am | 6 | ||||
| -rw-r--r-- | libedataserverui/e-name-selector-dialog.c | 91 | ||||
| -rw-r--r-- | libedataserverui/e-name-selector-entry.c | 108 | ||||
| -rw-r--r-- | libedataserverui/e-name-selector-entry.h | 18 | ||||
| -rw-r--r-- | libedataserverui/e-name-selector-model.c | 74 | ||||
| -rw-r--r-- | libedataserverui/e-name-selector-model.h | 38 | ||||
| -rw-r--r-- | libedataserverui/e-tree-model-generator.c | 1250 | ||||
| -rw-r--r-- | libedataserverui/e-tree-model-generator.h | 92 |
9 files changed, 1615 insertions, 75 deletions
diff --git a/libedataserverui/ChangeLog b/libedataserverui/ChangeLog index 2bf36559b..de34c9614 100644 --- a/libedataserverui/ChangeLog +++ b/libedataserverui/ChangeLog @@ -1,3 +1,16 @@ +2005-01-22 Hans Petter Jansson <hpj@novell.com> + + * Makefile.am (libedataserverui_1_2_la_SOURCES) + (libedataserveruiinclude_HEADERS): Add newly implemented + ETreeModelGenerator to build. + + * e-tree-model-generator.[ch]: Implement ETreeModelGenerator, a model + wrapper similar to GtkTreeModelFilter, but with the capability to map + each child row to an arbitrary number of rows, including zero, and pass + a permutation identifier to the user's modify callback. + + * e-name-selector-dialog.c (contact_column_formatter): + 2005-01-19 Hans Petter Jansson <hpj@novell.com> * e-name-selector-entry.c (sanitize_string): Implement. diff --git a/libedataserverui/Makefile.am b/libedataserverui/Makefile.am index aa6bb691e..73bf1a421 100644 --- a/libedataserverui/Makefile.am +++ b/libedataserverui/Makefile.am @@ -28,7 +28,8 @@ libedataserverui_1_2_la_SOURCES = \ e-name-selector-model.c \ e-source-selector.c \ e-source-selector-dialog.c \ - e-source-option-menu.c + e-source-option-menu.c \ + e-tree-model-generator.c libedataserverui_1_2_la_LIBADD = \ $(top_builddir)/addressbook/libebook/libebook-1.2.la \ @@ -49,7 +50,8 @@ libedataserveruiinclude_HEADERS = \ e-name-selector-model.h \ e-source-selector.h \ e-source-selector-dialog.h \ - e-source-option-menu.h + e-source-option-menu.h \ + e-tree-model-generator.h # Test programs test_source_selector_SOURCES = test-source-selector.c diff --git a/libedataserverui/e-name-selector-dialog.c b/libedataserverui/e-name-selector-dialog.c index a6782c93e..d4b4b67f5 100644 --- a/libedataserverui/e-name-selector-dialog.c +++ b/libedataserverui/e-name-selector-dialog.c @@ -54,6 +54,9 @@ static void contact_activated (ENameSelectorDialog *name_selector_di static void destination_activated (ENameSelectorDialog *name_selector_dialog, GtkTreePath *path, GtkTreeViewColumn *column, GtkTreeView *tree_view); static void remove_books (ENameSelectorDialog *name_selector_dialog); +static void contact_column_formatter (GtkTreeViewColumn *column, GtkCellRenderer *cell, + GtkTreeModel *model, GtkTreeIter *iter, + ENameSelectorDialog *name_selector_dialog); /* ------------------ * * Class/object setup * @@ -130,7 +133,9 @@ e_name_selector_dialog_init (ENameSelectorDialog *name_selector_dialog) column = gtk_tree_view_column_new (); cell_renderer = GTK_CELL_RENDERER (gtk_cell_renderer_text_new ()); gtk_tree_view_column_pack_start (column, cell_renderer, TRUE); - gtk_tree_view_column_add_attribute (column, cell_renderer, "text", E_CONTACT_FILE_AS); + gtk_tree_view_column_set_cell_data_func (column, cell_renderer, + (GtkTreeCellDataFunc) contact_column_formatter, + name_selector_dialog, NULL); gtk_tree_view_append_column (name_selector_dialog->contact_view, column); g_signal_connect_swapped (name_selector_dialog->contact_view, "row-activated", G_CALLBACK (contact_activated), name_selector_dialog); @@ -254,16 +259,21 @@ escape_sexp_string (const gchar *string) } static void -sort_iter_to_contact_store_iter (ENameSelectorDialog *name_selector_dialog, GtkTreeIter *iter) +sort_iter_to_contact_store_iter (ENameSelectorDialog *name_selector_dialog, GtkTreeIter *iter, + gint *email_n) { - GtkTreeModelFilter *contact_filter; - GtkTreeIter child_iter; + ETreeModelGenerator *contact_filter; + GtkTreeIter child_iter; + gint email_n_local; contact_filter = e_name_selector_model_peek_contact_filter (name_selector_dialog->name_selector_model); gtk_tree_model_sort_convert_iter_to_child_iter (name_selector_dialog->contact_sort, &child_iter, iter); - gtk_tree_model_filter_convert_iter_to_child_iter (contact_filter, iter, &child_iter); + e_tree_model_generator_convert_iter_to_child_iter (contact_filter, iter, &email_n_local, &child_iter); + + if (email_n) + *email_n = email_n_local; } static ESource * @@ -287,7 +297,7 @@ find_first_source (ESourceList *source_list) } static void -add_destination (EDestinationStore *destination_store, EContact *contact) +add_destination (EDestinationStore *destination_store, EContact *contact, gint email_n) { EDestination *destination; @@ -295,7 +305,7 @@ add_destination (EDestinationStore *destination_store, EContact *contact) * source automatically) */ destination = e_destination_new (); - e_destination_set_contact (destination, contact, 0); + e_destination_set_contact (destination, contact, email_n); e_destination_store_append_destination (destination_store, destination); g_object_unref (destination); } @@ -567,6 +577,7 @@ contact_activated (ENameSelectorDialog *name_selector_dialog, GtkTreePath *path) EContact *contact; GtkTreeIter iter; Section *section; + gint email_n; /* When a contact is activated, we transfer it to the first destination on our list */ @@ -583,7 +594,7 @@ contact_activated (ENameSelectorDialog *name_selector_dialog, GtkTreePath *path) &iter, path)) g_assert_not_reached (); - sort_iter_to_contact_store_iter (name_selector_dialog, &iter); + sort_iter_to_contact_store_iter (name_selector_dialog, &iter, &email_n); contact = e_contact_store_get_contact (contact_store, &iter); if (!contact) { @@ -598,7 +609,7 @@ contact_activated (ENameSelectorDialog *name_selector_dialog, GtkTreePath *path) return; } - add_destination (destination_store, contact); + add_destination (destination_store, contact, email_n); } static void @@ -646,6 +657,7 @@ transfer_button_clicked (ENameSelectorDialog *name_selector_dialog, GtkButton *t gint section_index; Section *section; EDestination *destination; + gint email_n; /* Get the contact to be transferred */ @@ -657,7 +669,7 @@ transfer_button_clicked (ENameSelectorDialog *name_selector_dialog, GtkButton *t return; } - sort_iter_to_contact_store_iter (name_selector_dialog, &iter); + sort_iter_to_contact_store_iter (name_selector_dialog, &iter, &email_n); contact = e_contact_store_get_contact (contact_store, &iter); if (!contact) { @@ -680,7 +692,7 @@ transfer_button_clicked (ENameSelectorDialog *name_selector_dialog, GtkButton *t return; } - add_destination (destination_store, contact); + add_destination (destination_store, contact, email_n); } /* --------------------- * @@ -690,13 +702,13 @@ transfer_button_clicked (ENameSelectorDialog *name_selector_dialog, GtkButton *t static void setup_name_selector_model (ENameSelectorDialog *name_selector_dialog) { - EContactStore *contact_store; - GtkTreeModelFilter *contact_filter; - GtkTreeSelection *contact_selection; - EBookQuery *book_query; - GList *new_sections; - GList *l; - gint i; + EContactStore *contact_store; + ETreeModelGenerator *contact_filter; + GtkTreeSelection *contact_selection; + EBookQuery *book_query; + GList *new_sections; + GList *l; + gint i; /* Rid UI of previous destination sections */ @@ -755,6 +767,49 @@ setup_name_selector_model (ENameSelectorDialog *name_selector_dialog) contact_selection_changed (name_selector_dialog); } +static void +deep_free_list (GList *list) +{ + GList *l; + + for (l = list; l; l = g_list_next (l)) + g_free (l->data); + + g_list_free (list); +} + +static void +contact_column_formatter (GtkTreeViewColumn *column, GtkCellRenderer *cell, GtkTreeModel *model, + GtkTreeIter *iter, ENameSelectorDialog *name_selector_dialog) +{ + EContactStore *contact_store; + EContact *contact; + GtkTreeIter contact_store_iter; + GList *email_list; + gchar *string; + gchar *file_as_str; + gchar *email_str; + gint email_n; + + contact_store_iter = *iter; + sort_iter_to_contact_store_iter (name_selector_dialog, &contact_store_iter, &email_n); + + contact_store = e_name_selector_model_peek_contact_store (name_selector_dialog->name_selector_model); + contact = e_contact_store_get_contact (contact_store, &contact_store_iter); + email_list = e_contact_get (contact, E_CONTACT_EMAIL); + email_str = g_list_nth_data (email_list, email_n); + file_as_str = e_contact_get (contact, E_CONTACT_FILE_AS); + + string = g_strdup_printf ("%s <%s>", file_as_str ? file_as_str : "", + email_str ? email_str : ""); + + g_free (file_as_str); + deep_free_list (email_list); + + g_object_set (cell, "text", string, NULL); + g_free (string); +} + /* ----------------------- * * ENameSelectorDialog API * * ----------------------- */ diff --git a/libedataserverui/e-name-selector-entry.c b/libedataserverui/e-name-selector-entry.c index b9761c6e4..6e90c5925 100644 --- a/libedataserverui/e-name-selector-entry.c +++ b/libedataserverui/e-name-selector-entry.c @@ -1165,30 +1165,23 @@ completion_match_selected (ENameSelectorEntry *name_selector_entry, GtkTreeModel gint cursor_pos; const gchar *text; gchar *raw_address; + GtkTreeIter generator_iter; GtkTreeIter contact_iter; + gint email_n; gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (model), - &contact_iter, iter); + &generator_iter, iter); + e_tree_model_generator_convert_iter_to_child_iter (name_selector_entry->email_generator, + &contact_iter, &email_n, + &generator_iter); contact = e_contact_store_get_contact (name_selector_entry->contact_store, &contact_iter); - - /* Find the matching field, in case the user entered a specific e-mail address. - * The default is to use the first e-mail. */ - cursor_pos = gtk_editable_get_position (GTK_EDITABLE (name_selector_entry)); - text = gtk_entry_get_text (GTK_ENTRY (name_selector_entry)); - raw_address = get_address_at_position (text, cursor_pos); - - if (raw_address) - contact_match_cue (contact, raw_address, &matched_field, NULL); - - if (matched_field < E_CONTACT_FIRST_EMAIL_ID || matched_field > E_CONTACT_LAST_EMAIL_ID) - matched_field = E_CONTACT_EMAIL_1; /* Set the contact in the model's destination */ destination = find_destination_at_position (name_selector_entry, cursor_pos); - e_destination_set_contact (destination, contact, matched_field - E_CONTACT_FIRST_EMAIL_ID); + e_destination_set_contact (destination, contact, email_n); sync_destination_at_position (name_selector_entry, cursor_pos, &cursor_pos); /* Place cursor at end of address */ @@ -1217,12 +1210,89 @@ entry_activate (ENameSelectorEntry *name_selector_entry) } static void +deep_free_list (GList *list) +{ + GList *l; + + for (l = list; l; l = g_list_next (l)) + g_free (l->data); + + g_list_free (list); +} + +static void +contact_layout_formatter (GtkCellLayout *cell_layout, GtkCellRenderer *cell, GtkTreeModel *model, + GtkTreeIter *iter, ENameSelectorEntry *name_selector_entry) +{ + EContact *contact; + GtkTreeIter generator_iter; + GtkTreeIter contact_store_iter; + GList *email_list; + gchar *string; + gchar *file_as_str; + gchar *email_str; + gint email_n; + + gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (model), + &generator_iter, iter); + e_tree_model_generator_convert_iter_to_child_iter (name_selector_entry->email_generator, + &contact_store_iter, &email_n, + &generator_iter); + + contact = e_contact_store_get_contact (name_selector_entry->contact_store, &contact_store_iter); + email_list = e_contact_get (contact, E_CONTACT_EMAIL); + email_str = g_list_nth_data (email_list, email_n); + file_as_str = e_contact_get (contact, E_CONTACT_FILE_AS); + + string = g_strdup_printf ("%s <%s>", file_as_str ? file_as_str : "", + email_str ? email_str : ""); + + g_free (file_as_str); + deep_free_list (email_list); + + g_object_set (cell, "text", string, NULL); + g_free (string); +} + +static gint +generate_contact_rows (EContactStore *contact_store, GtkTreeIter *iter, + ENameSelectorEntry *name_selector_entry) +{ + EContact *contact; + const gchar *contact_uid; + GList *email_list; + gint n_rows; + + contact = e_contact_store_get_contact (contact_store, iter); + g_assert (contact != NULL); + + contact_uid = e_contact_get_const (contact, E_CONTACT_UID); + if (!contact_uid) + return 0; /* Can happen with broken databases */ + + email_list = e_contact_get (contact, E_CONTACT_EMAIL); + n_rows = g_list_length (email_list); + deep_free_list (email_list); + + return n_rows; +} + +static void setup_contact_store (ENameSelectorEntry *name_selector_entry) { + if (name_selector_entry->email_generator) + g_object_unref (name_selector_entry->email_generator); + name_selector_entry->email_generator = + e_tree_model_generator_new (GTK_TREE_MODEL (name_selector_entry->contact_store)); + + e_tree_model_generator_set_generate_func (name_selector_entry->email_generator, + (ETreeModelGeneratorGenerateFunc) generate_contact_rows, + name_selector_entry, NULL); + /* Assign the store to the entry completion */ gtk_entry_completion_set_model (name_selector_entry->entry_completion, - GTK_TREE_MODEL (name_selector_entry->contact_store)); + GTK_TREE_MODEL (name_selector_entry->email_generator)); } static void @@ -1475,6 +1545,8 @@ e_name_selector_entry_init (ENameSelectorEntry *name_selector_entry) /* Completion */ + name_selector_entry->email_generator = NULL; + name_selector_entry->entry_completion = gtk_entry_completion_new (); gtk_entry_completion_set_match_func (name_selector_entry->entry_completion, (GtkEntryCompletionMatchFunc) completion_match_cb, NULL, NULL); @@ -1488,8 +1560,10 @@ e_name_selector_entry_init (ENameSelectorEntry *name_selector_entry) renderer = gtk_cell_renderer_text_new (); gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (name_selector_entry->entry_completion), renderer, TRUE); - gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (name_selector_entry->entry_completion), - renderer, "text", E_CONTACT_FILE_AS); + gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (name_selector_entry->entry_completion), + GTK_CELL_RENDERER (renderer), + (GtkCellLayoutDataFunc) contact_layout_formatter, + name_selector_entry, NULL); /* Completion store */ diff --git a/libedataserverui/e-name-selector-entry.h b/libedataserverui/e-name-selector-entry.h index 42994f4ca..b28699ae3 100644 --- a/libedataserverui/e-name-selector-entry.h +++ b/libedataserverui/e-name-selector-entry.h @@ -28,6 +28,7 @@ #include <libebook/e-contact.h> #include <libedataserverui/e-contact-store.h> #include <libedataserverui/e-destination-store.h> +#include <libedataserverui/e-tree-model-generator.h> G_BEGIN_DECLS @@ -46,18 +47,19 @@ struct ENameSelectorEntryClass { }; struct ENameSelectorEntry { - GtkEntry parent; + GtkEntry parent; /* Private */ - PangoAttrList *attr_list; - ESourceList *source_list; - EContactStore *contact_store; - EDestinationStore *destination_store; - GtkEntryCompletion *entry_completion; + PangoAttrList *attr_list; + ESourceList *source_list; + EContactStore *contact_store; + ETreeModelGenerator *email_generator; + EDestinationStore *destination_store; + GtkEntryCompletion *entry_completion; - guint type_ahead_complete_cb_id; - guint update_completions_cb_id; + guint type_ahead_complete_cb_id; + guint update_completions_cb_id; }; GType e_name_selector_entry_get_type (void); diff --git a/libedataserverui/e-name-selector-model.c b/libedataserverui/e-name-selector-model.c index 5516bb3d7..51437c6a9 100644 --- a/libedataserverui/e-name-selector-model.c +++ b/libedataserverui/e-name-selector-model.c @@ -38,8 +38,11 @@ typedef struct { } Section; -static gboolean filter_show_not_in_destinations (EContactStore *contact_store, GtkTreeIter *iter, - ENameSelectorModel *name_selector_model); +static gint generate_contact_rows (EContactStore *contact_store, GtkTreeIter *iter, + ENameSelectorModel *name_selector_model); +static void override_email_address (EContactStore *contact_store, GtkTreeIter *iter, + gint permutation_n, gint column, GValue *value, + ENameSelectorModel *name_selector_model); /* ------------------ * * Class/object setup * @@ -62,11 +65,16 @@ e_name_selector_model_init (ENameSelectorModel *name_selector_model) { name_selector_model->sections = g_array_new (FALSE, FALSE, sizeof (Section)); name_selector_model->contact_store = e_contact_store_new (); - name_selector_model->contact_filter = GTK_TREE_MODEL_FILTER ( - gtk_tree_model_filter_new (GTK_TREE_MODEL (name_selector_model->contact_store), NULL)); - gtk_tree_model_filter_set_visible_func (name_selector_model->contact_filter, - (GtkTreeModelFilterVisibleFunc) filter_show_not_in_destinations, + + name_selector_model->contact_filter = + e_tree_model_generator_new (GTK_TREE_MODEL (name_selector_model->contact_store)); + e_tree_model_generator_set_generate_func (name_selector_model->contact_filter, + (ETreeModelGeneratorGenerateFunc) generate_contact_rows, + name_selector_model, NULL); + e_tree_model_generator_set_modify_func (name_selector_model->contact_filter, + (ETreeModelGeneratorModifyFunc) override_email_address, name_selector_model, NULL); + g_object_unref (name_selector_model->contact_store); name_selector_model->destination_uid_hash = NULL; @@ -121,13 +129,25 @@ e_name_selector_model_new (void) * GtkTreeModelFilter filtering * * ---------------------------- */ -static gboolean -filter_show_not_in_destinations (EContactStore *contact_store, GtkTreeIter *iter, - ENameSelectorModel *name_selector_model) +static void +deep_free_list (GList *list) +{ + GList *l; + + for (l = list; l; l = g_list_next (l)) + g_free (l->data); + + g_list_free (list); +} + +static gint +generate_contact_rows (EContactStore *contact_store, GtkTreeIter *iter, + ENameSelectorModel *name_selector_model) { EContact *contact; const gchar *contact_uid; gboolean result = TRUE; + gint n_rows; gint i; contact = e_contact_store_get_contact (contact_store, iter); @@ -159,7 +179,39 @@ filter_show_not_in_destinations (EContactStore *contact_store, GtkTreeIter *iter g_list_free (destinations); } - return result; + if (result) { + EContact *contact; + GList *email_list; + + contact = e_contact_store_get_contact (contact_store, iter); + email_list = e_contact_get (contact, E_CONTACT_EMAIL); + n_rows = g_list_length (email_list); + deep_free_list (email_list); + } else { + n_rows = 0; + } + + return n_rows; +} + +static void +override_email_address (EContactStore *contact_store, GtkTreeIter *iter, + gint permutation_n, gint column, GValue *value, + ENameSelectorModel *name_selector_model) +{ + if (column == E_CONTACT_EMAIL_1) { + EContact *contact; + GList *email_list; + gchar *email; + + contact = e_contact_store_get_contact (contact_store, iter); + email_list = e_contact_get (contact, E_CONTACT_EMAIL); + email = g_strdup (g_list_nth_data (email_list, permutation_n)); + g_value_set_string (value, email); + deep_free_list (email_list); + } else { + gtk_tree_model_get_value (GTK_TREE_MODEL (contact_store), iter, column, value); + } } /* --------------- * @@ -286,7 +338,7 @@ e_name_selector_model_peek_contact_store (ENameSelectorModel *name_selector_mode return name_selector_model->contact_store; } -GtkTreeModelFilter * +ETreeModelGenerator * e_name_selector_model_peek_contact_filter (ENameSelectorModel *name_selector_model) { g_return_val_if_fail (E_IS_NAME_SELECTOR_MODEL (name_selector_model), NULL); diff --git a/libedataserverui/e-name-selector-model.h b/libedataserverui/e-name-selector-model.h index 60dc7d05d..94ad73b5a 100644 --- a/libedataserverui/e-name-selector-model.h +++ b/libedataserverui/e-name-selector-model.h @@ -24,7 +24,7 @@ #define E_NAME_SELECTOR_MODEL_H #include <glib.h> -#include <gtk/gtktreemodelfilter.h> +#include <libedataserverui/e-tree-model-generator.h> #include <libedataserverui/e-contact-store.h> #include <libedataserverui/e-destination-store.h> @@ -40,15 +40,15 @@ typedef struct ENameSelectorModel ENameSelectorModel; typedef struct ENameSelectorModelClass ENameSelectorModelClass; struct ENameSelectorModel { - GObject parent; + GObject parent; /* Private */ - GArray *sections; - EContactStore *contact_store; - GtkTreeModelFilter *contact_filter; + GArray *sections; + EContactStore *contact_store; + ETreeModelGenerator *contact_filter; - GHashTable *destination_uid_hash; + GHashTable *destination_uid_hash; }; struct ENameSelectorModelClass { @@ -59,24 +59,24 @@ struct ENameSelectorModelClass { void (* section_removed) (gchar *name); }; -GType e_name_selector_model_get_type (void); -ENameSelectorModel *e_name_selector_model_new (void); +GType e_name_selector_model_get_type (void); +ENameSelectorModel *e_name_selector_model_new (void); -EContactStore *e_name_selector_model_peek_contact_store (ENameSelectorModel *name_selector_model); -GtkTreeModelFilter *e_name_selector_model_peek_contact_filter (ENameSelectorModel *name_selector_model); +EContactStore *e_name_selector_model_peek_contact_store (ENameSelectorModel *name_selector_model); +ETreeModelGenerator *e_name_selector_model_peek_contact_filter (ENameSelectorModel *name_selector_model); /* Deep copy of section names; free strings and list when you're done */ -GList *e_name_selector_model_list_sections (ENameSelectorModel *name_selector_model); +GList *e_name_selector_model_list_sections (ENameSelectorModel *name_selector_model); /* pretty_name will be newly allocated, but destination_store must be reffed if you keep it */ -gboolean e_name_selector_model_peek_section (ENameSelectorModel *name_selector_model, - const gchar *name, gchar **pretty_name, - EDestinationStore **destination_store); -void e_name_selector_model_add_section (ENameSelectorModel *name_selector_model, - const gchar *name, const gchar *pretty_name, - EDestinationStore *destination_store); -void e_name_selector_model_remove_section (ENameSelectorModel *name_selector_model, - const gchar *name); +gboolean e_name_selector_model_peek_section (ENameSelectorModel *name_selector_model, + const gchar *name, gchar **pretty_name, + EDestinationStore **destination_store); +void e_name_selector_model_add_section (ENameSelectorModel *name_selector_model, + const gchar *name, const gchar *pretty_name, + EDestinationStore *destination_store); +void e_name_selector_model_remove_section (ENameSelectorModel *name_selector_model, + const gchar *name); G_END_DECLS diff --git a/libedataserverui/e-tree-model-generator.c b/libedataserverui/e-tree-model-generator.c new file mode 100644 index 000000000..239628a44 --- /dev/null +++ b/libedataserverui/e-tree-model-generator.c @@ -0,0 +1,1250 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* e-tree-model-generator.c - Model wrapper that permutes underlying rows. + * + * Copyright (C) 2005 Novell, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + * Authors: Hans Petter Jansson <hpj@novell.com> + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <string.h> +#include <gtk/gtktreemodel.h> +#include <gtk/gtksignal.h> +#include <libgnome/gnome-i18n.h> +#include "e-tree-model-generator.h" + +#define ETMG_DEBUG(x) + +#define ITER_IS_VALID(tree_model_generator, iter) ((iter)->stamp == (tree_model_generator)->stamp) +#define ITER_GET(iter, group, index) \ +G_STMT_START { \ + *(group) = (iter)->user_data; \ + *(index) = GPOINTER_TO_INT ((iter)->user_data2); \ +} G_STMT_END + +#define ITER_SET(tree_model_generator, iter, group, index) \ +G_STMT_START { \ + (iter)->stamp = (tree_model_generator)->stamp; \ + (iter)->user_data = group; \ + (iter)->user_data2 = GINT_TO_POINTER (index); \ +} G_STMT_END + +static void e_tree_model_generator_init (ETreeModelGenerator *tree_model_generator); +static void e_tree_model_generator_class_init (ETreeModelGeneratorClass *class); +static void e_tree_model_generator_tree_model_init (GtkTreeModelIface *iface); +static void e_tree_model_generator_finalize (GObject *object); +static guint e_tree_model_generator_get_flags (GtkTreeModel *tree_model); +static gint e_tree_model_generator_get_n_columns (GtkTreeModel *tree_model); +static GType e_tree_model_generator_get_column_type (GtkTreeModel *tree_model, + gint index); +static gboolean e_tree_model_generator_get_iter (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreePath *path); +static GtkTreePath *e_tree_model_generator_get_path (GtkTreeModel *tree_model, + GtkTreeIter *iter); +static void e_tree_model_generator_get_value (GtkTreeModel *tree_model, + GtkTreeIter *iter, + gint column, + GValue *value); +static gboolean e_tree_model_generator_iter_next (GtkTreeModel *tree_model, + GtkTreeIter *iter); +static gboolean e_tree_model_generator_iter_children (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreeIter *parent); +static gboolean e_tree_model_generator_iter_has_child (GtkTreeModel *tree_model, + GtkTreeIter *iter); +static gint e_tree_model_generator_iter_n_children (GtkTreeModel *tree_model, + GtkTreeIter *iter); +static gboolean e_tree_model_generator_iter_nth_child (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreeIter *parent, + gint n); +static gboolean e_tree_model_generator_iter_parent (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreeIter *child); + +static GArray *build_node_map (ETreeModelGenerator *tree_model_generator, GtkTreeIter *parent_iter, + GArray *parent_group, gint parent_index); +static void release_node_map (GArray *group); + +static void child_row_changed (ETreeModelGenerator *tree_model_generator, GtkTreePath *path, GtkTreeIter *iter); +static void child_row_inserted (ETreeModelGenerator *tree_model_generator, GtkTreePath *path, GtkTreeIter *iter); +static void child_row_deleted (ETreeModelGenerator *tree_model_generator, GtkTreePath *path); + +typedef struct { + GArray *parent_group; + gint parent_index; + + gint n_generated; + GArray *child_nodes; +} +Node; + +enum { + PROP_0, + PROP_CHILD_MODEL +}; + +/* ------------------ * + * Class/object setup * + * ------------------ */ + +static GObjectClass *parent_class = NULL; + +static void +e_tree_model_generator_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (object); + + switch (prop_id) + { + case PROP_CHILD_MODEL: + g_value_set_object (value, tree_model_generator->child_model); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +e_tree_model_generator_set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (object); + + switch (prop_id) + { + case PROP_CHILD_MODEL: + tree_model_generator->child_model = g_value_get_object (value); + g_object_ref (tree_model_generator->child_model); + + if (tree_model_generator->root_nodes) + release_node_map (tree_model_generator->root_nodes); + tree_model_generator->root_nodes = + build_node_map (tree_model_generator, NULL, NULL, -1); + + g_signal_connect_swapped (tree_model_generator->child_model, "row-changed", + G_CALLBACK (child_row_changed), tree_model_generator); + g_signal_connect_swapped (tree_model_generator->child_model, "row-deleted", + G_CALLBACK (child_row_deleted), tree_model_generator); + g_signal_connect_swapped (tree_model_generator->child_model, "row-inserted", + G_CALLBACK (child_row_inserted), tree_model_generator); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +GType +e_tree_model_generator_get_type (void) +{ + static GType tree_model_generator_type = 0; + + if (!tree_model_generator_type) { + static const GTypeInfo tree_model_generator_info = + { + sizeof (ETreeModelGeneratorClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) e_tree_model_generator_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (ETreeModelGenerator), + 0, + (GInstanceInitFunc) e_tree_model_generator_init, + }; + + static const GInterfaceInfo tree_model_info = + { + (GInterfaceInitFunc) e_tree_model_generator_tree_model_init, + NULL, + NULL + }; + + tree_model_generator_type = g_type_register_static (G_TYPE_OBJECT, "ETreeModelGenerator", + &tree_model_generator_info, 0); + g_type_add_interface_static (tree_model_generator_type, + GTK_TYPE_TREE_MODEL, + &tree_model_info); + } + + return tree_model_generator_type; +} + +static void +e_tree_model_generator_class_init (ETreeModelGeneratorClass *class) +{ + GObjectClass *object_class; + + parent_class = g_type_class_peek_parent (class); + object_class = (GObjectClass *) class; + + object_class->get_property = e_tree_model_generator_get_property; + object_class->set_property = e_tree_model_generator_set_property; + object_class->finalize = e_tree_model_generator_finalize; + + g_object_class_install_property (object_class, PROP_CHILD_MODEL, + g_param_spec_object ("child-model", + "Child Model", + "The child model to extend", + G_TYPE_OBJECT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); +} + +static void +e_tree_model_generator_tree_model_init (GtkTreeModelIface *iface) +{ + iface->get_flags = e_tree_model_generator_get_flags; + iface->get_n_columns = e_tree_model_generator_get_n_columns; + iface->get_column_type = e_tree_model_generator_get_column_type; + iface->get_iter = e_tree_model_generator_get_iter; + iface->get_path = e_tree_model_generator_get_path; + iface->get_value = e_tree_model_generator_get_value; + iface->iter_next = e_tree_model_generator_iter_next; + iface->iter_children = e_tree_model_generator_iter_children; + iface->iter_has_child = e_tree_model_generator_iter_has_child; + iface->iter_n_children = e_tree_model_generator_iter_n_children; + iface->iter_nth_child = e_tree_model_generator_iter_nth_child; + iface->iter_parent = e_tree_model_generator_iter_parent; +} + +static void +e_tree_model_generator_init (ETreeModelGenerator *tree_model_generator) +{ + tree_model_generator->stamp = g_random_int (); + tree_model_generator->root_nodes = g_array_new (FALSE, FALSE, sizeof (Node)); +} + +static void +e_tree_model_generator_finalize (GObject *object) +{ + ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (object); + + if (tree_model_generator->child_model) { + g_signal_handlers_disconnect_matched (tree_model_generator->child_model, + G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, + tree_model_generator); + g_object_unref (tree_model_generator->child_model); + } + + if (tree_model_generator->root_nodes) + release_node_map (tree_model_generator->root_nodes); + + if (G_OBJECT_CLASS (parent_class)->finalize) + (* G_OBJECT_CLASS (parent_class)->finalize) (object); +} + +/* ------------------ * + * Row update helpers * + * ------------------ */ + +static void +row_deleted (ETreeModelGenerator *tree_model_generator, GtkTreePath *path) +{ + g_assert (path); + + ETMG_DEBUG (g_print ("row_deleted emitting\n")); + gtk_tree_model_row_deleted (GTK_TREE_MODEL (tree_model_generator), path); +} + +static void +row_inserted (ETreeModelGenerator *tree_model_generator, GtkTreePath *path) +{ + GtkTreeIter iter; + + g_assert (path); + + if (gtk_tree_model_get_iter (GTK_TREE_MODEL (tree_model_generator), &iter, path)) { + ETMG_DEBUG (g_print ("row_inserted emitting\n")); + gtk_tree_model_row_inserted (GTK_TREE_MODEL (tree_model_generator), path, &iter); + } else { + ETMG_DEBUG (g_print ("row_inserted could not get iter!\n")); + } +} + +static void +row_changed (ETreeModelGenerator *tree_model_generator, GtkTreePath *path) +{ + GtkTreeIter iter; + + g_assert (path); + + if (gtk_tree_model_get_iter (GTK_TREE_MODEL (tree_model_generator), &iter, path)) { + ETMG_DEBUG (g_print ("row_changed emitting\n")); + gtk_tree_model_row_changed (GTK_TREE_MODEL (tree_model_generator), path, &iter); + } else { + ETMG_DEBUG (g_print ("row_changed could not get iter!\n")); + } +} + +/* -------------------- * + * Node map translation * + * -------------------- */ + +static gint +generated_offset_to_child_offset (GArray *group, gint offset, gint *internal_offset) +{ + gboolean success = FALSE; + gint accum_offset = 0; + gint i; + + for (i = 0; i < group->len; i++) { + Node *node = &g_array_index (group, Node, i); + + accum_offset += node->n_generated; + if (accum_offset > offset) { + accum_offset -= node->n_generated; + success = TRUE; + break; + } + } + + if (!success) + return -1; + + if (internal_offset) + *internal_offset = offset - accum_offset; + + return i; +} + +static gint +child_offset_to_generated_offset (GArray *group, gint offset) +{ + gint accum_offset = 0; + gint i; + + for (i = 0; i < group->len && i < offset; i++) { + Node *node = &g_array_index (group, Node, i); + + accum_offset += node->n_generated; + } + + return accum_offset; +} + +static gint +count_generated_nodes (GArray *group) +{ + gint accum_offset = 0; + gint i; + + for (i = 0; i < group->len; i++) { + Node *node = &g_array_index (group, Node, i); + + accum_offset += node->n_generated; + } + + return accum_offset; +} + +/* ------------------- * + * Node map management * + * ------------------- */ + +static void +release_node_map (GArray *group) +{ + gint i; + + for (i = 0; i < group->len; i++) { + Node *node = &g_array_index (group, Node, i); + + if (node->child_nodes) + release_node_map (node->child_nodes); + } + + g_array_free (group, TRUE); +} + +static gint +append_node (GArray *group) +{ + g_array_set_size (group, group->len + 1); + return group->len - 1; +} + +static GArray * +build_node_map (ETreeModelGenerator *tree_model_generator, GtkTreeIter *parent_iter, + GArray *parent_group, gint parent_index) +{ + GArray *group; + GtkTreeIter iter; + gboolean result; + + if (parent_iter) + result = gtk_tree_model_iter_children (tree_model_generator->child_model, &iter, parent_iter); + else + result = gtk_tree_model_get_iter_first (tree_model_generator->child_model, &iter); + + if (!result) + return NULL; + + group = g_array_new (FALSE, FALSE, sizeof (Node)); + + do { + Node *node; + gint i; + + i = append_node (group); + node = &g_array_index (group, Node, i); + + node->parent_group = parent_group; + node->parent_index = parent_index; + + if (tree_model_generator->generate_func) + node->n_generated = + tree_model_generator->generate_func (tree_model_generator->child_model, + &iter, tree_model_generator->generate_func_data); + else + node->n_generated = 1; + + node->child_nodes = build_node_map (tree_model_generator, &iter, group, i); + } while (gtk_tree_model_iter_next (tree_model_generator->child_model, &iter)); + + return group; +} + +static gint +get_first_visible_index_from (GArray *group, gint index) +{ + gint i; + + for (i = index; i < group->len; i++) { + Node *node = &g_array_index (group, Node, i); + + if (node->n_generated) + break; + } + + if (i >= group->len) + i = -1; + + return i; +} + +static Node * +get_node_by_child_path (ETreeModelGenerator *tree_model_generator, GtkTreePath *path, GArray **node_group) +{ + Node *node = NULL; + GArray *group; + gint depth; + + group = tree_model_generator->root_nodes; + + for (depth = 0; depth < gtk_tree_path_get_depth (path); depth++) { + gint index; + gint generated_index; + + if (!group) { + g_warning ("ETreeModelGenerator got unknown child element!"); + break; + } + + index = gtk_tree_path_get_indices (path) [depth]; + node = &g_array_index (group, Node, index); + + if (depth + 1 < gtk_tree_path_get_depth (path)) + group = node->child_nodes; + } + + if (!node) + group = NULL; + + if (node_group) + *node_group = group; + + return node; +} + +static Node * +create_node_at_child_path (ETreeModelGenerator *tree_model_generator, GtkTreePath *path) +{ + GtkTreePath *parent_path; + gint parent_index; + GArray *parent_group; + GArray *group; + gint index; + Node *node; + + parent_path = gtk_tree_path_copy (path); + gtk_tree_path_up (parent_path); + node = get_node_by_child_path (tree_model_generator, parent_path, &parent_group); + + if (node) { + if (!node->child_nodes) + node->child_nodes = g_array_new (FALSE, FALSE, sizeof (Node)); + + group = node->child_nodes; + parent_index = gtk_tree_path_get_indices (parent_path) [gtk_tree_path_get_depth (parent_path) - 1]; + } else { + if (!tree_model_generator->root_nodes) + tree_model_generator->root_nodes = g_array_new (FALSE, FALSE, sizeof (Node)); + + group = tree_model_generator->root_nodes; + parent_index = -1; + } + + gtk_tree_path_free (parent_path); + + index = gtk_tree_path_get_indices (path) [gtk_tree_path_get_depth (path) - 1]; + ETMG_DEBUG (g_print ("Inserting index %d into group of length %d\n", index, group->len)); + index = MIN (index, group->len); + + append_node (group); + + if (group->len - 1 - index > 0) { + gint i; + + memmove ((Node *) group->data + index + 1, + (Node *) group->data + index, + (group->len - 1 - index) * sizeof (Node)); + + /* Update parent pointers */ + for (i = index + 1; i < group->len; i++) { + Node *node = &g_array_index (group, Node, i); + GArray *child_group; + gint j; + + child_group = node->child_nodes; + if (!child_group) + continue; + + for (j = 0; j < child_group->len; j++) { + Node *child_node = &g_array_index (child_group, Node, j); + child_node->parent_index = i; + } + } + } + + node = &g_array_index (group, Node, index); + node->parent_group = parent_group; + node->parent_index = parent_index; + node->n_generated = 0; + node->child_nodes = NULL; + + ETMG_DEBUG (g_print ("Created node at offset %d, parent_group = %p, parent_index = %d\n", + index, node->parent_group, node->parent_index)); + + return node; +} + +ETMG_DEBUG ( + +static void +dump_group (GArray *group) +{ + gint i; + + g_print ("\nGroup %p:\n", group); + + for (i = 0; i < group->len; i++) { + Node *node = &g_array_index (group, Node, i); + g_print (" %04d: pgroup=%p, pindex=%d, n_generated=%d, child_nodes=%p\n", + i, node->parent_group, node->parent_index, node->n_generated, node->child_nodes); + } +} + +) + +static void +delete_node_at_child_path (ETreeModelGenerator *tree_model_generator, GtkTreePath *path) +{ + GtkTreePath *parent_path; + gint parent_index; + GArray *parent_group; + GArray *group; + gint index; + Node *node; + gint i; + + parent_path = gtk_tree_path_copy (path); + gtk_tree_path_up (parent_path); + node = get_node_by_child_path (tree_model_generator, parent_path, &parent_group); + + if (node) { + group = node->child_nodes; + parent_index = gtk_tree_path_get_indices (parent_path) [gtk_tree_path_get_depth (parent_path) - 1]; + } else { + group = tree_model_generator->root_nodes; + parent_index = -1; + } + + gtk_tree_path_free (parent_path); + + if (!group) + return; + + index = gtk_tree_path_get_indices (path) [gtk_tree_path_get_depth (path) - 1]; + if (index >= group->len) + return; + + node = &g_array_index (group, Node, index); + if (node->child_nodes) + release_node_map (node->child_nodes); + g_array_remove_index (group, index); + + /* Update parent pointers */ + for (i = index; i < group->len; i++) { + Node *node = &g_array_index (group, Node, i); + GArray *child_group; + gint j; + + child_group = node->child_nodes; + if (!child_group) + continue; + + for (j = 0; j < child_group->len; j++) { + Node *child_node = &g_array_index (child_group, Node, j); + child_node->parent_index = i; + } + } +} + +static void +child_row_changed (ETreeModelGenerator *tree_model_generator, GtkTreePath *path, GtkTreeIter *iter) +{ + GtkTreePath *generated_path; + Node *node; + gint n_generated; + gint i; + + if (tree_model_generator->generate_func) + n_generated = + tree_model_generator->generate_func (tree_model_generator->child_model, + iter, tree_model_generator->generate_func_data); + else + n_generated = 1; + + node = get_node_by_child_path (tree_model_generator, path, NULL); + if (!node) + return; + + generated_path = e_tree_model_generator_convert_child_path_to_path (tree_model_generator, path); + + /* FIXME: Converting the path to an iter every time is inefficient */ + + for (i = 0; i < n_generated && i < node->n_generated; i++) { + row_changed (tree_model_generator, generated_path); + gtk_tree_path_next (generated_path); + } + + for ( ; i < node->n_generated; ) { + node->n_generated--; + row_deleted (tree_model_generator, generated_path); + } + + for ( ; i < n_generated; i++) { + node->n_generated++; + row_inserted (tree_model_generator, generated_path); + gtk_tree_path_next (generated_path); + } + + gtk_tree_path_free (generated_path); +} + +static void +child_row_inserted (ETreeModelGenerator *tree_model_generator, GtkTreePath *path, GtkTreeIter *iter) +{ + GtkTreePath *generated_path; + Node *node; + gint n_generated; + gint i; + + if (tree_model_generator->generate_func) + n_generated = + tree_model_generator->generate_func (tree_model_generator->child_model, + iter, tree_model_generator->generate_func_data); + else + n_generated = 1; + + node = create_node_at_child_path (tree_model_generator, path); + if (!node) + return; + + generated_path = e_tree_model_generator_convert_child_path_to_path (tree_model_generator, path); + + /* FIXME: Converting the path to an iter every time is inefficient */ + + for (node->n_generated = 0; node->n_generated < n_generated; ) { + node->n_generated++; + row_inserted (tree_model_generator, generated_path); + gtk_tree_path_next (generated_path); + } + + gtk_tree_path_free (generated_path); +} + +static void +child_row_deleted (ETreeModelGenerator *tree_model_generator, GtkTreePath *path) +{ + GtkTreePath *generated_path; + Node *node; + + node = get_node_by_child_path (tree_model_generator, path, NULL); + if (!node) + return; + + generated_path = e_tree_model_generator_convert_child_path_to_path (tree_model_generator, path); + + /* FIXME: Converting the path to an iter every time is inefficient */ + + for ( ; node->n_generated; ) { + node->n_generated--; + row_deleted (tree_model_generator, generated_path); + } + + delete_node_at_child_path (tree_model_generator, path); + gtk_tree_path_free (generated_path); +} + +/* ----------------------- * + * ETreeModelGenerator API * + * ----------------------- */ + +ETreeModelGenerator * +e_tree_model_generator_new (GtkTreeModel *child_model) +{ + g_return_val_if_fail (GTK_IS_TREE_MODEL (child_model), NULL); + + return E_TREE_MODEL_GENERATOR (g_object_new (E_TYPE_TREE_MODEL_GENERATOR, + "child-model", child_model, NULL)); +} + +GtkTreeModel * +e_tree_model_generator_get_model (ETreeModelGenerator *tree_model_generator) +{ + g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model_generator), NULL); + + return tree_model_generator->child_model; +} + +void +e_tree_model_generator_set_generate_func (ETreeModelGenerator *tree_model_generator, + ETreeModelGeneratorGenerateFunc func, + gpointer data, GtkDestroyNotify destroy) +{ + g_return_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model_generator)); + + tree_model_generator->generate_func = func; + tree_model_generator->generate_func_data = data; +} + +void +e_tree_model_generator_set_modify_func (ETreeModelGenerator *tree_model_generator, + ETreeModelGeneratorModifyFunc func, + gpointer data, GtkDestroyNotify destroy) +{ + g_return_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model_generator)); + + tree_model_generator->modify_func = func; + tree_model_generator->modify_func_data = data; +} + +GtkTreePath * +e_tree_model_generator_convert_child_path_to_path (ETreeModelGenerator *tree_model_generator, + GtkTreePath *child_path) +{ + GtkTreePath *path; + GArray *group; + gint depth; + + g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model_generator), NULL); + g_return_val_if_fail (child_path != NULL, NULL); + + path = gtk_tree_path_new (); + + group = tree_model_generator->root_nodes; + + for (depth = 0; depth < gtk_tree_path_get_depth (child_path); depth++) { + Node *node; + gint index; + gint generated_index; + + if (!group) { + g_warning ("ETreeModelGenerator was asked for path to unknown child element!"); + break; + } + + index = gtk_tree_path_get_indices (child_path) [depth]; + generated_index = child_offset_to_generated_offset (group, index); + node = &g_array_index (group, Node, index); + group = node->child_nodes; + + gtk_tree_path_append_index (path, generated_index); + } + + return path; +} + +void +e_tree_model_generator_convert_child_iter_to_iter (ETreeModelGenerator *tree_model_generator, + GtkTreeIter *generator_iter, + GtkTreeIter *child_iter) +{ + GtkTreePath *path; + GArray *group; + gint depth; + gint index = 0; + + g_return_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model_generator)); + + path = gtk_tree_model_get_path (tree_model_generator->child_model, child_iter); + if (!path) + return; + + group = tree_model_generator->root_nodes; + + for (depth = 0; depth < gtk_tree_path_get_depth (path); depth++) { + Node *node; + + index = gtk_tree_path_get_indices (path) [depth]; + node = &g_array_index (group, Node, index); + + if (depth + 1 < gtk_tree_path_get_depth (path)) + group = node->child_nodes; + + if (!group) { + g_warning ("ETreeModelGenerator was asked for iter to unknown child element!"); + break; + } + } + + index = child_offset_to_generated_offset (group, index); + ITER_SET (tree_model_generator, generator_iter, group, index); + gtk_tree_path_free (path); +} + +GtkTreePath * +e_tree_model_generator_convert_path_to_child_path (ETreeModelGenerator *tree_model_generator, + GtkTreePath *generator_path) +{ + GtkTreePath *path; + GArray *group; + gint depth; + + g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model_generator), NULL); + g_return_val_if_fail (generator_path != NULL, NULL); + + path = gtk_tree_path_new (); + + group = tree_model_generator->root_nodes; + + for (depth = 0; depth < gtk_tree_path_get_depth (generator_path); depth++) { + Node *node; + gint index; + gint child_index; + + if (!group) { + g_warning ("ETreeModelGenerator was asked for path to unknown child element!"); + break; + } + + index = gtk_tree_path_get_indices (generator_path) [depth]; + child_index = generated_offset_to_child_offset (group, index, NULL); + node = &g_array_index (group, Node, child_index); + group = node->child_nodes; + + gtk_tree_path_append_index (path, child_index); + } + + return path; +} + +void +e_tree_model_generator_convert_iter_to_child_iter (ETreeModelGenerator *tree_model_generator, + GtkTreeIter *child_iter, + gint *permutation_n, + GtkTreeIter *generator_iter) +{ + GtkTreePath *path; + GArray *group; + gint generator_index; + gint index; + gint internal_offset; + + g_return_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model_generator)); + g_return_if_fail (ITER_IS_VALID (tree_model_generator, generator_iter)); + + path = gtk_tree_path_new (); + ITER_GET (generator_iter, &group, &index); + + generator_index = index; + index = generated_offset_to_child_offset (group, index, &internal_offset); + gtk_tree_path_prepend_index (path, index); + + ETMG_DEBUG (g_print ("iter_to_child_iter: %d -> %d (%d)\n", generator_index, index, internal_offset)); + + while (group) { + Node *node = &g_array_index (group, Node, index); + gint generated_index; + + group = node->parent_group; + index = node->parent_index; + + if (group) + gtk_tree_path_prepend_index (path, index); + } + + if (child_iter) + gtk_tree_model_get_iter (tree_model_generator->child_model, child_iter, path); + if (permutation_n) + *permutation_n = internal_offset; + + gtk_tree_path_free (path); +} + +/* ---------------- * + * GtkTreeModel API * + * ---------------- */ + +static guint +e_tree_model_generator_get_flags (GtkTreeModel *tree_model) +{ + ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (tree_model); + + g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model), 0); + + return gtk_tree_model_get_flags (tree_model_generator->child_model); +} + +static gint +e_tree_model_generator_get_n_columns (GtkTreeModel *tree_model) +{ + ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (tree_model); + + g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model), 0); + + return gtk_tree_model_get_n_columns (tree_model_generator->child_model); +} + +static GType +e_tree_model_generator_get_column_type (GtkTreeModel *tree_model, + gint index) +{ + ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (tree_model); + + g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model), G_TYPE_INVALID); + + return gtk_tree_model_get_column_type (tree_model_generator->child_model, index); +} + +static gboolean +e_tree_model_generator_get_iter (GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreePath *path) +{ + ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (tree_model); + GArray *group; + gint depth; + gint index = 0; + + g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model), FALSE); + g_return_val_if_fail (gtk_tree_path_get_depth (path) > 0, FALSE); + + group = tree_model_generator->root_nodes; + if (!group) + return FALSE; + + for (depth = 0; depth < gtk_tree_path_get_depth (path); depth++) { + Node *node; + gint child_index; + + index = gtk_tree_path_get_indices (path) [depth]; + child_index = generated_offset_to_child_offset (group, index, NULL); + if (child_index < 0) + return FALSE; + + node = &g_array_index (group, Node, child_index); + + if (depth + 1 < gtk_tree_path_get_depth (path)) { + group = node->child_nodes; + if (!group) + return FALSE; + } + } + + ITER_SET (tree_model_generator, iter, group, index); + return TRUE; +} + +static GtkTreePath * +e_tree_model_generator_get_path (GtkTreeModel *tree_model, + GtkTreeIter *iter) +{ + ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (tree_model); + GtkTreePath *path; + GArray *group; + gint index; + + g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model), NULL); + g_return_val_if_fail (ITER_IS_VALID (tree_model_generator, iter), NULL); + + ITER_GET (iter, &group, &index); + path = gtk_tree_path_new (); + + /* FIXME: Converting a path to an iter is a destructive operation, because + * we don't store a node for each generated entry... Doesn't matter for + * lists, not sure about trees. */ + + gtk_tree_path_prepend_index (path, index); + index = generated_offset_to_child_offset (group, index, NULL); + + while (group) { + Node *node = &g_array_index (group, Node, index); + gint generated_index; + + group = node->parent_group; + index = node->parent_index; + generated_index = child_offset_to_generated_offset (group, index); + + if (group) + gtk_tree_path_prepend_index (path, generated_index); + } + + return path; +} + +static gboolean +e_tree_model_generator_iter_next (GtkTreeModel *tree_model, + GtkTreeIter *iter) +{ + ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (tree_model); + Node *node; + GArray *group; + gint index; + gint child_index; + gint internal_offset; + + g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model), FALSE); + g_return_val_if_fail (ITER_IS_VALID (tree_model_generator, iter), FALSE); + + ITER_GET (iter, &group, &index); + child_index = generated_offset_to_child_offset (group, index, &internal_offset); + node = &g_array_index (group, Node, child_index); + + if (internal_offset + 1 < node->n_generated || + get_first_visible_index_from (group, child_index + 1) >= 0) { + ITER_SET (tree_model_generator, iter, group, index + 1); + return TRUE; + } + + return FALSE; +} + +static gboolean +e_tree_model_generator_iter_children (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreeIter *parent) +{ + ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (tree_model); + Node *node; + GArray *group; + gint index; + + g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model), FALSE); + + if (!parent) { + if (!tree_model_generator->root_nodes || + !count_generated_nodes (tree_model_generator->root_nodes)) + return FALSE; + + ITER_SET (tree_model_generator, iter, tree_model_generator->root_nodes, 0); + return TRUE; + } + + ITER_GET (parent, &group, &index); + index = generated_offset_to_child_offset (group, index, NULL); + if (index < 0) + return FALSE; + + node = &g_array_index (group, Node, index); + + if (!node->child_nodes) + return FALSE; + + if (!count_generated_nodes (node->child_nodes)) + return FALSE; + + ITER_SET (tree_model_generator, iter, node->child_nodes, 0); + return TRUE; +} + +static gboolean +e_tree_model_generator_iter_has_child (GtkTreeModel *tree_model, + GtkTreeIter *iter) +{ + ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (tree_model); + Node *node; + GArray *group; + gint index; + + g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model), FALSE); + + if (iter == NULL) { + if (!tree_model_generator->root_nodes || + !count_generated_nodes (tree_model_generator->root_nodes)) + return FALSE; + + return TRUE; + } + + ITER_GET (iter, &group, &index); + index = generated_offset_to_child_offset (group, index, NULL); + if (!index < 0) + return FALSE; + + node = &g_array_index (group, Node, index); + + if (!node->child_nodes) + return FALSE; + + if (!count_generated_nodes (node->child_nodes)) + return FALSE; + + return TRUE; +} + +static gint +e_tree_model_generator_iter_n_children (GtkTreeModel *tree_model, + GtkTreeIter *iter) +{ + ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (tree_model); + Node *node; + GArray *group; + gint index; + + g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model), FALSE); + + if (iter == NULL) + return tree_model_generator->root_nodes ? + count_generated_nodes (tree_model_generator->root_nodes) : 0; + + ITER_GET (iter, &group, &index); + index = generated_offset_to_child_offset (group, index, NULL); + if (index < 0) + return 0; + + node = &g_array_index (group, Node, index); + + if (!node->child_nodes) + return 0; + + return count_generated_nodes (node->child_nodes); +} + +static gboolean +e_tree_model_generator_iter_nth_child (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreeIter *parent, + gint n) +{ + ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (tree_model); + Node *node; + GArray *group; + gint index; + + g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model), FALSE); + + if (!parent) { + if (!tree_model_generator->root_nodes) + return FALSE; + + if (n >= count_generated_nodes (tree_model_generator->root_nodes)) + return FALSE; + + ITER_SET (tree_model_generator, iter, tree_model_generator->root_nodes, n); + return TRUE; + } + + ITER_GET (parent, &group, &index); + index = generated_offset_to_child_offset (group, index, NULL); + if (index < 0) + return FALSE; + + node = &g_array_index (group, Node, index); + + if (!node->child_nodes) + return FALSE; + + if (n >= count_generated_nodes (node->child_nodes)) + return FALSE; + + ITER_SET (tree_model_generator, iter, node->child_nodes, n); + return TRUE; +} + +static gboolean +e_tree_model_generator_iter_parent (GtkTreeModel *tree_model, + GtkTreeIter *iter, + GtkTreeIter *child) +{ + ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (tree_model); + Node *node; + GArray *group; + gint index; + + g_return_val_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model), FALSE); + g_return_val_if_fail (ITER_IS_VALID (tree_model_generator, iter), FALSE); + + ITER_GET (child, &group, &index); + index = generated_offset_to_child_offset (group, index, NULL); + if (index < 0) + return FALSE; + + node = &g_array_index (group, Node, index); + + group = node->parent_group; + if (!group) + return FALSE; + + ITER_SET (tree_model_generator, iter, group, node->parent_index); + return TRUE; +} + +static void +e_tree_model_generator_get_value (GtkTreeModel *tree_model, + GtkTreeIter *iter, + gint column, + GValue *value) +{ + ETreeModelGenerator *tree_model_generator = E_TREE_MODEL_GENERATOR (tree_model); + GtkTreeIter child_iter; + gint permutation_n; + + g_return_if_fail (E_IS_TREE_MODEL_GENERATOR (tree_model)); + g_return_if_fail (ITER_IS_VALID (tree_model_generator, iter)); + + e_tree_model_generator_convert_iter_to_child_iter (tree_model_generator, &child_iter, + &permutation_n, iter); + + if (tree_model_generator->modify_func) { + tree_model_generator->modify_func (tree_model_generator->child_model, + &child_iter, permutation_n, + column, value, + tree_model_generator->modify_func_data); + return; + } + + gtk_tree_model_get_value (tree_model_generator->child_model, &child_iter, column, value); +} diff --git a/libedataserverui/e-tree-model-generator.h b/libedataserverui/e-tree-model-generator.h new file mode 100644 index 000000000..09a818f4b --- /dev/null +++ b/libedataserverui/e-tree-model-generator.h @@ -0,0 +1,92 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* e-tree-model-generator.h - Model wrapper that permutes underlying rows. + * + * Copyright (C) 2005 Novell, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * + * Authors: Hans Petter Jansson <hpj@novell.com> + */ + +#ifndef E_TREE_MODEL_GENERATOR_H +#define E_TREE_MODEL_GENERATOR_H + +#include <gtk/gtktreemodel.h> + +G_BEGIN_DECLS + +#define E_TYPE_TREE_MODEL_GENERATOR (e_tree_model_generator_get_type ()) +#define E_TREE_MODEL_GENERATOR(obj) (GTK_CHECK_CAST ((obj), E_TYPE_TREE_MODEL_GENERATOR, ETreeModelGenerator)) +#define E_TREE_MODEL_GENERATOR_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), E_TYPE_TREE_MODEL_GENERATOR, ETreeModelGeneratorClass)) +#define E_IS_TREE_MODEL_GENERATOR(obj) (GTK_CHECK_TYPE ((obj), E_TYPE_TREE_MODEL_GENERATOR)) +#define E_IS_TREE_MODEL_GENERATOR_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), E_TYPE_TREE_MODEL_GENERATOR)) +#define E_TREE_MODEL_GENERATOR_GET_CLASS(obj) (GTK_CHECK_GET_CLASS ((obj), E_TYPE_TREE_MODEL_GENERATOR, ETreeModelGeneratorClass)) + +typedef gint (*ETreeModelGeneratorGenerateFunc) (GtkTreeModel *model, GtkTreeIter *child_iter, + gpointer data); +typedef void (*ETreeModelGeneratorModifyFunc) (GtkTreeModel *model, GtkTreeIter *child_iter, + gint permutation_n, gint column, GValue *value, + gpointer data); + +typedef struct ETreeModelGenerator ETreeModelGenerator; +typedef struct ETreeModelGeneratorClass ETreeModelGeneratorClass; + +struct ETreeModelGeneratorClass { + GObjectClass parent_class; +}; + +struct ETreeModelGenerator { + GObject parent; + + /* Private */ + + gint stamp; + GtkTreeModel *child_model; + GArray *root_nodes; + + ETreeModelGeneratorGenerateFunc generate_func; + gpointer generate_func_data; + + ETreeModelGeneratorModifyFunc modify_func; + gpointer modify_func_data; +}; + +GtkType e_tree_model_generator_get_type (void); + +ETreeModelGenerator *e_tree_model_generator_new (GtkTreeModel *child_model); +GtkTreeModel *e_tree_model_generator_get_model (ETreeModelGenerator *tree_model_generator); + +void e_tree_model_generator_set_generate_func (ETreeModelGenerator *tree_model_generator, + ETreeModelGeneratorGenerateFunc func, + gpointer data, GtkDestroyNotify destroy); +void e_tree_model_generator_set_modify_func (ETreeModelGenerator *tree_model_generator, + ETreeModelGeneratorModifyFunc func, + gpointer data, GtkDestroyNotify destroy); + +GtkTreePath *e_tree_model_generator_convert_child_path_to_path (ETreeModelGenerator *tree_model_generator, + GtkTreePath *child_path); +void e_tree_model_generator_convert_child_iter_to_iter (ETreeModelGenerator *tree_model_generator, + GtkTreeIter *generator_iter, + GtkTreeIter *child_iter); +GtkTreePath *e_tree_model_generator_convert_path_to_child_path (ETreeModelGenerator *tree_model_generator, + GtkTreePath *generator_path); +void e_tree_model_generator_convert_iter_to_child_iter (ETreeModelGenerator *tree_model_generator, + GtkTreeIter *child_iter, + gint *permutation_n, + GtkTreeIter *generator_iter); + +G_END_DECLS + +#endif /* E_TREE_MODEL_GENERATOR_H */ |
