/* * 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 . * */ #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; if (dyntable->priv->curr_entries >= dyntable->priv->max_entries) return; 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 || (dyntable->priv->justified && col < dyntable->priv->columns-1))) 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) { GtkTreeModel *store; gint n_children; g_return_if_fail (max > 0); store = GTK_TREE_MODEL (dyntable->priv->data_store); n_children = gtk_tree_model_iter_n_children (store, NULL); if (n_children > max) { g_warning ("Dyntable holds %i items, setting max to %i, instead of %i", n_children, n_children, max); max = n_children; } 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; remove_empty_entries (dyntable, TRUE); show_button (dyntable); } /* 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); valid = gtk_tree_model_iter_next (store, &iter); if (valid && pos >= dyntable->priv->max_entries) { g_warning ("dyntable is configured with max_entries = %i, ignoring the rest.", dyntable->priv->max_entries); break; } } /* 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; }