view src/streambrowser/gui/streambrowser_win.c @ 2850:587b3657990d

xiph now updates the streams as well; i am aware of gui crashing when adding xiph streams, but tomorrow is another lovely day
author Calin Crisan ccrisan@gmail.com
date Tue, 29 Jul 2008 00:20:31 +0300
parents fbb32674bfd2
children
line wrap: on
line source


#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <glib.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>

#include "../streambrowser.h"
#include "streambrowser_win.h"


typedef struct {

	streamdir_t*	streamdir;
	GtkWidget*		table;
	GtkWidget*		tree_view;

} streamdir_gui_t;


void					(* update_function) (streamdir_t *streamdir, category_t *category, streaminfo_t *streaminfo, gboolean add_to_playlist);

static GtkWidget*		gtk_label_new_with_icon(gchar *icon_filename, gchar *label_text);
static GtkWidget*		gtk_streamdir_tree_view_new();
static GtkWidget*		gtk_streamdir_table_new(GtkWidget *tree_view);

static gboolean			on_notebook_switch_page(GtkNotebook *notebook, GtkNotebookPage *page, guint page_num, gpointer data);
static gboolean			on_tree_view_cursor_changed(GtkTreeView *tree_view, gpointer data);
static gboolean			on_add_button_clicked(GtkButton *button, gpointer data);
static gboolean			on_search_entry_key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data);
static gboolean			on_tree_view_key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data);
static gboolean			on_tree_view_button_pressed(GtkWidget *widget, GdkEventButton *event, gpointer data);

static streamdir_gui_t*	find_streamdir_gui_by_name(gchar *name);
//static streamdir_gui_t*	find_streamdir_gui_by_tree_view(GtkTreeView *tree_view); todo: remove this
static streamdir_gui_t*	find_streamdir_gui_by_table(GtkTable *table);
static streamdir_gui_t*	find_streamdir_gui_by_streamdir(streamdir_t *streamdir);
static gboolean			tree_view_search_equal_func(GtkTreeModel *model, gint column, const gchar *key, GtkTreeIter *iter, gpointer data);

static GtkWidget*		notebook;
static GtkWidget*		search_entry;
static GtkWidget*		add_button;
static GtkWidget*		streambrowser_window;
static GList*			streamdir_gui_list;
static GtkCellRenderer*	cell_renderer_pixbuf;
static GtkCellRenderer*	cell_renderer_text;

static gboolean			tree_view_button_pressed = FALSE;



void streambrowser_win_init()
{
	/* notebook */
	notebook = gtk_notebook_new();
	g_signal_connect(G_OBJECT(notebook), "switch-page", G_CALLBACK(on_notebook_switch_page), NULL);
	gtk_widget_show(notebook);

	GtkWidget *search_label = gtk_label_new(_("Search:"));
	gtk_widget_show(search_label);

	/* search entry */
	search_entry = gtk_entry_new();
	g_signal_connect(G_OBJECT(search_entry), "key-press-event", G_CALLBACK(on_search_entry_key_pressed), NULL);
	gtk_widget_show(search_entry);
	
	GtkWidget *hbox1 = gtk_hbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(hbox1), search_label, FALSE, TRUE, 3);
	gtk_box_pack_start(GTK_BOX(hbox1), search_entry, TRUE, TRUE, 3);
	gtk_widget_show(hbox1);

	/* add button */
	add_button = gtk_button_new_from_stock(GTK_STOCK_ADD);
	g_signal_connect(G_OBJECT(add_button), "clicked", G_CALLBACK(on_add_button_clicked), NULL);
	gtk_widget_show(add_button);

	GtkWidget *vbox1 = gtk_vbox_new(FALSE, 3);
	gtk_box_pack_start(GTK_BOX(vbox1), notebook, TRUE, TRUE, 0);
	gtk_box_pack_start(GTK_BOX(vbox1), hbox1, FALSE, TRUE, 0);
	gtk_box_pack_start(GTK_BOX(vbox1), add_button, FALSE, TRUE, 0);
	gtk_widget_show(vbox1);

	/* streambrowser window */
	streambrowser_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
	gtk_window_set_title(GTK_WINDOW(streambrowser_window), _("Stream browser"));
	gtk_window_set_position(GTK_WINDOW(streambrowser_window), GTK_WIN_POS_CENTER);
	gtk_window_set_default_size(GTK_WINDOW(streambrowser_window), 700, 400);
	gtk_window_set_icon_from_file(GTK_WINDOW(streambrowser_window), STREAMBROWSER_ICON, NULL);
	g_signal_connect(G_OBJECT(streambrowser_window), "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), streambrowser_window);
	gtk_container_add(GTK_CONTAINER(streambrowser_window), vbox1);

	/* others */
	cell_renderer_pixbuf = gtk_cell_renderer_pixbuf_new();
	cell_renderer_text = gtk_cell_renderer_text_new();
}

void streambrowser_win_done()
{
}

void streambrowser_win_show()
{
	gtk_widget_show(streambrowser_window);
}

void streambrowser_win_hide()
{
	gtk_widget_hide(streambrowser_window);
}

void streambrowser_win_set_streamdir(streamdir_t *streamdir, gchar *icon_filename)
{
	GtkWidget *tree_view = NULL;
	
	/* search for an old instance of this streamdir and replace it */
	streamdir_gui_t *streamdir_gui = find_streamdir_gui_by_name(streamdir->name);
	if (streamdir_gui != NULL) {
		streamdir_delete(streamdir_gui->streamdir);
		streamdir_gui->streamdir = streamdir;
		tree_view = streamdir_gui->tree_view;
	}
	/* if no older instance of this streamdir has been found, we add a brand new one */
	else {
		streamdir_gui = g_malloc(sizeof(streamdir_gui_t));

		tree_view = gtk_streamdir_tree_view_new();

		GtkWidget *table = gtk_streamdir_table_new(tree_view);
		gtk_widget_show_all(table);

		GtkWidget *label = gtk_label_new_with_icon(icon_filename, streamdir->name);
		gtk_widget_show_all(label);

		streamdir_gui->streamdir = streamdir;
		streamdir_gui->tree_view = tree_view;
		streamdir_gui->table = table;

		streamdir_gui_list = g_list_append(streamdir_gui_list, streamdir_gui);
		
		gtk_notebook_append_page(GTK_NOTEBOOK(notebook), table, label);
	}

	/* fill the tree with categories */
	GtkTreeIter iter;
	GtkTreeStore *store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(tree_view)));
	
	gtk_tree_store_clear(store);

	int i, count = category_get_count(streamdir);
	category_t *category;
	for (i = 0; i < count; i++) {
		category = category_get_by_index(streamdir, i);

		gtk_tree_store_append(store, &iter, NULL);
		gtk_tree_store_set(store, &iter, 0, "gtk-directory", 1, category->name, 2, "", -1);
	}
}

void streambrowser_win_set_category(streamdir_t *streamdir, category_t *category)
{
	streamdir_gui_t *streamdir_gui = find_streamdir_gui_by_name(streamdir->name);
	if (streamdir_gui == NULL) {
		failure("gui: streambrowser_win_set_category() called with non-existent streamdir\n");
		return;
	}
	
	GtkTreeView *tree_view = GTK_TREE_VIEW(streamdir_gui->tree_view);
	GtkTreeStore *store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(tree_view)));
	GtkTreePath *path;
	GtkTreeIter iter, new_iter;
	
	/* clear all the previously added streaminfo in this category */
	path = gtk_tree_path_new_from_indices(category_get_index(streamdir, category), 0, -1);
	if (gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path)) {
		while (gtk_tree_store_remove(store, &iter))
			;
	}
	
	/* find the corresponding category tree iter */
	path = gtk_tree_path_new_from_indices(category_get_index(streamdir, category), -1);
	if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path))
		return;
	
	/* append the new streaminfos to the current category / iter */
	int i, count = streaminfo_get_count(category);
	streaminfo_t *streaminfo;
	for (i = 0; i < count; i++) {
		streaminfo = streaminfo_get_by_index(category, i);

		gtk_tree_store_append(store, &new_iter, &iter);
		gtk_tree_store_set(store, &new_iter, 0, "gtk-media-play", 1, streaminfo->name, 2, streaminfo->current_track, -1);
	}
	
	gtk_tree_path_free(path);
}

void streambrowser_win_set_streaminfo(streamdir_t *streamdir, category_t *category, streaminfo_t *streaminfo)
{
	streamdir_gui_t *streamdir_gui = find_streamdir_gui_by_name(streamdir->name);
	if (streamdir_gui == NULL) {
		failure("gui: streambrowser_win_set_category() called with non-existent streamdir\n");
		return;
	}
	
	GtkTreeView *tree_view = GTK_TREE_VIEW(streamdir_gui->tree_view);
	GtkTreeStore *store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(tree_view)));
	GtkTreePath *path;
	GtkTreeIter iter, new_iter;
	
	/* find the corresponding streaminfo tree iter */
	path = gtk_tree_path_new_from_indices(category_get_index(streamdir, category), streaminfo_get_index(category, streaminfo), -1);
	if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path))
		return;
	
	/* update the streaminfo*/
	gtk_tree_store_set(store, &new_iter, 0, "gtk-media-play", 1, streaminfo->name, 2, streaminfo->current_track, -1);
	
	gtk_tree_path_free(path);
}

void streambrowser_win_set_update_function(void (*function) (streamdir_t *streamdir, category_t *category, streaminfo_t *streaminfo, gboolean add_to_playlist))
{
	update_function = function;
}

void streambrowser_win_set_category_state(streamdir_t *streamdir, category_t *category, gboolean fetching)
{
	streamdir_gui_t *streamdir_gui = find_streamdir_gui_by_streamdir(streamdir);
	GtkTreeView *tree_view = GTK_TREE_VIEW(streamdir_gui->tree_view);
	GtkTreeStore *store = GTK_TREE_STORE(gtk_tree_view_get_model(tree_view));
	GtkTreePath *path;
	GtkTreeIter iter;
	
	/* find the corresponding category tree iter */
	path = gtk_tree_path_new_from_indices(category_get_index(streamdir, category), -1);
	if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path))
		return;
	
	if (fetching) {
		gchar temp[DEF_STRING_LEN];
		sprintf(temp, "<span style='italic' weight='heavy'>%s</span>", category->name);
		gtk_tree_store_set(store, &iter, 0, "gtk-refresh", 1, temp, 2, "", -1);
	}
	else {
		gtk_tree_store_set(store, &iter, 0, "gtk-directory", 1, category->name, 2, "", -1);
	}
}

void streambrowser_win_set_streaminfo_state(streamdir_t *streamdir, category_t *category, streaminfo_t *streaminfo, gboolean fetching)
{
	streamdir_gui_t *streamdir_gui = find_streamdir_gui_by_streamdir(streamdir);
	GtkTreeView *tree_view = GTK_TREE_VIEW(streamdir_gui->tree_view);
	GtkTreeStore *store = GTK_TREE_STORE(gtk_tree_view_get_model(tree_view));
	GtkTreePath *path;
	GtkTreeIter iter;
	
	/* find the corresponding category tree iter */
	path = gtk_tree_path_new_from_indices(category_get_index(streamdir, category), streaminfo_get_index(category, streaminfo), -1);
	if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, path))
		return;
	
	if (fetching) {
		gchar temp[DEF_STRING_LEN];
		sprintf(temp, "<span style='italic' weight='heavy'>%s</span>", streaminfo->name);
		
		gtk_tree_store_set(store, &iter, 0, "gtk-refresh", 1, temp, 2, streaminfo->current_track, -1);
	}
	else {
		gtk_tree_store_set(store, &iter, 0, "gtk-media-play", 1, streaminfo->name, 2, streaminfo->current_track, -1);
	}
}

static GtkWidget* gtk_label_new_with_icon(gchar *icon_filename, gchar *label_text)
{
	GtkWidget *hbox = gtk_hbox_new(FALSE, 1);
	GtkWidget *label = gtk_label_new(label_text);
	GtkWidget *icon = gtk_image_new_from_file(icon_filename);

	gtk_box_pack_start(GTK_BOX(hbox), icon, FALSE, TRUE, 0);
	gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
	
	return hbox;
}

static GtkWidget *gtk_streamdir_tree_view_new()
{
	GtkWidget *tree_view = gtk_tree_view_new();

	GtkTreeStore *store = gtk_tree_store_new(3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
	gtk_tree_view_set_model(GTK_TREE_VIEW(tree_view), GTK_TREE_MODEL(store));

	gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree_view), TRUE);
	gtk_tree_view_set_search_entry(GTK_TREE_VIEW(tree_view), GTK_ENTRY(search_entry));
	gtk_tree_view_set_search_equal_func(GTK_TREE_VIEW(tree_view), tree_view_search_equal_func, NULL, NULL);
	gtk_tree_view_set_search_column(GTK_TREE_VIEW(tree_view), 1);
	g_signal_connect(G_OBJECT(tree_view), "key-press-event", G_CALLBACK(on_tree_view_key_pressed), NULL);
	g_signal_connect(G_OBJECT(tree_view), "cursor-changed", G_CALLBACK(on_tree_view_cursor_changed), NULL);
	g_signal_connect(G_OBJECT(tree_view), "button-press-event", G_CALLBACK(on_tree_view_button_pressed), NULL);

	GtkTreeViewColumn *column = gtk_tree_view_column_new();
	gtk_tree_view_column_pack_start(column, cell_renderer_pixbuf, TRUE);
	gtk_tree_view_column_add_attribute(column, cell_renderer_pixbuf, "stock-id", 0);
	gtk_tree_view_column_set_resizable(column, TRUE);
	gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), column);
	
	column = gtk_tree_view_column_new();
	gtk_tree_view_column_pack_start(column, cell_renderer_text, TRUE);
	gtk_tree_view_column_add_attribute(column, cell_renderer_text, "markup", 1);
	gtk_tree_view_column_set_resizable(column, TRUE);
	gtk_tree_view_column_set_title(column, _("Stream name"));
	gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), column);

	column = gtk_tree_view_column_new();
	gtk_tree_view_column_pack_start(column, cell_renderer_text, TRUE);
	gtk_tree_view_column_add_attribute(column, cell_renderer_text, "text", 2);
	gtk_tree_view_column_set_resizable(column, TRUE);
	gtk_tree_view_column_set_title(column, _("Now playing"));
	gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), column);

	return tree_view;
}

static GtkWidget* gtk_streamdir_table_new(GtkWidget *tree_view)
{
	GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL);
	gtk_container_add(GTK_CONTAINER(scrolled_window), tree_view);
	
	GtkWidget *table = gtk_table_new(1, 1, FALSE);
	gtk_table_attach(GTK_TABLE(table), scrolled_window, 0, 1, 0, 1, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);

	return table;
}

static gboolean on_notebook_switch_page(GtkNotebook *notebook, GtkNotebookPage *page, guint page_num, gpointer data)
{
	if (page_num < 0)
		return FALSE;
		
	/* only enable searching in the current tree (tab) */

	streamdir_gui_t *streamdir_gui;
	int i;

	for (i = 0; i < g_list_length(streamdir_gui_list); i++) {
		streamdir_gui = g_list_nth_data(streamdir_gui_list, i);

		if (i == page_num) 
			gtk_tree_view_set_search_column(GTK_TREE_VIEW(streamdir_gui->tree_view), 1);
		else
			gtk_tree_view_set_search_column(GTK_TREE_VIEW(streamdir_gui->tree_view), -1);
	}

	/* clear the search box */
	gtk_entry_set_text(GTK_ENTRY(search_entry), "");

	return TRUE;
}


static gboolean on_tree_view_cursor_changed(GtkTreeView *tree_view, gpointer data)
{
	/* only do the refresh if this cursor change occured due to a mouse click */
	if (!tree_view_button_pressed)
		return FALSE;

	tree_view_button_pressed = FALSE;

	GtkTreePath *path;
	GtkTreeViewColumn *focus_column;

	/* get the currently selected tree view */
	GtkWidget *table = gtk_notebook_get_nth_page(GTK_NOTEBOOK(notebook), gtk_notebook_get_current_page(GTK_NOTEBOOK(notebook)));
	streamdir_gui_t *streamdir_gui = find_streamdir_gui_by_table(GTK_TABLE(table));
	if (streamdir_gui == NULL)
		return FALSE;

	/* get the currently selected path in the tree */
	gtk_tree_view_get_cursor(tree_view, &path, &focus_column);

	if (path == NULL || gtk_tree_path_get_depth(path) == 0)
		return FALSE;

	gint *indices = gtk_tree_path_get_indices(path);
	int category_index = indices[0];
	streamdir_t *streamdir = streamdir_gui->streamdir;
	category_t *category = category_get_by_index(streamdir_gui->streamdir, category_index);

	/* if the selected item is a category */
	if (gtk_tree_path_get_depth(path) == 1) {
		if (!gtk_tree_view_row_expanded(tree_view, path)) {
			gtk_entry_set_text(GTK_ENTRY(search_entry), "");
			update_function(streamdir, category, NULL, FALSE);
		}

		gtk_tree_path_free(path);
	}
	/* if the selected item is a streaminfo */
	else {
		int streaminfo_index = indices[1];

		gtk_tree_path_free(path);

		/* get the currently selected stream (info) */
		streaminfo_t *streaminfo = streaminfo_get_by_index(category, streaminfo_index);
		
		gtk_entry_set_text(GTK_ENTRY(search_entry), "");
		update_function(streamdir, category, streaminfo, FALSE);
	}
	
	return FALSE;
}

static gboolean on_tree_view_button_pressed(GtkWidget *widget, GdkEventButton *event, gpointer data)
{
	/* double click adds the currently selected stream to the playlist */
	if (event->type == GDK_2BUTTON_PRESS) {
		tree_view_button_pressed = FALSE;
		on_add_button_clicked(NULL, NULL);
	}
	/* single click triggers a refresh of the selected item */
	else {
		// todo: separate single from double click somehow
		tree_view_button_pressed = TRUE;
	}	
	
	return FALSE;
}

static gboolean on_add_button_clicked(GtkButton *button, gpointer data)
{
	GtkTreePath *path;
	GtkTreeViewColumn *focus_column;

	/* get the currently selected tree view */
	GtkWidget *table = gtk_notebook_get_nth_page(GTK_NOTEBOOK(notebook), gtk_notebook_get_current_page(GTK_NOTEBOOK(notebook)));
	streamdir_gui_t *streamdir_gui = find_streamdir_gui_by_table(GTK_TABLE(table));
	if (streamdir_gui == NULL)
		return TRUE;

	GtkTreeView *tree_view = GTK_TREE_VIEW(streamdir_gui->tree_view);
	
	/* get the currently selected path in the tree */
	gtk_tree_view_get_cursor(tree_view, &path, &focus_column);
	
	if (path == NULL)
		return TRUE;

	gint *indices = gtk_tree_path_get_indices(path);
	if (gtk_tree_path_get_depth(path) == 1) {
		if (gtk_tree_view_row_expanded(tree_view, path))
			gtk_tree_view_collapse_row(tree_view, path);
		else
			gtk_tree_view_expand_row(tree_view, path, FALSE);
	
		gtk_tree_path_free(path);
		return TRUE;
	}
	
	int category_index = indices[0];
	int streaminfo_index = indices[1];
	
	gtk_tree_path_free(path);
	
	/* get the currently selected stream (info) */
	streamdir_t *streamdir = streamdir_gui->streamdir;
	category_t *category = category_get_by_index(streamdir_gui->streamdir, category_index);
	streaminfo_t *streaminfo = streaminfo_get_by_index(category, streaminfo_index);
	
	gtk_entry_set_text(GTK_ENTRY(search_entry), "");
	update_function(streamdir, category, streaminfo, TRUE);

	return TRUE;
}

static gboolean on_search_entry_key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data)
{
	if (event->keyval == GDK_Return || event->keyval == GDK_KP_Enter) {
		on_add_button_clicked(GTK_BUTTON(add_button), NULL);
		return TRUE;
	}

	if (event->keyval == GDK_Escape) {
		gtk_entry_set_text(GTK_ENTRY(search_entry), "");
		return FALSE;
	}
	
	return FALSE;
}

static gboolean on_tree_view_key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data)
{
	if (event->keyval == GDK_Down || event->keyval == GDK_Up)
		return FALSE;
	else
		if (event->keyval == GDK_Return || event->keyval == GDK_KP_Enter) {
			on_add_button_clicked(GTK_BUTTON(add_button), NULL);
			return FALSE;
		}

	gtk_widget_grab_focus(search_entry);
	on_search_entry_key_pressed(widget, event, data);

	return TRUE;
}

static streamdir_gui_t *find_streamdir_gui_by_name(gchar *name)
{
	GList *iterator;
	streamdir_gui_t *streamdir_gui;

	for (iterator = g_list_first(streamdir_gui_list); iterator != NULL; iterator = g_list_next(iterator)) {
		streamdir_gui = iterator->data;

		if (strcmp(streamdir_gui->streamdir->name, name) == 0)
			return streamdir_gui;
	}
	
	return NULL;
}

/* todo: remove this
static streamdir_gui_t *find_streamdir_gui_by_tree_view(GtkTreeView *tree_view)
{
	GList *iterator;
	streamdir_gui_t *streamdir_gui;

	for (iterator = g_list_first(streamdir_gui_list); iterator != NULL; iterator = g_list_next(iterator)) {
		streamdir_gui = iterator->data;

		if ((void *) streamdir_gui->tree_view == (void *) tree_view)
			return streamdir_gui;
	}
	
	return NULL;
}
*/

static streamdir_gui_t *find_streamdir_gui_by_table(GtkTable *table)
{
	GList *iterator;
	streamdir_gui_t *streamdir_gui;

	for (iterator = g_list_first(streamdir_gui_list); iterator != NULL; iterator = g_list_next(iterator)) {
		streamdir_gui = iterator->data;

		if ((void *) streamdir_gui->table == (void *) table)
			return streamdir_gui;
	}
	
	return NULL;
}

static streamdir_gui_t* find_streamdir_gui_by_streamdir(streamdir_t *streamdir)
{
	GList *iterator;
	streamdir_gui_t *streamdir_gui;

	for (iterator = g_list_first(streamdir_gui_list); iterator != NULL; iterator = g_list_next(iterator)) {
		streamdir_gui = iterator->data;

		if ((void *) streamdir_gui->streamdir == (void *) streamdir)
			return streamdir_gui;
	}
	
	return NULL;
}

static gboolean tree_view_search_equal_func(GtkTreeModel *model, gint column, const gchar *key, GtkTreeIter *iter, gpointer data)
{
	if (column == -1)
		return TRUE;

	GValue value = {0, };
	gboolean ret;
	
	gtk_tree_model_get_value(model, iter, column, &value);
	const gchar *string = g_value_get_string(&value);
	
	if (string == NULL || string[0] == '\0' || key == NULL || key[0] == '\0')
		ret = TRUE;

	ret = !mystrcasestr(string, key);
	
	g_value_unset(&value);

	return ret;
}