changeset 2918:d354afe9df0d

Automated merge with ssh://chrome@hg.atheme.org//hg/audacious-plugins
author Andrew O. Shadoura <bugzilla@tut.by>
date Mon, 18 Aug 2008 14:17:58 +0300
parents 082f79c0563d (current diff) 223822ba79f2 (diff)
children 9ec994a3bb59
files src/streambrowser/gui/about_win.c src/streambrowser/gui/about_win.h
diffstat 13 files changed, 365 insertions(+), 231 deletions(-) [+]
line wrap: on
line diff
--- a/src/bluetooth/agent.c	Sun Aug 17 23:57:16 2008 +0300
+++ b/src/bluetooth/agent.c	Mon Aug 18 14:17:58 2008 +0300
@@ -711,6 +711,9 @@
 	dbus_g_proxy_call(object, "GetRemoteName", NULL,
 				G_TYPE_STRING, address, G_TYPE_INVALID,
 				G_TYPE_STRING, &name, G_TYPE_INVALID);
+     mcs_handle_t *cfgfile = aud_cfg_db_open();
+    aud_cfg_db_set_string(cfgfile,"BLUETOOTH_PLUGIN","bonded", address);
+    aud_cfg_db_close(cfgfile);
 
 	if (name) {
 		if (g_strrstr(name, address))
@@ -735,8 +738,7 @@
 {
 	const char *adapter = NULL, *name = NULL;
 	gchar *device, *text;
-
-	dbus_g_proxy_call(object, "GetName", NULL, G_TYPE_INVALID,
+ 	dbus_g_proxy_call(object, "GetName", NULL, G_TYPE_INVALID,
 				G_TYPE_STRING, &adapter, G_TYPE_INVALID);
 
 	dbus_g_proxy_call(object, "GetRemoteName", NULL,
@@ -752,6 +754,9 @@
 		device = g_strdup(address);
 
 	text = g_strdup_printf(_("Removed bonding with %s"), device);
+    mcs_handle_t *cfgfile = aud_cfg_db_open();
+    aud_cfg_db_set_string(cfgfile,"BLUETOOTH_PLUGIN","bonded","no");
+    aud_cfg_db_close(cfgfile);
 
 	g_free(device);
     printf("bonding removed\n");
--- a/src/bluetooth/bluetooth.c	Sun Aug 17 23:57:16 2008 +0300
+++ b/src/bluetooth/bluetooth.c	Mon Aug 18 14:17:58 2008 +0300
@@ -44,7 +44,7 @@
 void discover_devices(void);
 void disconnect_dbus_signals(void);
 /*static void show_restart_dialog(void); */
-static void remove_bonding();
+static void remove_bonding(gchar* device);
 GeneralPlugin bluetooth_gp =
 {
     .description = "Bluetooth audio support",
@@ -61,7 +61,19 @@
     audio_devices = NULL;
     bus = NULL;
     obj = NULL;
+    gchar* bonded_addr="zz";
     discover_devices();
+    mcs_handle_t *cfgfile ;
+    cfgfile = aud_cfg_db_open();
+    if(!aud_cfg_db_get_string(cfgfile, "BLUETOOTH_PLUGIN", "bonded",
+				  &bonded_addr))
+        return;
+    if(bonded_addr!=NULL && g_strcmp0(bonded_addr,"no")!=0)
+        {
+             remove_bonding(bonded_addr);
+        }
+    aud_cfg_db_close(cfgfile);
+
 }
 
 void bluetooth_cleanup ( void )
@@ -72,7 +84,7 @@
         close_window();
         config =0;
     }
-    remove_bonding();
+    remove_bonding(bonded_dev);
     if(discover_finish == 2) {
         dbus_g_connection_flush (bus);
         dbus_g_connection_unref(bus);
@@ -127,10 +139,11 @@
     audio_devices = NULL;
     //g_list_free(current_device);
 }
-static void remove_bonding()
+static void remove_bonding(gchar *device)
 {
+    printf("remove_bonding call\n");
     dbus_g_object_register_marshaller(marshal_VOID__STRING_UINT_INT, G_TYPE_NONE, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_INT, G_TYPE_INVALID);
-    dbus_g_proxy_call(obj,"RemoveBonding",NULL,G_TYPE_STRING,bonded_dev,G_TYPE_INVALID,G_TYPE_INVALID); 
+    dbus_g_proxy_call(obj,"RemoveBonding",NULL,G_TYPE_STRING,device,G_TYPE_INVALID,G_TYPE_INVALID); 
 
 }
 void refresh_call(void)
@@ -151,22 +164,21 @@
 
 gpointer connect_call_th(void)
 {
-
     //I will have to enable the audio service if necessary 
-
-    dbus_g_object_register_marshaller(marshal_VOID__STRING_UINT_INT, G_TYPE_NONE, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_INT, G_TYPE_INVALID);
     run_agents();
+    dbus_g_object_register_marshaller(marshal_VOID__STRING_UINT_INT, G_TYPE_NONE, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_INT, G_TYPE_INVALID);
     dbus_g_proxy_call(obj,"CreateBonding",NULL,G_TYPE_STRING,current_address,G_TYPE_INVALID,G_TYPE_INVALID); 
-    return NULL;
+
+return NULL;
 }
+
 void connect_call(void)
-{   close_call();
+{ 
+    close_call();
     close_window();
     show_scan(1);
-    remove_bonding();
     current_address = g_strdup(((DeviceData*)(selected_dev->data))->address);
     connect_th = g_thread_create((GThreadFunc)connect_call_th,NULL,TRUE,NULL) ; 
-
 }
 
 
@@ -226,7 +238,7 @@
     printf("play callback\n");
     close_window();
     aud_output_plugin_cleanup();
-    audacious_drct_stop();
+//    audacious_drct_stop();
     audacious_drct_play();
 
 
--- a/src/skins/plugin.c	Sun Aug 17 23:57:16 2008 +0300
+++ b/src/skins/plugin.c	Mon Aug 18 14:17:58 2008 +0300
@@ -88,6 +88,10 @@
     init_skins(config.skin);
     mainwin_setup_menus();
 
+    gint h_vol[2];
+    aud_input_get_volume(&h_vol[0], &h_vol[1]);
+    aud_hook_call("volume set", h_vol);
+
     skins_interface.ops->create_prefs_window();
     cfgdlg = skins_configure();
     aud_prefswin_page_new(cfgdlg, N_("Skinned Interface"), DATA_DIR "/images/appearance.png");
--- a/src/streambrowser/Makefile	Sun Aug 17 23:57:16 2008 +0300
+++ b/src/streambrowser/Makefile	Mon Aug 18 14:17:58 2008 +0300
@@ -5,7 +5,6 @@
        shoutcast.c \
        xiph.c \
        bookmarks.c \
-       gui/about_win.c \
        gui/streambrowser_win.c
 
 DATA = images/shoutcast.png \
--- a/src/streambrowser/bookmarks.c	Sun Aug 17 23:57:16 2008 +0300
+++ b/src/streambrowser/bookmarks.c	Mon Aug 18 14:17:58 2008 +0300
@@ -25,11 +25,14 @@
 #include "bookmarks.h"
 
 
-static bookmark_t *bookmarks;
-static int bookmarks_count;
+static bookmark_t **bookmarks;
+static int *bookmarks_count;
 
 gboolean bookmarks_streaminfo_fetch(category_t *category, streaminfo_t *streaminfo)
 {
+	// todo: implement and make use of this
+	
+	return FALSE;
 }
 
 gboolean bookmarks_category_fetch(streamdir_t *streamdir, category_t *category)
@@ -42,13 +45,11 @@
 
 	int i;
 	/* find bookmarks that match this category */
-	for (i = 0; i < bookmarks_count; i++)
-		if (strcmp(bookmarks[i].streamdir_name, streamdir->name) == 0 &&
-			strcmp(bookmarks[i].category_name, category->name) == 0) {
-
+	for (i = 0; i < *bookmarks_count; i++)
+		if (strcmp((*bookmarks)[i].streamdir_name, category->name) == 0) {
 			debug("bookmarks: adding stream info for '%s/%d'\n", streamdir->name, category->name);
 
-			streaminfo_t *streaminfo = streaminfo_new(bookmarks[i].name, bookmarks[i].playlist_url, bookmarks[i].url, "");
+			streaminfo_t *streaminfo = streaminfo_new((*bookmarks)[i].name, (*bookmarks)[i].playlist_url, (*bookmarks)[i].url, "");
 			streaminfo_add(category, streaminfo);
 			
 			debug("bookmarks: stream info added\n");
@@ -57,10 +58,10 @@
 	return TRUE;
 }
 
-streamdir_t* bookmarks_streamdir_fetch(bookmark_t *bms, int count)
+streamdir_t* bookmarks_streamdir_fetch(bookmark_t **p_bookmarks, int *p_bookmarks_count)
 {
-	bookmarks = bms;
-	bookmarks_count = count;
+	bookmarks = p_bookmarks;
+	bookmarks_count = p_bookmarks_count;
 	
 	streamdir_t *streamdir = streamdir_new(BOOKMARKS_NAME);
 	
@@ -77,3 +78,70 @@
 	return streamdir;
 }
 
+void bookmark_add(bookmark_t *bookmark)
+{
+	debug("bookmarks: adding bookmark with streamdir = '%s', name = '%s', playlist_url = '%s', url = '%s'\n", 
+		bookmark->streamdir_name, bookmark->name, bookmark->playlist_url, bookmark->url);
+		
+	int i;
+	for (i = 0; i < *bookmarks_count; i++)
+		if (strcmp((*bookmarks)[i].name, bookmark->name) == 0) {
+			debug("bookmarks: bookmark with name = '%s' already exists, skipping\n", bookmark->name);
+			return;
+		}
+
+	*bookmarks = realloc(*bookmarks, sizeof(bookmark_t) * ((*bookmarks_count) + 1));
+	
+	strncpy((*bookmarks)[*bookmarks_count].streamdir_name, bookmark->streamdir_name, DEF_STRING_LEN);
+	strncpy((*bookmarks)[*bookmarks_count].name, bookmark->name, DEF_STRING_LEN);
+	strncpy((*bookmarks)[*bookmarks_count].playlist_url, bookmark->playlist_url, DEF_STRING_LEN);
+	strncpy((*bookmarks)[*bookmarks_count].url, bookmark->url, DEF_STRING_LEN);
+	
+	(*bookmarks_count)++;
+	
+	debug("bookmarks: bookmark added, there are now %d bookmarks\n", *bookmarks_count);
+	
+	/* issue a configuration save for immediately saving the new added bookmark */
+	config_save();
+}
+
+void bookmark_remove(gchar *name)
+{
+	debug("bookmarks: searching for bookmark with name = '%s'\n", name);
+
+	int pos = -1, i;
+	
+	for (i = 0; i < *bookmarks_count; i++)
+		if (strcmp((*bookmarks)[i].name, name) == 0) {
+			pos = i;
+			break;
+		}
+		
+	if (pos != -1) {
+		debug("bookmarks: removing bookmark with streamdir = '%s', name = '%s', playlist_url = '%s', url = '%s'\n", 
+			(*bookmarks)[i].streamdir_name, (*bookmarks)[i].name, (*bookmarks)[i].playlist_url, (*bookmarks)[i].url);
+
+		for (i = pos; i < (*bookmarks_count) - 1; i++) {
+			/* bookmarks[i] = bookmarks[i + 1] */
+
+			strncpy((*bookmarks)[i].streamdir_name, (*bookmarks)[i + 1].streamdir_name, DEF_STRING_LEN);
+			strncpy((*bookmarks)[i].name, (*bookmarks)[i + 1].name, DEF_STRING_LEN);
+			strncpy((*bookmarks)[i].playlist_url, (*bookmarks)[i + 1].playlist_url, DEF_STRING_LEN);
+			strncpy((*bookmarks)[i].url, (*bookmarks)[i + 1].url, DEF_STRING_LEN);
+		}
+		
+		(*bookmarks_count)--;
+		if (*bookmarks_count > 0)
+			*bookmarks = realloc(*bookmarks, sizeof(bookmark_t) * (*bookmarks_count));
+		else
+			*bookmarks = NULL;
+			
+		debug("bookmarks: bookmark removed, there are now %d bookmarks\n", *bookmarks_count);
+	}
+	else
+		failure("bookmarks: cannot find a bookmark with name = '%s'\n", name);
+
+	/* issue a configuration save for immediately saving the remaining bookmarks */
+	config_save();
+}
+
--- a/src/streambrowser/bookmarks.h	Sun Aug 17 23:57:16 2008 +0300
+++ b/src/streambrowser/bookmarks.h	Mon Aug 18 14:17:58 2008 +0300
@@ -30,7 +30,6 @@
 typedef struct {
 	
 	gchar			streamdir_name[DEF_STRING_LEN];
-	gchar			category_name[DEF_STRING_LEN];
 	
 	gchar			name[DEF_STRING_LEN];
 	gchar			playlist_url[DEF_STRING_LEN];
@@ -41,7 +40,10 @@
 
 gboolean							bookmarks_streaminfo_fetch(category_t *category, streaminfo_t *streaminfo);
 gboolean							bookmarks_category_fetch(streamdir_t *streamdir, category_t *category);
-streamdir_t*						bookmarks_streamdir_fetch(bookmark_t *bms, int count);
+streamdir_t*						bookmarks_streamdir_fetch(bookmark_t **p_bookmarks, int *p_bookmarks_count);
+
+void								bookmark_add(bookmark_t *bookmark);
+void								bookmark_remove(gchar *name);
 
 
 #endif	// BOOKMARKS_H
--- a/src/streambrowser/gui/about_win.c	Sun Aug 17 23:57:16 2008 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,19 +0,0 @@
-/*
- * Audacious Streambrowser Plugin
- *
- * Copyright (c) 2008 Calin Crisan <ccrisan@gmail.com>
- *
- * 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; under version 3 of the License.
- *
- * 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, see <http://www.gnu.org/licenses>.
- */
-
-
--- a/src/streambrowser/gui/about_win.h	Sun Aug 17 23:57:16 2008 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,24 +0,0 @@
-/*
- * Audacious Streambrowser Plugin
- *
- * Copyright (c) 2008 Calin Crisan <ccrisan@gmail.com>
- *
- * 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; under version 3 of the License.
- *
- * 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, see <http://www.gnu.org/licenses>.
- */
-
-
-#ifndef ABOUT_WIN_H
-#define ABOUT_WIN_H
-
-#endif	// ABOUT_WIN_H
-
--- a/src/streambrowser/gui/streambrowser_win.c	Sun Aug 17 23:57:16 2008 +0300
+++ b/src/streambrowser/gui/streambrowser_win.c	Mon Aug 18 14:17:58 2008 +0300
@@ -8,6 +8,7 @@
 
 #include "../streambrowser.h"
 #include "streambrowser_win.h"
+#include "../bookmarks.h"
 
 
 typedef struct {
@@ -28,12 +29,12 @@
 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_bookmark_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);
@@ -41,6 +42,7 @@
 static GtkWidget*		notebook;
 static GtkWidget*		search_entry;
 static GtkWidget*		add_button;
+static GtkWidget*		bookmark_button;
 static GtkWidget*		streambrowser_window;
 static GList*			streamdir_gui_list;
 static GtkCellRenderer*	cell_renderer_pixbuf;
@@ -75,17 +77,24 @@
 	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);
+	/* bookmark button */
+	bookmark_button = gtk_button_new_with_label(_("Add Bookmark"));
+	gtk_button_set_image(GTK_BUTTON(bookmark_button), gtk_image_new_from_file(BOOKMARKS_ICON));
+	g_signal_connect(G_OBJECT(bookmark_button), "clicked", G_CALLBACK(on_bookmark_button_clicked), NULL);
+	gtk_widget_show(bookmark_button);
+
+	GtkWidget *vbox1 = gtk_vbox_new(FALSE, 4);
 	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_box_pack_start(GTK_BOX(vbox1), bookmark_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_default_size(GTK_WINDOW(streambrowser_window), 1000, 700);
 	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);
@@ -153,7 +162,7 @@
 		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);
+		gtk_tree_store_set(store, &iter, 0, "gtk-directory", 1, category->name, 2, "", 3, PANGO_WEIGHT_NORMAL, -1);
 	}
 }
 
@@ -189,7 +198,7 @@
 		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_store_set(store, &new_iter, 0, "gtk-media-play", 1, streaminfo->name, 2, streaminfo->current_track, 3, PANGO_WEIGHT_NORMAL, -1);
 	}
 	
 	gtk_tree_path_free(path);
@@ -214,7 +223,7 @@
 		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_store_set(store, &new_iter, 0, "gtk-media-play", 1, streaminfo->name, 2, streaminfo->current_track, 3, PANGO_WEIGHT_NORMAL -1);
 	
 	gtk_tree_path_free(path);
 }
@@ -238,12 +247,13 @@
 		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);
+		gtk_tree_store_set(store, &iter, 0, "gtk-refresh", 1, category->name, 2, "", 3, PANGO_WEIGHT_BOLD, -1);
 	}
 	else {
-		gtk_tree_store_set(store, &iter, 0, "gtk-directory", 1, category->name, 2, "", -1);
+		gtk_tree_store_set(store, &iter, 0, "gtk-directory", 1, category->name, 2, "", 3, PANGO_WEIGHT_NORMAL, -1);
+		
+		/* we also expand the corresponding category tree element in the treeview */
+		gtk_tree_view_expand_row(tree_view, path, FALSE);
 	}
 }
 
@@ -261,13 +271,10 @@
 		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);
+		gtk_tree_store_set(store, &iter, 0, "gtk-media-play", 1, streaminfo->name, 2, streaminfo->current_track, 3, PANGO_WEIGHT_BOLD, -1);
 	}
 	else {
-		gtk_tree_store_set(store, &iter, 0, "gtk-media-play", 1, streaminfo->name, 2, streaminfo->current_track, -1);
+		gtk_tree_store_set(store, &iter, 0, "gtk-media-play", 1, streaminfo->name, 2, streaminfo->current_track, 3, PANGO_WEIGHT_NORMAL, -1);
 	}
 }
 
@@ -287,7 +294,7 @@
 {
 	GtkWidget *tree_view = gtk_tree_view_new();
 
-	GtkTreeStore *store = gtk_tree_store_new(3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
+	GtkTreeStore *store = gtk_tree_store_new(4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT);
 	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);
@@ -306,7 +313,8 @@
 	
 	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_add_attribute(column, cell_renderer_text, "text", 1);
+	gtk_tree_view_column_add_attribute(column, cell_renderer_text, "weight", 3);
 	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);
@@ -353,6 +361,14 @@
 
 	/* clear the search box */
 	gtk_entry_set_text(GTK_ENTRY(search_entry), "");
+	
+	/* if this is the last page, aka the bookmarks page, bookmark button has to say "Remove bookmark", instead of "Add bookmark"0 */
+	if (page_num == gtk_notebook_get_n_pages(notebook) - 1) {
+		gtk_button_set_label(GTK_BUTTON(bookmark_button), _("Remove Bookmark"));
+	}
+	else {
+		gtk_button_set_label(GTK_BUTTON(bookmark_button), _("Add Bookmark"));
+	}
 
 	return TRUE;
 }
@@ -420,7 +436,6 @@
 	}
 	/* single click triggers a refresh of the selected item */
 	else {
-		// todo: separate single from double click somehow
 		tree_view_button_pressed = TRUE;
 	}	
 	
@@ -473,6 +488,66 @@
 	return TRUE;
 }
 
+static gboolean on_bookmark_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) {
+		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);
+	
+	/* if we have the bookmarks tab focused, we remove the stream, rather than add it. otherwise we simply add it */
+	if (strcmp(streamdir->name, BOOKMARKS_NAME) == 0) {
+		bookmark_remove(streaminfo->name);
+		
+		update_function(streamdir, category, NULL, FALSE);
+	}
+	else {
+		bookmark_t bookmark;
+		strncpy(bookmark.streamdir_name, streamdir->name, DEF_STRING_LEN);
+		strncpy(bookmark.name, streaminfo->name, DEF_STRING_LEN);
+		strncpy(bookmark.playlist_url, streaminfo->playlist_url, DEF_STRING_LEN);
+		strncpy(bookmark.url, streaminfo->url, DEF_STRING_LEN);
+	
+		bookmark_add(&bookmark);
+		
+		/* find the corresponding category (having the current streamdir name) in the bookmarks */
+		streamdir_t *bookmarks_streamdir = find_streamdir_gui_by_name(BOOKMARKS_NAME)->streamdir;
+		category_t *bookmarks_category = category_get_by_name(bookmarks_streamdir, streamdir->name);
+		
+		update_function(bookmarks_streamdir, bookmarks_category, NULL, FALSE);
+	}
+	
+	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) {
@@ -519,23 +594,6 @@
 	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;
--- a/src/streambrowser/shoutcast.c	Sun Aug 17 23:57:16 2008 +0300
+++ b/src/streambrowser/shoutcast.c	Mon Aug 18 14:17:58 2008 +0300
@@ -72,7 +72,7 @@
 			g_snprintf(streaminfo_playlist_url, DEF_STRING_LEN, SHOUTCAST_STREAMINFO_URL, streaminfo_id);
 			
 			if (strncmp(streaminfo_playlist_url, streaminfo->playlist_url, DEF_STRING_LEN) == 0) {
-				debug("shoutcast: updating stream info for '%s/%d' from '%s'\n", streaminfo_name, streaminfo_id, url);
+				debug("shoutcast: updating stream info for '%s' with id %s from '%s'\n", streaminfo_name, streaminfo_id, url);
 
 				strcpy(streaminfo->name, streaminfo_name);
 				strcpy(streaminfo->playlist_url, streaminfo_playlist_url);
--- a/src/streambrowser/streambrowser.c	Sun Aug 17 23:57:16 2008 +0300
+++ b/src/streambrowser/streambrowser.c	Mon Aug 18 14:17:58 2008 +0300
@@ -29,7 +29,6 @@
 #include "xiph.h"
 #include "bookmarks.h"
 #include "gui/streambrowser_win.h"
-#include "gui/about_win.h"
 
 
 typedef struct {
@@ -57,8 +56,6 @@
 
 static void gui_init();
 static void gui_done();
-static void config_load();
-static void config_save();
 
 static void streamdir_update(streamdir_t *streamdir, category_t *category, streaminfo_t *streaminfo, gboolean add_to_playlist);
 static gpointer update_thread_core(gpointer user_data);
@@ -152,6 +149,132 @@
     return TRUE;
 }
 
+void config_load()
+{
+    streambrowser_cfg.debug = FALSE;
+    streambrowser_cfg.bookmarks = NULL;
+    streambrowser_cfg.bookmarks_count = 0;
+
+    mcs_handle_t *db;
+    if ((db = aud_cfg_db_open()) == NULL) {
+        failure("failed to load configuration\n");
+        return;
+    }
+
+	aud_cfg_db_get_bool(db, "streambrowser", "debug", &streambrowser_cfg.debug);
+	aud_cfg_db_get_int(db, "streambrowser", "bookmarks_count", &streambrowser_cfg.bookmarks_count);
+	
+    debug("debug = %d\n", streambrowser_cfg.debug);
+
+	if (streambrowser_cfg.bookmarks_count == 0)
+		streambrowser_cfg.bookmarks = NULL;
+	else
+		streambrowser_cfg.bookmarks = g_malloc(sizeof(bookmark_t) * streambrowser_cfg.bookmarks_count);
+
+    int i;
+	gchar item[DEF_STRING_LEN];
+	gchar *value;
+	for (i = 0; i < streambrowser_cfg.bookmarks_count; i++) {
+		g_snprintf(item, DEF_STRING_LEN, "bookmark%d_streamdir_name", i);
+		if (aud_cfg_db_get_string(db, "streambrowser", item, &value)) {
+			strncpy(streambrowser_cfg.bookmarks[i].streamdir_name, value, DEF_STRING_LEN);
+			g_free(value);
+		}
+		else
+			streambrowser_cfg.bookmarks[i].streamdir_name[0] = '\0';
+
+		g_snprintf(item, DEF_STRING_LEN, "bookmark%d_name", i);
+		if (aud_cfg_db_get_string(db, "streambrowser", item, &value)) {
+			strncpy(streambrowser_cfg.bookmarks[i].name, value, DEF_STRING_LEN);
+			g_free(value);
+		}
+		else
+			streambrowser_cfg.bookmarks[i].name[0] = '\0';
+
+		g_snprintf(item, DEF_STRING_LEN, "bookmark%d_playlist_url", i);
+		if (aud_cfg_db_get_string(db, "streambrowser", item, &value)) {
+			strncpy(streambrowser_cfg.bookmarks[i].playlist_url, value, DEF_STRING_LEN);
+			g_free(value);
+		}
+		else
+			streambrowser_cfg.bookmarks[i].playlist_url[0] = '\0';
+
+		g_snprintf(item, DEF_STRING_LEN, "bookmark%d_url", i);
+		if (aud_cfg_db_get_string(db, "streambrowser", item, &value)) {
+			strncpy(streambrowser_cfg.bookmarks[i].url, value, DEF_STRING_LEN);
+			g_free(value);
+		}
+		else
+			streambrowser_cfg.bookmarks[i].url[0] = '\0';
+
+	    debug("loaded a bookmark with streamdir_name = '%s', name = '%s', playlist_url = '%s', url = '%s'\n",
+	    	streambrowser_cfg.bookmarks[i].streamdir_name, 
+	    	streambrowser_cfg.bookmarks[i].name, 
+	    	streambrowser_cfg.bookmarks[i].playlist_url, 
+	    	streambrowser_cfg.bookmarks[i].url);
+	}
+	
+	debug("loaded %d bookmarks\n", streambrowser_cfg.bookmarks_count);
+
+    aud_cfg_db_close(db);
+
+    debug("configuration loaded\n");
+}
+
+void config_save()
+{
+    mcs_handle_t *db;
+    if ((db = aud_cfg_db_open()) == NULL) {
+        failure("failed to save configuration\n");
+        return;
+    }
+
+    aud_cfg_db_set_bool(db, "streambrowser", "debug", streambrowser_cfg.debug);
+    
+    int old_bookmarks_count, i;
+    gchar item[DEF_STRING_LEN];
+    aud_cfg_db_get_int(db, "streambrowser", "bookmarks_count", &old_bookmarks_count);
+	aud_cfg_db_set_int(db, "streambrowser", "bookmarks_count", streambrowser_cfg.bookmarks_count);
+    
+    for (i = 0; i < streambrowser_cfg.bookmarks_count; i++) {
+		debug("saving bookmark with streamdir_name = '%s', name = '%s', playlist_url = '%s', url = '%s'\n",
+	    	streambrowser_cfg.bookmarks[i].streamdir_name, 
+	    	streambrowser_cfg.bookmarks[i].name, 
+	    	streambrowser_cfg.bookmarks[i].playlist_url, 
+	    	streambrowser_cfg.bookmarks[i].url);
+	    
+    	g_snprintf(item, DEF_STRING_LEN, "bookmark%d_streamdir_name", i);
+		aud_cfg_db_set_string(db, "streambrowser", item, streambrowser_cfg.bookmarks[i].streamdir_name);
+
+    	g_snprintf(item, DEF_STRING_LEN, "bookmark%d_name", i);
+		aud_cfg_db_set_string(db, "streambrowser", item, streambrowser_cfg.bookmarks[i].name);
+
+    	g_snprintf(item, DEF_STRING_LEN, "bookmark%d_playlist_url", i);
+		aud_cfg_db_set_string(db, "streambrowser", item, streambrowser_cfg.bookmarks[i].playlist_url);
+
+    	g_snprintf(item, DEF_STRING_LEN, "bookmark%d_url", i);
+		aud_cfg_db_set_string(db, "streambrowser", item, streambrowser_cfg.bookmarks[i].url);
+    }
+    
+    for (i = streambrowser_cfg.bookmarks_count; i < old_bookmarks_count; i++) {
+    	g_snprintf(item, DEF_STRING_LEN, "bookmark%d_streamdir_name", i);
+		aud_cfg_db_unset_key(db, "streambrowser", item);
+
+    	g_snprintf(item, DEF_STRING_LEN, "bookmark%d_name", i);
+		aud_cfg_db_unset_key(db, "streambrowser", item);
+
+    	g_snprintf(item, DEF_STRING_LEN, "bookmark%d_playlist_url", i);
+		aud_cfg_db_unset_key(db, "streambrowser", item);
+
+    	g_snprintf(item, DEF_STRING_LEN, "bookmark%d_url", i);
+		aud_cfg_db_unset_key(db, "streambrowser", item);
+	}
+
+    aud_cfg_db_close(db);
+
+    debug("configuration saved\n");
+}
+
 gboolean mystrcasestr(const char *haystack, const char *needle)
 {
 	int len_h = strlen(haystack) + 1;
@@ -189,6 +312,23 @@
 static void sb_about()
 {
     debug("sb_about()\n");
+
+	static GtkWidget* about_window = NULL;
+
+	if (about_window != NULL) {
+		gtk_window_present(GTK_WINDOW(about_window));
+	}
+	else {
+		about_window = audacious_info_dialog(_("About Stream Browser"),
+		_("Copyright (c) 2008, by Calin Crisan <ccrisan@gmail.com> and The Audacious Team.\n\n"
+		"This is a simple stream browser that includes the most popular streaming directories.\n"
+		"Many thanks to the Streamtuner developers <http://www.nongnu.org/streamtuner>,\n"
+		"\tand of course to the whole Audacious community.\n\n"
+		"Also thank you Tony Vroon for mentoring & guiding me, again.\n\n"
+		"This was a Google Summer of Code 2008 project."), _("OK"), FALSE, NULL, NULL);
+
+	    g_signal_connect(G_OBJECT(about_window), "destroy",	G_CALLBACK(gtk_widget_destroyed), &about_window);
+    }
 }
 
 static void sb_configure()
@@ -251,113 +391,6 @@
     debug("gui destroyed\n");
 }
 
-static void config_load()
-{
-    streambrowser_cfg.debug = FALSE;
-
-    mcs_handle_t *db;
-    if ((db = aud_cfg_db_open()) == NULL) {
-        failure("failed to load configuration\n");
-        return;
-    }
-
-	aud_cfg_db_get_bool(db, "streambrowser", "debug", &streambrowser_cfg.debug);
-	aud_cfg_db_get_int(db, "streambrowser", "bookmarks_count", &streambrowser_cfg.bookmarks_count);
-	
-	streambrowser_cfg.bookmarks = g_malloc(sizeof(bookmark_t) * streambrowser_cfg.bookmarks_count);
-
-    int i;
-	gchar item[DEF_STRING_LEN];
-	gchar *value;
-	for (i = 0; i < streambrowser_cfg.bookmarks_count; i++) {
-		g_snprintf(item, DEF_STRING_LEN, "bookmark%d_streamdir_name", i);
-		aud_cfg_db_get_string(db, "streambrowser", item, &value);
-		strncpy(streambrowser_cfg.bookmarks[i].streamdir_name, value, DEF_STRING_LEN);
-		g_free(value);
-
-		g_snprintf(item, DEF_STRING_LEN, "bookmark%d_category_name", i);
-		aud_cfg_db_get_string(db, "streambrowser", item, &value);
-		strncpy(streambrowser_cfg.bookmarks[i].category_name, value, DEF_STRING_LEN);
-		g_free(value);
-
-		g_snprintf(item, DEF_STRING_LEN, "bookmark%d_name", i);
-		aud_cfg_db_get_string(db, "streambrowser", item, &value);
-		strncpy(streambrowser_cfg.bookmarks[i].name, value, DEF_STRING_LEN);
-		g_free(value);
-
-		g_snprintf(item, DEF_STRING_LEN, "bookmark%d_playlist_url", i);
-		aud_cfg_db_get_string(db, "streambrowser", item, &value);
-		strncpy(streambrowser_cfg.bookmarks[i].playlist_url, value, DEF_STRING_LEN);
-		g_free(value);
-
-		g_snprintf(item, DEF_STRING_LEN, "bookmark%d_url", i);
-		aud_cfg_db_get_string(db, "streambrowser", item, &value);
-		strncpy(streambrowser_cfg.bookmarks[i].url, value, DEF_STRING_LEN);
-		g_free(value);
-	}
-
-    aud_cfg_db_close(db);
-
-    debug("configuration loaded\n");
-    debug("debug = %d\n", streambrowser_cfg.debug);
-    
-	// todo: write all other config options to the console
-}
-
-static void config_save()
-{
-    mcs_handle_t *db;
-    if ((db = aud_cfg_db_open()) == NULL) {
-        failure("failed to save configuration\n");
-        return;
-    }
-
-    aud_cfg_db_set_bool(db, "streambrowser", "debug", streambrowser_cfg.debug);
-    
-    int old_bookmarks_count, i;
-    gchar item[DEF_STRING_LEN];
-    aud_cfg_db_get_int(db, "streambrowser", "bookmarks_count", &old_bookmarks_count);
-	aud_cfg_db_set_int(db, "streambrowser", "bookmarks_count", streambrowser_cfg.bookmarks_count);
-    
-    for (i = 0; i < streambrowser_cfg.bookmarks_count; i++) {
-    	g_snprintf(item, DEF_STRING_LEN, "bookmark%d_streamdir_name", i);
-		aud_cfg_db_set_string(db, "streambrowser", item, streambrowser_cfg.bookmarks[i].streamdir_name);
-
-    	g_snprintf(item, DEF_STRING_LEN, "bookmark%d_category_name", i);
-		aud_cfg_db_set_string(db, "streambrowser", item, streambrowser_cfg.bookmarks[i].category_name);
-
-    	g_snprintf(item, DEF_STRING_LEN, "bookmark%d_name", i);
-		aud_cfg_db_set_string(db, "streambrowser", item, streambrowser_cfg.bookmarks[i].name);
-
-    	g_snprintf(item, DEF_STRING_LEN, "bookmark%d_playlist_url", i);
-		aud_cfg_db_set_string(db, "streambrowser", item, streambrowser_cfg.bookmarks[i].playlist_url);
-
-    	g_snprintf(item, DEF_STRING_LEN, "bookmark%d_url", i);
-		aud_cfg_db_set_string(db, "streambrowser", item, streambrowser_cfg.bookmarks[i].url);
-    }
-    
-    for (i = streambrowser_cfg.bookmarks_count; i < old_bookmarks_count; i++) {
-    	g_snprintf(item, DEF_STRING_LEN, "bookmark%d_streamdir_name", i);
-		aud_cfg_db_unset_key(db, "streambrowser", item);
-
-    	g_snprintf(item, DEF_STRING_LEN, "bookmark%d_category_name", i);
-		aud_cfg_db_unset_key(db, "streambrowser", item);
-
-    	g_snprintf(item, DEF_STRING_LEN, "bookmark%d_name", i);
-		aud_cfg_db_unset_key(db, "streambrowser", item);
-
-    	g_snprintf(item, DEF_STRING_LEN, "bookmark%d_playlist_url", i);
-		aud_cfg_db_unset_key(db, "streambrowser", item);
-
-    	g_snprintf(item, DEF_STRING_LEN, "bookmark%d_url", i);
-		aud_cfg_db_unset_key(db, "streambrowser", item);
-	}
-
-    aud_cfg_db_close(db);
-
-    debug("configuration saved\n");
-}
-
 static void streamdir_update(streamdir_t *streamdir, category_t *category, streaminfo_t *streaminfo, gboolean add_to_playlist)
 {
     debug("requested streamdir update (streamdir = '%s', category = '%s', streaminfo = '%s', add_to_playlist = %d)\n", 
@@ -516,7 +549,7 @@
 		    }
 		    /* bookmarks */
 		    else if (strncmp(data->streamdir->name, BOOKMARKS_NAME, strlen(BOOKMARKS_NAME)) == 0) {
-		        streamdir_t *streamdir = bookmarks_streamdir_fetch(streambrowser_cfg.bookmarks, streambrowser_cfg.bookmarks_count);
+		        streamdir_t *streamdir = bookmarks_streamdir_fetch(&streambrowser_cfg.bookmarks, &streambrowser_cfg.bookmarks_count);
 		        if (streamdir != NULL) {
 		            gdk_threads_enter();
 		            streambrowser_win_set_streamdir(streamdir, BOOKMARKS_ICON);
@@ -541,11 +574,15 @@
 		        gdk_threads_leave();
 		    }
 		    /* bookmarks */
-		    streamdir = bookmarks_streamdir_fetch(streambrowser_cfg.bookmarks, streambrowser_cfg.bookmarks_count);
+	    	streamdir = bookmarks_streamdir_fetch(&streambrowser_cfg.bookmarks, &streambrowser_cfg.bookmarks_count);
 		    if (streamdir != NULL) {
 		        gdk_threads_enter();
 		        streambrowser_win_set_streamdir(streamdir, BOOKMARKS_ICON);
 		        gdk_threads_leave();
+		        
+		        int i;
+		        for (i = 0; i < category_get_count(streamdir); i++)
+			        streamdir_update(streamdir, category_get_by_index(streamdir, i), NULL, FALSE);
 		    }
 		}
 
@@ -592,9 +629,9 @@
 
 static void on_plugin_services_menu_item_click()
 {
-    debug("on_plugin_services_menu_item_click()\n");
+	debug("on_plugin_services_menu_item_click()\n");
 
     streambrowser_win_show();
-    streamdir_update(NULL, NULL, NULL, FALSE);
+	streamdir_update(NULL, NULL, NULL, FALSE);
 }
 
--- a/src/streambrowser/streambrowser.h	Sun Aug 17 23:57:16 2008 +0300
+++ b/src/streambrowser/streambrowser.h	Mon Aug 18 14:17:58 2008 +0300
@@ -38,6 +38,9 @@
 void				failure(const char *fmt, ...);
 gboolean			fetch_remote_to_local_file(gchar *remote_url, gchar *local_url);
 
+void				config_load();
+void				config_save();
+
 	/* returns true if the substring has been found, false otherwise */
 gboolean			mystrcasestr(const char *haystack, const char *needle);
 
--- a/src/streambrowser/streamdir.c	Sun Aug 17 23:57:16 2008 +0300
+++ b/src/streambrowser/streamdir.c	Mon Aug 18 14:17:58 2008 +0300
@@ -113,17 +113,6 @@
 
 streaminfo_t* streaminfo_new(gchar *name, gchar *playlist_url, gchar *url, gchar *current_track)
 {
-	/* replace ampersands with slashes, to avoit gtk/pango markup confusions */
-	int i, count = strlen(name);
-	for (i = 0; i < count; i++)
-		if (name[i] == '&')
-			name[i] = '/';
-
-	count = strlen(current_track);
-	for (i = 0; i < count; i++)
-		if (current_track[i] == '&')
-			current_track[i] = '/';
-
 	streaminfo_t *streaminfo = (streaminfo_t*) g_malloc(sizeof(streaminfo_t));
 	strncpy(streaminfo->name, name, DEF_STRING_LEN);
 	strncpy(streaminfo->playlist_url, playlist_url, DEF_STRING_LEN);