diff options
Diffstat (limited to 'filtered-combo.c')
| -rw-r--r-- | filtered-combo.c | 263 |
1 files changed, 263 insertions, 0 deletions
diff --git a/filtered-combo.c b/filtered-combo.c new file mode 100644 index 0000000..a15d939 --- /dev/null +++ b/filtered-combo.c @@ -0,0 +1,263 @@ +#include "common.h" + +G_DEFINE_TYPE(FilteredCombo, filtered_combo, GTK_TYPE_HBOX) + + +enum { + FILTERED_COMBO_CHANGED, + LAST_SIGNAL +}; + + +static unsigned int signals[LAST_SIGNAL]; + + +static int +filtered_combo_matches(const char *value, const char *query) +{ + return !!libsimple_strcasestr(value, query); +} + + +static void +filtered_combo_set_selected(FilteredCombo *this, const char *value) +{ + free(this->selected); + this->selected = estrdup(value); +} + + +static void +filtered_combo_notify_changed(FilteredCombo *this) +{ + if (this->selected) + g_signal_emit(this, signals[FILTERED_COMBO_CHANGED], 0, this->selected); +} + + +static gboolean +filtered_combo_completion_match(GtkEntryCompletion *completion, const char *query, GtkTreeIter *iter, void *user_data) +{ + GtkTreeModel *model = gtk_entry_completion_get_model(completion); + char *value; + int matches; + (void) user_data; + gtk_tree_model_get(model, iter, 0, &value, -1); + if (!value) + return FALSE; + matches = filtered_combo_matches(value, query); + g_free(value); + return matches; +} + + +static int +filtered_combo_select_first(FilteredCombo *this, const char *query) +{ + GtkTreeIter iter; + char *value; + if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(this->store), &iter)) + return 0; + do { + gtk_tree_model_get(GTK_TREE_MODEL(this->store), &iter, 0, &value, -1); + if (value && filtered_combo_matches(value, query)) { + filtered_combo_set_selected(this, value); + g_free(value); + return 1; + } + g_free(value); + } while (gtk_tree_model_iter_next(GTK_TREE_MODEL(this->store), &iter)); + free(this->selected); + this->selected = NULL; + return 0; +} + + +static void +filtered_combo_changed(GtkComboBox *combo, FilteredCombo *this) +{ + GtkTreeIter iter; + char *value; + if (!gtk_combo_box_get_active_iter(combo, &iter)) + return; + gtk_tree_model_get(GTK_TREE_MODEL(this->store), &iter, 0, &value, -1); + if (!value) + return; + filtered_combo_set_selected(this, value); + filtered_combo_notify_changed(this); + g_free(value); +} + + +static void +filtered_combo_entry_changed(GtkEditable *editable, FilteredCombo *this) +{ + const char *query = gtk_entry_get_text(GTK_ENTRY(editable)); + int has_match = filtered_combo_select_first(this, query); + gtk_entry_completion_set_popup_completion(this->completion, has_match); + gtk_entry_completion_complete(this->completion); +} + + +static gboolean +filtered_combo_entry_key_press(GtkWidget *widget, GdkEventKey *event, FilteredCombo *this) +{ + (void) widget; + if (event->keyval == GDK_Up || event->keyval == GDK_Down) { + gtk_entry_completion_complete(this->completion); + return FALSE; + } + return FALSE; +} + + +static gboolean +filtered_combo_completion_cursor_on_match(GtkEntryCompletion *completion, GtkTreeModel *model, + GtkTreeIter *iter, FilteredCombo *this) +{ + char *value; + (void) completion; + gtk_tree_model_get(model, iter, 0, &value, -1); + if (value) { + filtered_combo_set_selected(this, value); + g_free(value); + } + return FALSE; +} + + +static gboolean +filtered_combo_completion_match_selected(GtkEntryCompletion *completion, GtkTreeModel *model, + GtkTreeIter *iter, FilteredCombo *this) +{ + char *value; + (void) completion; + gtk_tree_model_get(model, iter, 0, &value, -1); + if (value) { + filtered_combo_set_selected(this, value); + filtered_combo_notify_changed(this); + g_free(value); + } + return FALSE; +} + + +static void +filtered_combo_entry_accept(GtkEntry *entry, FilteredCombo *this) +{ + (void) entry; + filtered_combo_notify_changed(this); +} + + +static gboolean +filtered_combo_entry_focus_out(GtkWidget *widget, GdkEventFocus *event, FilteredCombo *this) +{ + (void) event; + filtered_combo_entry_accept(GTK_ENTRY(widget), this); + return FALSE; +} + + +static gboolean +filtered_combo_entry_focus_in(GtkWidget *widget, GdkEventFocus *event, FilteredCombo *this) +{ + (void) event; + (void) this; + gtk_editable_set_position(GTK_EDITABLE(widget), -1); + return FALSE; +} + + +static void +filtered_combo_destroy(GtkObject *this_) +{ + FilteredCombo *this = FILTERED_COMBO(this_); + if (this->reposition_source) + g_source_remove(this->reposition_source); + this->reposition_source = 0; + free(this->selected); + this->selected = NULL; + GTK_OBJECT_CLASS(filtered_combo_parent_class)->destroy(this_); +} + + +static void +filtered_combo_class_init(FilteredComboClass *class) +{ + GtkObjectClass *object_class = GTK_OBJECT_CLASS(class); + object_class->destroy = filtered_combo_destroy; + signals[FILTERED_COMBO_CHANGED] = g_signal_new("changed", G_TYPE_FROM_CLASS(class), G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, 1, G_TYPE_STRING); +} + + +static void +filtered_combo_init(FilteredCombo *this) +{ + GtkCellRenderer *width_renderer; + + this->store = gtk_list_store_new(1, G_TYPE_STRING); + this->combo = gtk_combo_box_entry_new_with_model(GTK_TREE_MODEL(this->store), 0); + this->entry = gtk_bin_get_child(GTK_BIN(this->combo)); + this->completion = gtk_entry_completion_new(); + + gtk_entry_completion_set_model(this->completion, GTK_TREE_MODEL(this->store)); + gtk_entry_completion_set_match_func(this->completion, &filtered_combo_completion_match, this, NULL); + gtk_entry_completion_set_minimum_key_length(this->completion, 0); + gtk_entry_completion_set_text_column(this->completion, 0); + width_renderer = gtk_cell_renderer_text_new(); + gtk_cell_renderer_set_padding(width_renderer, 0, 0); + gtk_cell_renderer_set_fixed_size(width_renderer, 48, -1); + gtk_cell_layout_pack_end(GTK_CELL_LAYOUT(this->completion), width_renderer, FALSE); + gtk_entry_completion_set_inline_completion(this->completion, FALSE); + gtk_entry_completion_set_inline_selection(this->completion, FALSE); + gtk_entry_completion_set_popup_completion(this->completion, TRUE); + gtk_entry_completion_set_popup_set_width(this->completion, FALSE); + gtk_entry_completion_set_popup_single_match(this->completion, TRUE); + + gtk_entry_set_completion(GTK_ENTRY(this->entry), this->completion); + gtk_combo_box_set_focus_on_click(GTK_COMBO_BOX(this->combo), TRUE); + gtk_box_pack_start(GTK_BOX(this), this->combo, FALSE, FALSE, 0); + + g_signal_connect(this->combo, "changed", G_CALLBACK(&filtered_combo_changed), this); + g_signal_connect(this->entry, "changed", G_CALLBACK(&filtered_combo_entry_changed), this); + g_signal_connect(this->entry, "key-press-event", G_CALLBACK(&filtered_combo_entry_key_press), this); + g_signal_connect(this->entry, "activate", G_CALLBACK(&filtered_combo_entry_accept), this); + g_signal_connect(this->entry, "focus-out-event", G_CALLBACK(&filtered_combo_entry_focus_out), this); + g_signal_connect_after(this->entry, "focus-in-event", G_CALLBACK(&filtered_combo_entry_focus_in), this); + g_signal_connect(this->completion, "cursor-on-match", G_CALLBACK(&filtered_combo_completion_cursor_on_match), this); + g_signal_connect(this->completion, "match-selected", G_CALLBACK(&filtered_combo_completion_match_selected), this); +} + + +GtkWidget * +filtered_combo_new(void) +{ + return g_object_new(FILTERED_TYPE_COMBO, NULL); +} + + +void +filtered_combo_append(FilteredCombo *this, const char *value) +{ + GtkTreeIter iter; + gtk_list_store_append(this->store, &iter); + gtk_list_store_set(this->store, &iter, 0, value, -1); +} + + +void +filtered_combo_set_active(FilteredCombo *this, int index) +{ + gtk_combo_box_set_active(GTK_COMBO_BOX(this->combo), index); + gtk_editable_set_position(GTK_EDITABLE(this->entry), -1); +} + + +const char * +filtered_combo_get_selected(FilteredCombo *this) +{ + return this->selected; +} |
