From: Richard Kettlewell Date: Thu, 12 Jun 2008 16:24:47 +0000 (+0100) Subject: Start reimplenting search in Disobedience choose tabs. Results are X-Git-Tag: 4.1~15^2~33 X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/commitdiff_plain/cfa78eaa803d4481efa546b515e408e7aaab3dd9 Start reimplenting search in Disobedience choose tabs. Results are highlighted correctly but only if they are already visible. Focus is still a bit wonky and ^A doesn't reliably go to the right place. --- diff --git a/disobedience/Makefile.am b/disobedience/Makefile.am index 90a0bd6..08e465c 100644 --- a/disobedience/Makefile.am +++ b/disobedience/Makefile.am @@ -27,9 +27,9 @@ PNGS:=$(shell export LC_COLLATE=C;echo ${top_srcdir}/images/*.png) disobedience_SOURCES=disobedience.h disobedience.c client.c queue.c \ recent.c added.c queue-generic.c queue-generic.h queue-menu.c \ - choose.c choose-menu.c popup.c misc.c control.c properties.c \ - menu.c log.c progress.c login.c rtp.c help.c ../lib/memgc.c \ - settings.c users.c lookup.c + choose.c choose-menu.c choose-search.c popup.c misc.c \ + control.c properties.c menu.c log.c progress.c login.c rtp.c \ + help.c ../lib/memgc.c settings.c users.c lookup.c disobedience_LDADD=../lib/libdisorder.a $(LIBPCRE) $(LIBGC) $(LIBGCRYPT) \ $(LIBASOUND) $(COREAUDIO) $(LIBDB) disobedience_LDFLAGS=$(GTK_LIBS) diff --git a/disobedience/choose-search.c b/disobedience/choose-search.c new file mode 100644 index 0000000..ed76532 --- /dev/null +++ b/disobedience/choose-search.c @@ -0,0 +1,163 @@ +/* + * This file is part of DisOrder + * Copyright (C) 2008 Richard Kettlewell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 + */ +#include "disobedience.h" +#include "choose.h" + +static GtkWidget *choose_search_entry; +static GtkWidget *choose_next; +static GtkWidget *choose_prev; +static GtkWidget *choose_clear; + +/** @brief True if a search command is in flight */ +static int choose_searching; + +/** @brief True if in-flight search is now known to be obsolete */ +static int choose_search_obsolete; + +/** @brief Hash of all search result */ +static hash *choose_search_hash; + +static void choose_search_entry_changed(GtkEditable *editable, + gpointer user_data); + +int choose_is_search_result(const char *track) { + return choose_search_hash && hash_find(choose_search_hash, track); +} + +/** @brief Called when the cancel search button is clicked */ +static void choose_clear_clicked(GtkButton attribute((unused)) *button, + gpointer attribute((unused)) userdata) { + gtk_entry_set_text(GTK_ENTRY(choose_search_entry), ""); + /* The changed signal will do the rest of the work for us */ +} + +/** @brief Called with search results */ +static void choose_search_completed(void attribute((unused)) *v, + const char *error, + int nvec, char **vec) { + if(error) { + popup_protocol_error(0, error); + return; + } + choose_searching = 0; + /* If the search was obsoleted initiate another one */ + if(choose_search_obsolete) { + choose_search_entry_changed(0, 0); + return; + } + //fprintf(stderr, "%d search results\n", nvec); + choose_search_hash = hash_new(1); + for(int n = 0; n < nvec; ++n) + hash_add(choose_search_hash, vec[n], "", HASH_INSERT); + /* TODO arrange visibility of all search results */ + event_raise("search-results-changed", 0); +} + +/** @brief Called when the search entry changes */ +static void choose_search_entry_changed + (GtkEditable attribute((unused)) *editable, + gpointer attribute((unused)) user_data) { + /* If a search is in flight don't initiate a new one until it comes back */ + if(choose_searching) { + choose_search_obsolete = 1; + return; + } + char *terms = xstrdup(gtk_entry_get_text(GTK_ENTRY(choose_search_entry))); + /* Strip leading and trailing space */ + while(*terms == ' ') ++terms; + char *e = terms + strlen(terms); + while(e > terms && e[-1] == ' ') --e; + *e = 0; + if(!*terms) { + /* Nothing to search for. Fake a completion call. */ + choose_search_completed(0, 0, 0, 0); + return; + } + if(disorder_eclient_search(client, choose_search_completed, terms, 0)) { + /* Bad search terms. Fake a completion call. */ + choose_search_completed(0, 0, 0, 0); + return; + } + choose_searching = 1; +} + +static void choose_next_clicked(GtkButton attribute((unused)) *button, + gpointer attribute((unused)) userdata) { + fprintf(stderr, "next\n"); /* TODO */ +} + +static void choose_prev_clicked(GtkButton attribute((unused)) *button, + gpointer attribute((unused)) userdata) { + fprintf(stderr, "prev\n"); /* TODO */ +} + +/** @brief Create the search widget */ +GtkWidget *choose_search_widget(void) { + + /* Text entry box for search terms */ + choose_search_entry = gtk_entry_new(); + gtk_widget_set_style(choose_search_entry, tool_style); + g_signal_connect(choose_search_entry, "changed", + G_CALLBACK(choose_search_entry_changed), 0); + gtk_tooltips_set_tip(tips, choose_search_entry, + "Enter search terms here; search is automatic", ""); + + /* Cancel button to clear the search */ + choose_clear = gtk_button_new_from_stock(GTK_STOCK_CANCEL); + gtk_widget_set_style(choose_clear, tool_style); + g_signal_connect(G_OBJECT(choose_clear), "clicked", + G_CALLBACK(choose_clear_clicked), 0); + gtk_tooltips_set_tip(tips, choose_clear, "Clear search terms", ""); + + /* Up and down buttons to find previous/next results; initially they are not + * usable as there are no search results. */ + choose_prev = iconbutton("up.png", "Previous search result"); + g_signal_connect(G_OBJECT(choose_prev), "clicked", + G_CALLBACK(choose_prev_clicked), 0); + gtk_widget_set_style(choose_prev, tool_style); + gtk_widget_set_sensitive(choose_prev, 0); + choose_next = iconbutton("down.png", "Next search result"); + g_signal_connect(G_OBJECT(choose_next), "clicked", + G_CALLBACK(choose_next_clicked), 0); + gtk_widget_set_style(choose_next, tool_style); + gtk_widget_set_sensitive(choose_next, 0); + + /* Pack the search tools button together on a line */ + GtkWidget *hbox = gtk_hbox_new(FALSE/*homogeneous*/, 1/*spacing*/); + gtk_box_pack_start(GTK_BOX(hbox), choose_search_entry, + TRUE/*expand*/, TRUE/*fill*/, 0/*padding*/); + gtk_box_pack_start(GTK_BOX(hbox), choose_prev, + FALSE/*expand*/, FALSE/*fill*/, 0/*padding*/); + gtk_box_pack_start(GTK_BOX(hbox), choose_next, + FALSE/*expand*/, FALSE/*fill*/, 0/*padding*/); + gtk_box_pack_start(GTK_BOX(hbox), choose_clear, + FALSE/*expand*/, FALSE/*fill*/, 0/*padding*/); + + return hbox; +} + +/* +Local Variables: +c-basic-offset:2 +comment-column:40 +fill-column:79 +indent-tabs-mode:nil +End: +*/ diff --git a/disobedience/choose.c b/disobedience/choose.c index 31535b3..ce3f754 100644 --- a/disobedience/choose.c +++ b/disobedience/choose.c @@ -123,6 +123,16 @@ static gboolean choose_set_state_callback(GtkTreeModel attribute((unused)) *mode LENGTH_COLUMN, length, STATE_COLUMN, queued(track), -1); + if(choose_is_search_result(track)) + gtk_tree_store_set(choose_store, it, + BG_COLUMN, "yellow", + FG_COLUMN, "black", + -1); + else + gtk_tree_store_set(choose_store, it, + BG_COLUMN, (char *)0, + FG_COLUMN, (char *)0, + -1); } return FALSE; /* continue walking */ } @@ -335,6 +345,8 @@ GtkWidget *choose_widget(void) { G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, G_TYPE_STRING); /* Create the view */ @@ -349,6 +361,8 @@ GtkWidget *choose_widget(void) { ("Track", r, "text", NAME_COLUMN, + "background", BG_COLUMN, + "foreground", FG_COLUMN, (char *)0); gtk_tree_view_column_set_resizable(c, TRUE); gtk_tree_view_column_set_reorderable(c, TRUE); @@ -397,15 +411,24 @@ GtkWidget *choose_widget(void) { event_register("queue-list-changed", choose_set_state, 0); event_register("playing-track-changed", choose_set_state, 0); + event_register("search-results-changed", choose_set_state, 0); /* Fill the root */ disorder_eclient_files(client, choose_files_completed, "", NULL, NULL); disorder_eclient_dirs(client, choose_dirs_completed, "", NULL, NULL); - + /* Make the widget scrollable */ GtkWidget *scrolled = scroll_widget(choose_view); - g_object_set_data(G_OBJECT(scrolled), "type", (void *)&choose_tabtype); - return scrolled; + + /* Pack vertically with the search widget */ + GtkWidget *vbox = gtk_vbox_new(FALSE/*homogenous*/, 1/*spacing*/); + gtk_box_pack_start(GTK_BOX(vbox), scrolled, + TRUE/*expand*/, TRUE/*fill*/, 0/*padding*/); + gtk_box_pack_end(GTK_BOX(vbox), choose_search_widget(), + FALSE/*expand*/, FALSE/*fill*/, 0/*padding*/); + + g_object_set_data(G_OBJECT(vbox), "type", (void *)&choose_tabtype); + return vbox; } /* diff --git a/disobedience/choose.h b/disobedience/choose.h index 16414a8..dfd2a73 100644 --- a/disobedience/choose.h +++ b/disobedience/choose.h @@ -30,6 +30,8 @@ enum { ISFILE_COLUMN, /* TRUE for a track, FALSE for a directory */ TRACK_COLUMN, /* Full track name, "" for placeholder */ SORT_COLUMN, /* Sort key */ + BG_COLUMN, /* Background color */ + FG_COLUMN, /* Foreground color */ CHOOSE_COLUMNS /* column count */ }; @@ -51,6 +53,8 @@ char *choose_get_sort(GtkTreeIter *iter); int choose_is_file(GtkTreeIter *iter); int choose_is_dir(GtkTreeIter *iter); int choose_is_placeholder(GtkTreeIter *iter); +GtkWidget *choose_search_widget(void); +int choose_is_search_result(const char *track); #endif /* CHOOSE_H */