diff options
Diffstat (limited to 'addressbook/gui/contact-editor/e-contact-editor-dyntable.c')
-rw-r--r-- | addressbook/gui/contact-editor/e-contact-editor-dyntable.c | 738 |
1 files changed, 738 insertions, 0 deletions
diff --git a/addressbook/gui/contact-editor/e-contact-editor-dyntable.c b/addressbook/gui/contact-editor/e-contact-editor-dyntable.c new file mode 100644 index 0000000000..4706d5b1f9 --- /dev/null +++ b/addressbook/gui/contact-editor/e-contact-editor-dyntable.c @@ -0,0 +1,738 @@ +/* + * e-contact-editor-dyntable.c + * + * This program 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. + * + * 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 Lesser General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + * + */ + +#include "e-contact-editor-dyntable.h" + +G_DEFINE_TYPE(EContactEditorDynTable, e_contact_editor_dyntable, GTK_TYPE_GRID) + +/* one position is occupied by two widgets: combo+entry */ +#define ENTRY_SIZE 2 +#define MAX_CAPACITY 100 + +enum { + CHANGED_SIGNAL, + ACTIVATE_SIGNAL, + ROW_ADDED_SIGNAL, + LAST_SIGNAL +}; + +static guint dyntable_signals[LAST_SIGNAL]; + +struct _EContactEditorDynTablePrivate { + + /* absolute max, dyntable will ignore the rest */ + guint max_entries; + + /* current number of entries with text or requested by user*/ + guint curr_entries; + + /* minimum to show, with or without text */ + guint show_min_entries; + + /* no matter how much data, show only */ + guint show_max_entries; + + /* number of entries (combo/text) per row */ + guint columns; + + /* if true, fill line with empty slots*/ + gboolean justified; + + GtkWidget *add_button; + GtkListStore *combo_store; + GtkListStore *data_store; + + /* array of default values for combo box */ + const gint *combo_defaults; + + /* number of elements in the array */ + size_t combo_defaults_n; +}; + +GtkWidget* +e_contact_editor_dyntable_new (void) +{ + GtkWidget* widget; + widget = GTK_WIDGET (g_object_new (e_contact_editor_dyntable_get_type (), NULL)); + return widget; +} + +static void +set_combo_box_active (EContactEditorDynTable *dyntable, + GtkComboBox *combo_box, + gint active) +{ + g_signal_handlers_block_matched (combo_box, + G_SIGNAL_MATCH_DATA, 0, 0, NULL, + NULL, dyntable); + gtk_combo_box_set_active (combo_box, active); + g_signal_handlers_unblock_matched (combo_box, + G_SIGNAL_MATCH_DATA, 0, 0, NULL, + NULL, dyntable); +} + +/* translate position into column/row */ +static void +position_to_grid (EContactEditorDynTable *dyntable, + guint pos, + guint *col, + guint *row) +{ + *row = pos / dyntable->priv->columns; + *col = pos % dyntable->priv->columns * ENTRY_SIZE; +} + +static void +move_widget (GtkGrid *grid, GtkWidget *w, guint col, guint row) +{ + GValue rowValue = G_VALUE_INIT, colValue = G_VALUE_INIT; + + g_value_init (&rowValue, G_TYPE_UINT); + g_value_init (&colValue, G_TYPE_UINT); + + g_value_set_uint (&rowValue, row); + g_value_set_uint (&colValue, col); + + gtk_container_child_set_property (GTK_CONTAINER (grid), w, + "left-attach", &colValue); + gtk_container_child_set_property (GTK_CONTAINER (grid), w, + "top-attach", &rowValue); +} + +static gboolean +is_button_required (EContactEditorDynTable *dyntable) +{ + if (dyntable->priv->curr_entries >= dyntable->priv->max_entries) + return FALSE; + if (dyntable->priv->curr_entries <= dyntable->priv->show_max_entries) + return TRUE; + else + return FALSE; +} + +static void +sensitize_button (EContactEditorDynTable *dyntable) +{ + guint row, col, current_entries; + GtkWidget *w; + GtkGrid *grid; + EContactEditorDynTableClass *class; + gboolean enabled; + + grid = GTK_GRID (dyntable); + class = E_CONTACT_EDITOR_DYNTABLE_GET_CLASS (dyntable); + + current_entries = dyntable->priv->curr_entries; + enabled = TRUE; + if (current_entries > 0) { + /* last entry */ + current_entries--; + position_to_grid (dyntable, current_entries, &col, &row); + w = gtk_grid_get_child_at (grid, col + 1, row); + + enabled = !class->widget_is_empty (dyntable, w); + } + + gtk_widget_set_sensitive (dyntable->priv->add_button, enabled); +} + +static void +show_button (EContactEditorDynTable *dyntable) +{ + guint col,row, pos; + gboolean visible = FALSE; + GtkGrid *grid; + + grid = GTK_GRID (dyntable); + + /* move button to end of current line */ + pos = dyntable->priv->curr_entries; + if (pos > 0) + pos--; + position_to_grid(dyntable, pos, &col, &row); + move_widget (grid, dyntable->priv->add_button, + dyntable->priv->columns*ENTRY_SIZE+1, row); + + /* set visibility */ + if (is_button_required (dyntable)) + visible = TRUE; + + gtk_widget_set_visible (dyntable->priv->add_button, visible); + + sensitize_button (dyntable); +} + +static void +increment_counter (EContactEditorDynTable *dyntable) +{ + dyntable->priv->curr_entries++; + show_button (dyntable); +} + +static void +decrement_counter (EContactEditorDynTable *dyntable) +{ + dyntable->priv->curr_entries--; + show_button (dyntable); +} + +static gint +get_next_combo_index (EContactEditorDynTable *dyntable) +{ + size_t array_size = dyntable->priv->combo_defaults_n; + gint idx = 0; + + if (dyntable->priv->combo_defaults != NULL) { + idx = dyntable->priv->combo_defaults[dyntable->priv->curr_entries % array_size]; + } + + return idx; +} + +static GtkWidget* +combo_box_create (EContactEditorDynTable *dyntable) +{ + GtkWidget *w; + GtkComboBox *combo; + GtkListStore *store; + GtkCellRenderer *cell; + + w = gtk_combo_box_new (); + combo = GTK_COMBO_BOX (w); + store = dyntable->priv->combo_store; + + gtk_combo_box_set_model (combo, GTK_TREE_MODEL(store)); + + gtk_cell_layout_clear (GTK_CELL_LAYOUT (combo)); + + cell = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), cell, TRUE); + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), cell, + "text", DYNTABLE_COMBO_COLUMN_TEXT, + "sensitive", DYNTABLE_COMBO_COLUMN_SENSITIVE, + NULL); + + gtk_combo_box_set_active (combo, get_next_combo_index (dyntable)); + + return w; +} + +static void +emit_changed (EContactEditorDynTable *dyntable) +{ + g_signal_emit (dyntable, dyntable_signals[CHANGED_SIGNAL], 0); +} + +static void +emit_activated (EContactEditorDynTable *dyntable) +{ + g_signal_emit (dyntable, dyntable_signals[ACTIVATE_SIGNAL], 0); +} + +static void +emit_row_added (EContactEditorDynTable *dyntable) +{ + g_signal_emit (dyntable, dyntable_signals[ROW_ADDED_SIGNAL], 0); +} + +static void +add_empty_entry (EContactEditorDynTable *dyntable) +{ + GtkGrid *grid; + guint row, col; + GtkWidget *box, *entry; + EContactEditorDynTableClass *class; + + grid = GTK_GRID (dyntable); + position_to_grid (dyntable, dyntable->priv->curr_entries, &col, &row); + + /* create new entry at last position */ + class = E_CONTACT_EDITOR_DYNTABLE_GET_CLASS (dyntable); + box = combo_box_create (dyntable); + gtk_grid_attach (grid, box, col, row, 1, 1); + gtk_widget_show (box); + + entry = class->widget_create (dyntable); + g_object_set (entry, "margin-left", 2, NULL); + g_object_set (entry, "margin-right", 5, NULL); + gtk_widget_set_hexpand (entry, GTK_EXPAND); + gtk_grid_attach (grid, entry, col + 1, row, 1, 1); + gtk_widget_show (entry); + + g_signal_connect_swapped(box, "changed", + G_CALLBACK (gtk_widget_grab_focus), entry); + g_signal_connect_swapped(box, "changed", + G_CALLBACK (emit_changed), dyntable); + g_signal_connect_swapped(entry, "changed", + G_CALLBACK (emit_changed), dyntable); + g_signal_connect_swapped(entry, "changed", + G_CALLBACK (sensitize_button), dyntable); + g_signal_connect_swapped(entry, "activate", + G_CALLBACK (emit_activated), dyntable); + + increment_counter (dyntable); + + if ( (dyntable->priv->justified && col < dyntable->priv->columns-1) || + (dyntable->priv->curr_entries < dyntable->priv->show_min_entries) ) + add_empty_entry (dyntable); + + gtk_widget_grab_focus (entry); +} + +static void +remove_empty_entries (EContactEditorDynTable *dyntable, gboolean fillup) +{ + guint row, col, pos; + GtkGrid* grid; + GtkWidget* w; + EContactEditorDynTableClass *class; + + grid = GTK_GRID (dyntable); + class = E_CONTACT_EDITOR_DYNTABLE_GET_CLASS (dyntable); + + for (pos = 0; pos < dyntable->priv->curr_entries; pos++) { + position_to_grid (dyntable, pos, &col, &row); + w = gtk_grid_get_child_at (grid, col + 1, row); + + if (w != NULL && class->widget_is_empty (dyntable, w)) { + guint pos2, next_col, next_row; + + gtk_widget_destroy (w); + w = gtk_grid_get_child_at (grid, col, row); + gtk_widget_destroy (w); + + /* now fill gap */ + for (pos2 = pos + 1; pos2 < dyntable->priv->curr_entries; pos2++) { + position_to_grid (dyntable, pos2, &next_col, &next_row); + w = gtk_grid_get_child_at (grid, next_col, next_row); + move_widget (grid, w, col, row); + w = gtk_grid_get_child_at (grid, next_col + 1, next_row); + move_widget (grid, w, col + 1, row); + col = next_col; + row = next_row; + } + decrement_counter (dyntable); + pos--; /* check the new widget on our current position */ + } + + } + + if (fillup && dyntable->priv->curr_entries < dyntable->priv->show_min_entries) + add_empty_entry (dyntable); + +} + +/* clears data, not the combo box list store */ +void +e_contact_editor_dyntable_clear_data (EContactEditorDynTable *dyntable) +{ + guint i, col, row; + GtkGrid *grid; + GtkWidget *w; + EContactEditorDynTableClass *class; + + grid = GTK_GRID(dyntable); + class = E_CONTACT_EDITOR_DYNTABLE_GET_CLASS(dyntable); + + for (i = 0; i < dyntable->priv->curr_entries; i++) { + position_to_grid (dyntable, i, &col, &row); + w = gtk_grid_get_child_at (grid, col + 1, row); + class->widget_clear (dyntable, w); + } + remove_empty_entries (dyntable, TRUE); + + gtk_list_store_clear (dyntable->priv->data_store); +} + +static void +adjust_visibility_of_widgets (EContactEditorDynTable *dyntable) +{ + guint pos, col, row; + GtkGrid *grid; + GtkWidget *w; + + grid = GTK_GRID (dyntable); + for (pos = 0; pos < dyntable->priv->curr_entries; pos++) { + gboolean visible = FALSE; + + if (pos < dyntable->priv->show_max_entries) + visible = TRUE; + + position_to_grid (dyntable, pos, &col, &row); + w = gtk_grid_get_child_at (grid, col, row); + gtk_widget_set_visible (w, visible); + w = gtk_grid_get_child_at (grid, col + 1, row); + gtk_widget_set_visible (w, visible); + } + + show_button (dyntable); +} + +/* number of columns can only be set before any data is added to this dyntable */ +void e_contact_editor_dyntable_set_num_columns (EContactEditorDynTable *dyntable, + guint number_of_columns, + gboolean justified) +{ + GtkTreeIter iter; + GtkTreeModel *store; + gboolean holds_data; + + g_return_if_fail (number_of_columns > 0); + + store = GTK_TREE_MODEL (dyntable->priv->data_store); + holds_data = gtk_tree_model_get_iter_first (store, &iter); + g_return_if_fail (!holds_data); + + remove_empty_entries(dyntable, FALSE); + + dyntable->priv->columns = number_of_columns; + dyntable->priv->justified = justified; + + remove_empty_entries(dyntable, TRUE); +} + +void +e_contact_editor_dyntable_set_max_entries (EContactEditorDynTable *dyntable, + guint max) +{ + GtkTreeIter iter; + GtkTreeModel *store; + gboolean holds_data; + + g_return_if_fail (max > 0); + + store = GTK_TREE_MODEL (dyntable->priv->data_store); + holds_data = gtk_tree_model_get_iter_first (store, &iter); + g_return_if_fail (!holds_data); + + dyntable->priv->max_entries = max; + if (dyntable->priv->show_max_entries>max) + dyntable->priv->show_max_entries = max; + if (dyntable->priv->show_min_entries>max) + dyntable->priv->show_min_entries = max; +} + +/* show at least number_of_entries, with or without data */ +void +e_contact_editor_dyntable_set_show_min (EContactEditorDynTable *dyntable, + guint number_of_entries) +{ + if (number_of_entries > dyntable->priv->show_max_entries) + dyntable->priv->show_min_entries = dyntable->priv->show_max_entries; + else + dyntable->priv->show_min_entries = number_of_entries; + + if (dyntable->priv->curr_entries < dyntable->priv->show_min_entries) + add_empty_entry (dyntable); + + adjust_visibility_of_widgets (dyntable); +} + +/* show no more than number_of_entries, hide the rest */ +void +e_contact_editor_dyntable_set_show_max (EContactEditorDynTable *dyntable, + guint number_of_entries) +{ + if (number_of_entries > dyntable->priv->max_entries) { + dyntable->priv->show_max_entries = dyntable->priv->max_entries; + } else if (number_of_entries < dyntable->priv->show_min_entries) { + dyntable->priv->show_max_entries = dyntable->priv->show_min_entries; + } else { + dyntable->priv->show_max_entries = number_of_entries; + } + + adjust_visibility_of_widgets (dyntable); +} + +/* use data_store to fill data into widgets */ +void +e_contact_editor_dyntable_fill_in_data (EContactEditorDynTable *dyntable) +{ + guint pos = 0, col, row; + EContactEditorDynTableClass *class; + GtkGrid *grid; + GtkTreeIter iter; + GtkTreeModel *store; + GtkWidget *w; + gboolean valid; + gchar *str_data; + gint int_data; + + class = E_CONTACT_EDITOR_DYNTABLE_GET_CLASS (dyntable); + grid = GTK_GRID (dyntable); + store = GTK_TREE_MODEL (dyntable->priv->data_store); + + valid = gtk_tree_model_get_iter_first (store, &iter); + while (valid) { + gtk_tree_model_get (store, &iter, + DYNTABLE_STORE_COLUMN_ENTRY_STRING, &str_data, + DYNTABLE_STORE_COLUMN_SELECTED_ITEM, &int_data, + -1); + + if (pos >= dyntable->priv->curr_entries) + add_empty_entry (dyntable); + + position_to_grid (dyntable, pos++, &col, &row); + w = gtk_grid_get_child_at (grid, col, row); + set_combo_box_active (dyntable, GTK_COMBO_BOX(w), int_data); + w = gtk_grid_get_child_at (grid, col + 1, row); + class->widget_fill (dyntable, w, str_data); + + g_return_if_fail(pos < dyntable->priv->max_entries); + valid = gtk_tree_model_iter_next (store, &iter); + } + + /* fix visibility of added items */ + adjust_visibility_of_widgets (dyntable); +} + +/* the model returned has 3 columns + * + * UINT: sort order + * UINT: active combo item + * STRING: data extracted with widget_extract() + */ +GtkListStore* +e_contact_editor_dyntable_extract_data (EContactEditorDynTable *dyntable) +{ + EContactEditorDynTableClass *class; + GtkGrid *grid; + GtkListStore *data_store; + GtkWidget *w; + guint pos, col, row; + + grid = GTK_GRID(dyntable); + class = E_CONTACT_EDITOR_DYNTABLE_GET_CLASS(dyntable); + data_store = dyntable->priv->data_store; + + gtk_list_store_clear (data_store); + + for (pos = 0; pos < dyntable->priv->curr_entries; pos++) { + + position_to_grid (dyntable, pos, &col, &row); + w = gtk_grid_get_child_at (grid, col + 1, row); + + if (!class->widget_is_empty (dyntable, w)) { + GtkTreeIter iter; + gchar *dup; + gint combo_item; + const gchar *data; + + data = class->widget_extract (dyntable, w); + w = gtk_grid_get_child_at (grid, col, row); + combo_item = gtk_combo_box_get_active (GTK_COMBO_BOX(w)); + + dup = g_strdup (data); + g_strstrip(dup); + + gtk_list_store_append (data_store, &iter); + gtk_list_store_set (data_store, &iter, + DYNTABLE_STORE_COLUMN_SORTORDER, pos, + DYNTABLE_STORE_COLUMN_SELECTED_ITEM, combo_item, + DYNTABLE_STORE_COLUMN_ENTRY_STRING, dup, + -1); + + g_free (dup); + } + } + + return dyntable->priv->data_store; +} + +/* the model returned has two columns + * + * STRING: bound to attribute "text" + * BOOLEAN: bound to attribute "sensitive" + */ +GtkListStore* +e_contact_editor_dyntable_get_combo_store (EContactEditorDynTable *dyntable) +{ + return dyntable->priv->combo_store; +} + +void +e_contact_editor_dyntable_set_combo_defaults (EContactEditorDynTable *dyntable, + const gint *defaults, + size_t defaults_n) +{ + dyntable->priv->combo_defaults = defaults; + dyntable->priv->combo_defaults_n = defaults_n; +} + +static void +dispose_impl (GObject *object) +{ + GtkListStore *store; + EContactEditorDynTable *dyntable; + + dyntable = E_CONTACT_EDITOR_DYNTABLE(object); + + store = dyntable->priv->data_store; + if (store) { + gtk_list_store_clear (store); + g_object_unref (store); + dyntable->priv->data_store = NULL; + } + + store = dyntable->priv->combo_store; + if (store) { + g_object_unref (store); + dyntable->priv->combo_store = NULL; + } + + G_OBJECT_CLASS(e_contact_editor_dyntable_parent_class)->dispose (object); +} + +static GtkWidget* +default_impl_widget_create (EContactEditorDynTable *dyntable) +{ + return gtk_entry_new (); +} + +static void +default_impl_widget_clear (EContactEditorDynTable *dyntable, + GtkWidget *w) +{ + GtkEntry *e; + e = GTK_ENTRY(w); + gtk_entry_set_text (e, ""); +} + +static const gchar* +default_impl_widget_extract (EContactEditorDynTable *dyntable, + GtkWidget *w) +{ + GtkEntry *e; + + e = GTK_ENTRY(w); + return gtk_entry_get_text (e); +} + +static void +default_impl_widget_fill (EContactEditorDynTable *dyntable, + GtkWidget *w, + const gchar *value) +{ + GtkEntry *e; + + e = GTK_ENTRY(w); + gtk_entry_set_text (e, value); +} + +static gboolean +default_impl_widget_is_empty (EContactEditorDynTable *dyntable, + GtkWidget *w) +{ + GtkEntry *e; + const gchar *data; + gchar * dup; + size_t len = -1; + + e = GTK_ENTRY(w); + if (0 == gtk_entry_get_text_length (e)) + return TRUE; + + data = gtk_entry_get_text (e); + + dup = g_strdup (data); + g_strchug (dup); + len = strlen (dup); + g_free (dup); + + return len <= 0; +} + +static void +e_contact_editor_dyntable_init (EContactEditorDynTable *dyntable) +{ + GtkGrid *grid; + + dyntable->priv = G_TYPE_INSTANCE_GET_PRIVATE(dyntable, + E_TYPE_CONTACT_EDITOR_DYNTABLE, + EContactEditorDynTablePrivate); + + /* fill in defaults */ + dyntable->priv->max_entries = MAX_CAPACITY; + dyntable->priv->curr_entries = 0; + dyntable->priv->show_min_entries = 0; + dyntable->priv->show_max_entries = dyntable->priv->max_entries; + dyntable->priv->columns = 2; + dyntable->priv->justified = FALSE; + dyntable->priv->combo_defaults = NULL; + + dyntable->priv->combo_store = gtk_list_store_new (DYNTABLE_COBMO_COLUMN_NUM_COLUMNS, + G_TYPE_STRING, G_TYPE_BOOLEAN); + dyntable->priv->data_store = gtk_list_store_new (DYNTABLE_STORE_COLUMN_NUM_COLUMNS, + G_TYPE_UINT, G_TYPE_INT, G_TYPE_STRING); + gtk_tree_sortable_set_sort_column_id ( + GTK_TREE_SORTABLE (dyntable->priv->data_store), + DYNTABLE_STORE_COLUMN_SORTORDER, + GTK_SORT_ASCENDING); + + dyntable->priv->add_button = gtk_button_new_with_label ("+"); + g_signal_connect_swapped (dyntable->priv->add_button, "clicked", + G_CALLBACK (add_empty_entry), dyntable); + g_signal_connect_swapped(dyntable->priv->add_button, "clicked", + G_CALLBACK (emit_row_added), dyntable); + + grid = GTK_GRID (dyntable); + + gtk_grid_attach (grid, dyntable->priv->add_button, 0, 0, 1, 1); + gtk_widget_set_valign (dyntable->priv->add_button, GTK_ALIGN_CENTER); + gtk_widget_set_halign (dyntable->priv->add_button, GTK_ALIGN_START); + gtk_widget_show (dyntable->priv->add_button); + + if (dyntable->priv->curr_entries < dyntable->priv->show_min_entries) + add_empty_entry (dyntable); +} + +static void +e_contact_editor_dyntable_class_init (EContactEditorDynTableClass *class) +{ + GObjectClass *object_class; + + g_type_class_add_private (class, sizeof(EContactEditorDynTablePrivate)); + + dyntable_signals[CHANGED_SIGNAL] = g_signal_new ("changed", + G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET(EContactEditorDynTableClass, changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + dyntable_signals[ACTIVATE_SIGNAL] = g_signal_new ("activate", + G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET(EContactEditorDynTableClass, activate), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + dyntable_signals[ROW_ADDED_SIGNAL] = g_signal_new ("row-added", + G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET(EContactEditorDynTableClass, row_added), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + object_class = G_OBJECT_CLASS (class); + object_class->dispose = dispose_impl; + + /* virtual functions */ + class->widget_create = default_impl_widget_create; + class->widget_is_empty = default_impl_widget_is_empty; + class->widget_clear = default_impl_widget_clear; + class->widget_extract = default_impl_widget_extract; + class->widget_fill = default_impl_widget_fill; +} |