diff audacious/util.c @ 0:cb178e5ad177 trunk

[svn] Import audacious source.
author nenolod
date Mon, 24 Oct 2005 03:06:47 -0700
parents
children 2d8234ea45e8
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/audacious/util.c	Mon Oct 24 03:06:47 2005 -0700
@@ -0,0 +1,1352 @@
+/*  BMP - Cross-platform multimedia player
+ *  Copyright (C) 2003-2004  BMP development team.
+ *
+ *  Based on XMMS:
+ *  Copyright (C) 1998-2003  XMMS development team.
+ *
+ *  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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#define NEED_GLADE
+#include "util.h"
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glade/glade.h>
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <gdk/gdkx.h>
+#include <gdk/gdkkeysyms.h>
+#include <X11/Xlib.h>
+#include <sys/ipc.h>
+#include <unistd.h>
+#include <errno.h>
+
+#ifdef HAVE_FTS_H
+#  include <fts.h>
+#endif
+
+#include "glade.h"
+#include "input.h"
+#include "main.h"
+#include "playback.h"
+#include "playlist.h"
+#include "playlistwin.h"
+
+
+static GQuark quark_popup_data;
+
+
+/*
+ * escape_shell_chars()
+ *
+ * Escapes characters that are special to the shell inside double quotes.
+ */
+
+gchar *
+escape_shell_chars(const gchar * string)
+{
+    const gchar *special = "$`\"\\";    /* Characters to escape */
+    const gchar *in = string;
+    gchar *out, *escaped;
+    gint num = 0;
+
+    while (*in != '\0')
+        if (strchr(special, *in++))
+            num++;
+
+    escaped = g_malloc(strlen(string) + num + 1);
+
+    in = string;
+    out = escaped;
+
+    while (*in != '\0') {
+        if (strchr(special, *in))
+            *out++ = '\\';
+        *out++ = *in++;
+    }
+    *out = '\0';
+
+    return escaped;
+}
+
+
+/*
+ * find <file> in directory <dirname> or subdirectories.  return
+ * pointer to complete filename which has to be freed by calling
+ * "g_free()" after use. Returns NULL if file could not be found.
+ */
+
+typedef struct {
+    const gchar *to_match;
+    gchar *match;
+    gboolean found;
+} FindFileContext;
+
+static gboolean
+find_file_func(const gchar * path, const gchar * basename, gpointer data)
+{
+    FindFileContext *context = data;
+
+    if (strlen(path) > FILENAME_MAX) {
+        g_warning("Ignoring path: name too long (%s)", path);
+        return TRUE;
+    }
+
+    if (g_file_test(path, G_FILE_TEST_IS_REGULAR)) {
+        if (!strcasecmp(basename, context->to_match)) {
+            context->match = g_strdup(path);
+            context->found = TRUE;
+            return TRUE;
+        }
+    }
+    else if (g_file_test(path, G_FILE_TEST_IS_DIR)) {
+        dir_foreach(path, find_file_func, context, NULL);
+        if (context->found)
+            return TRUE;
+    }
+
+    return FALSE;
+}
+
+gchar *
+find_file_recursively(const gchar * path, const gchar * filename)
+{
+    FindFileContext context;
+
+    context.to_match = filename;
+    context.match = NULL;
+    context.found = FALSE;
+
+    dir_foreach(path, find_file_func, &context, NULL);
+    return context.match;
+}
+
+
+typedef enum {
+    ARCHIVE_UNKNOWN = 0,
+    ARCHIVE_DIR,
+    ARCHIVE_TAR,
+    ARCHIVE_TGZ,
+    ARCHIVE_ZIP,
+    ARCHIVE_TBZ2
+} ArchiveType;
+
+typedef gchar *(*ArchiveExtractFunc) (const gchar *, const gchar *);
+
+typedef struct {
+    ArchiveType type;
+    const gchar *ext;
+} ArchiveExtensionType;
+
+static ArchiveExtensionType archive_extensions[] = {
+    {ARCHIVE_TAR, ".tar"},
+    {ARCHIVE_ZIP, ".wsz"},
+    {ARCHIVE_ZIP, ".zip"},
+    {ARCHIVE_TGZ, ".tar.gz"},
+    {ARCHIVE_TGZ, ".tgz"},
+    {ARCHIVE_TBZ2, ".tar.bz2"},
+    {ARCHIVE_TBZ2, ".bz2"},
+    {ARCHIVE_UNKNOWN, NULL}
+};
+
+static gchar *archive_extract_tar(const gchar * archive, const gchar * dest);
+static gchar *archive_extract_zip(const gchar * archive, const gchar * dest);
+static gchar *archive_extract_tgz(const gchar * archive, const gchar * dest);
+static gchar *archive_extract_tbz2(const gchar * archive, const gchar * dest);
+
+static ArchiveExtractFunc archive_extract_funcs[] = {
+    NULL,
+    NULL,
+    archive_extract_tar,
+    archive_extract_tgz,
+    archive_extract_zip,
+    archive_extract_tbz2
+};
+
+
+/* FIXME: these functions can be generalised into a function using a
+ * command lookup table */
+
+static const gchar *
+get_tar_command(void)
+{
+    static const gchar *command = NULL;
+
+    if (!command) {
+        if (!(command = getenv("TARCMD")))
+            command = "tar";
+    }
+
+    return command;
+}
+
+static const gchar *
+get_unzip_command(void)
+{
+    static const gchar *command = NULL;
+
+    if (!command) {
+        if (!(command = getenv("UNZIPCMD")))
+            command = "unzip";
+    }
+
+    return command;
+}
+
+
+static gchar *
+archive_extract_tar(const gchar * archive, const gchar * dest)
+{
+    return g_strdup_printf("%s >/dev/null xf \"%s\" -C %s",
+                           get_tar_command(), archive, dest);
+}
+
+static gchar *
+archive_extract_zip(const gchar * archive, const gchar * dest)
+{
+    return g_strdup_printf("%s >/dev/null -o -j \"%s\" -d %s",
+                           get_unzip_command(), archive, dest);
+}
+
+static gchar *
+archive_extract_tgz(const gchar * archive, const gchar * dest)
+{
+    return g_strdup_printf("%s >/dev/null xzf \"%s\" -C %s",
+                           get_tar_command(), archive, dest);
+}
+
+static gchar *
+archive_extract_tbz2(const gchar * archive, const gchar * dest)
+{
+    return g_strdup_printf("bzip2 -dc \"%s\" | %s >/dev/null xf - -C %s",
+                           archive, get_tar_command(), dest);
+}
+
+
+ArchiveType
+archive_get_type(const gchar * filename)
+{
+    gint i = 0;
+
+    if (g_file_test(filename, G_FILE_TEST_IS_DIR))
+        return ARCHIVE_DIR;
+
+    while (archive_extensions[i].ext) {
+        if (g_str_has_suffix(filename, archive_extensions[i].ext)) {
+            return archive_extensions[i].type;
+        }
+        i++;
+    }
+
+    return ARCHIVE_UNKNOWN;
+}
+
+gboolean
+file_is_archive(const gchar * filename)
+{
+    return (archive_get_type(filename) > ARCHIVE_DIR);
+}
+
+gchar *
+archive_basename(const gchar * str)
+{
+    gint i = 0;
+
+    while (archive_extensions[i].ext) {
+        if (str_has_suffix_nocase(str, archive_extensions[i].ext)) {
+            const gchar *end = g_strrstr(str, archive_extensions[i].ext);
+            if (end) {
+                return g_strndup(str, end - str);
+            }
+            break;
+        }
+        i++;
+    }
+
+    return NULL;
+}
+
+/*
+   decompress_archive
+
+   Decompresses the archive "filename" to a temporary directory,
+   returns the path to the temp dir, or NULL if failed,
+   watch out tho, doesn't actually check if the system command succeeds :-|
+*/
+
+gchar *
+archive_decompress(const gchar * filename)
+{
+    gchar *tmpdir, *cmd, *escaped_filename;
+    ArchiveType type;
+
+    if ((type = archive_get_type(filename)) <= ARCHIVE_DIR)
+        return NULL;
+
+    tmpdir = g_build_filename(g_get_tmp_dir(), "bmp.XXXXXXXX", NULL);
+    if (!mkdtemp(tmpdir)) {
+        g_free(tmpdir);
+        g_message("Unable to load skin: Failed to create temporary "
+                  "directory: %s", g_strerror(errno));
+        return NULL;
+    }
+
+    escaped_filename = escape_shell_chars(filename);
+    cmd = archive_extract_funcs[type] (escaped_filename, tmpdir);
+    g_free(escaped_filename);
+
+    if (!cmd) {
+        g_message("extraction function is NULL!");
+        g_free(tmpdir);
+        return NULL;
+    }
+
+    system(cmd);
+    g_free(cmd);
+
+    return tmpdir;
+}
+
+
+#ifdef HAVE_FTS_H
+
+void
+del_directory(const gchar * dirname)
+{
+    gchar *const argv[2] = { (gchar *) dirname, NULL };
+    FTS *fts;
+    FTSENT *p;
+
+    fts = fts_open(argv, FTS_PHYSICAL, (gint(*)())NULL);
+    while ((p = fts_read(fts))) {
+        switch (p->fts_info) {
+        case FTS_D:
+            break;
+        case FTS_DNR:
+        case FTS_ERR:
+            break;
+        case FTS_DP:
+            rmdir(p->fts_accpath);
+            break;
+        default:
+            unlink(p->fts_accpath);
+            break;
+        }
+    }
+    fts_close(fts);
+}
+
+#else                           /* !HAVE_FTS */
+
+gboolean
+del_directory_func(const gchar * path, const gchar * basename,
+                   gpointer params)
+{
+    if (!strcmp(basename, ".") || !strcmp(path, ".."))
+        return FALSE;
+
+    if (g_file_test(path, G_FILE_TEST_IS_DIR)) {
+        dir_foreach(path, del_directory_func, NULL, NULL);
+        rmdir(path);
+        return FALSE;
+    }
+
+    unlink(path);
+
+    return FALSE;
+}
+
+void
+del_directory(const gchar * path)
+{
+    dir_foreach(path, del_directory_func, NULL, NULL);
+    rmdir(path);
+}
+
+#endif                          /* ifdef HAVE_FTS */
+
+gchar *
+read_ini_string(const gchar * filename, const gchar * section,
+                const gchar * key)
+{
+    gchar *buffer, *ret_buffer = NULL;
+    gint found_section = 0, off = 0, len = 0;
+    gsize filesize;
+
+    if (!filename)
+        return NULL;
+
+    if (!g_file_get_contents(filename, &buffer, &filesize, NULL))
+        return NULL;
+
+    while (!ret_buffer && off < filesize) {
+        while (off < filesize &&
+               (buffer[off] == '\r' || buffer[off] == '\n' ||
+                buffer[off] == ' ' || buffer[off] == '\t'))
+            off++;
+        if (off >= filesize)
+            break;
+        if (buffer[off] == '[') {
+            gint slen = strlen(section);
+            off++;
+            found_section = 0;
+            if (off + slen + 1 < filesize &&
+                !strncasecmp(section, &buffer[off], slen)) {
+                off += slen;
+                if (buffer[off] == ']') {
+                    off++;
+                    found_section = 1;
+                }
+            }
+        }
+        else if (found_section && off + strlen(key) < filesize &&
+                 !strncasecmp(key, &buffer[off], strlen(key))) {
+            off += strlen(key);
+            while (off < filesize &&
+                   (buffer[off] == ' ' || buffer[off] == '\t'))
+                off++;
+            if (off >= filesize)
+                break;
+            if (buffer[off] == '=') {
+                off++;
+                while (off < filesize &&
+                       (buffer[off] == ' ' || buffer[off] == '\t'))
+                    off++;
+                if (off >= filesize)
+                    break;
+                len = 0;
+                while (off + len < filesize &&
+                       buffer[off + len] != '\r' &&
+                       buffer[off + len] != '\n' && buffer[off + len] != ';')
+                    len++;
+                ret_buffer = g_strndup(&buffer[off], len);
+                off += len;
+            }
+        }
+        while (off < filesize && buffer[off] != '\r' && buffer[off] != '\n')
+            off++;
+    }
+
+    g_free(buffer);
+    return ret_buffer;
+}
+
+GArray *
+string_to_garray(const gchar * str)
+{
+    GArray *array;
+    gint temp;
+    const gchar *ptr = str;
+    gchar *endptr;
+
+    array = g_array_new(FALSE, TRUE, sizeof(gint));
+    for (;;) {
+        temp = strtol(ptr, &endptr, 10);
+        if (ptr == endptr)
+            break;
+        g_array_append_val(array, temp);
+        ptr = endptr;
+        while (!isdigit(*ptr) && (*ptr) != '\0')
+            ptr++;
+        if (*ptr == '\0')
+            break;
+    }
+    return (array);
+}
+
+GArray *
+read_ini_array(const gchar * filename, const gchar * section,
+               const gchar * key)
+{
+    gchar *temp;
+    GArray *a;
+
+    if ((temp = read_ini_string(filename, section, key)) == NULL) {
+        g_free(temp);
+        return NULL;
+    }
+    a = string_to_garray(temp);
+    g_free(temp);
+    return a;
+}
+
+void
+glist_movedown(GList * list)
+{
+    gpointer temp;
+
+    if (g_list_next(list)) {
+        temp = list->data;
+        list->data = list->next->data;
+        list->next->data = temp;
+    }
+}
+
+void
+glist_moveup(GList * list)
+{
+    gpointer temp;
+
+    if (g_list_previous(list)) {
+        temp = list->data;
+        list->data = list->prev->data;
+        list->prev->data = temp;
+    }
+}
+
+
+void
+util_menu_position(GtkMenu * menu, gint * x, gint * y,
+                   gboolean * push_in, gpointer data)
+{
+    GtkRequisition requisition;
+    gint screen_width;
+    gint screen_height;
+    MenuPos *pos = data;
+
+    gtk_widget_size_request(GTK_WIDGET(menu), &requisition);
+
+    screen_width = gdk_screen_width();
+    screen_height = gdk_screen_height();
+
+    *x = CLAMP(pos->x - 2, 0, MAX(0, screen_width - requisition.width));
+    *y = CLAMP(pos->y - 2, 0, MAX(0, screen_height - requisition.height));
+}
+
+static void
+util_menu_delete_popup_data(GtkObject * object, GtkItemFactory * ifactory)
+{
+    gtk_signal_disconnect_by_func(object,
+                                  GTK_SIGNAL_FUNC
+                                  (util_menu_delete_popup_data), ifactory);
+    gtk_object_remove_data_by_id(GTK_OBJECT(ifactory), quark_popup_data);
+}
+
+
+/*
+ * util_item_factory_popup[_with_data]() is a replacement for
+ * gtk_item_factory_popup[_with_data]().
+ *
+ * The difference is that the menu is always popped up whithin the
+ * screen.  This means it does not neccesarily pop up at (x,y).
+ */
+
+void
+util_item_factory_popup_with_data(GtkItemFactory * ifactory,
+                                  gpointer data,
+                                  GtkDestroyNotify destroy, guint x,
+                                  guint y, guint mouse_button, guint32 time)
+{
+    static GQuark quark_user_menu_pos = 0;
+    MenuPos *pos;
+
+    if (!quark_user_menu_pos)
+        quark_user_menu_pos = g_quark_from_static_string("user_menu_pos");
+
+    if (!quark_popup_data)
+        quark_popup_data =
+            g_quark_from_static_string("GtkItemFactory-popup-data");
+
+    pos = g_object_get_qdata(G_OBJECT(ifactory), quark_user_menu_pos);
+    if (!pos) {
+        pos = g_new0(MenuPos, 1);
+
+        g_object_set_qdata_full(G_OBJECT(ifactory->widget),
+                                quark_user_menu_pos, pos, g_free);
+    }
+    pos->x = x;
+    pos->y = y;
+
+
+    if (data != NULL) {
+        g_object_set_qdata_full(G_OBJECT(ifactory),
+                                quark_popup_data, data, destroy);
+        g_signal_connect(G_OBJECT(ifactory->widget),
+                         "selection-done",
+                         G_CALLBACK(util_menu_delete_popup_data), ifactory);
+    }
+
+    gtk_menu_popup(GTK_MENU(ifactory->widget), NULL, NULL,
+                   (GtkMenuPositionFunc) util_menu_position,
+                   pos, mouse_button, time);
+}
+
+void
+util_item_factory_popup(GtkItemFactory * ifactory, guint x, guint y,
+                        guint mouse_button, guint32 time)
+{
+    util_item_factory_popup_with_data(ifactory, NULL, NULL, x, y,
+                                      mouse_button, time);
+}
+
+
+#define URL_HISTORY_MAX_SIZE 30
+
+static void
+util_add_url_callback(GtkWidget * widget,
+                      GtkEntry * entry)
+{
+    const gchar *text;
+
+    text = gtk_entry_get_text(entry);
+    if (g_list_find_custom(cfg.url_history, text, (GCompareFunc) strcasecmp))
+        return;
+
+    cfg.url_history = g_list_prepend(cfg.url_history, g_strdup(text));
+
+    while (g_list_length(cfg.url_history) > URL_HISTORY_MAX_SIZE) {
+        GList *node = g_list_last(cfg.url_history);
+        g_free(node->data);
+        cfg.url_history = g_list_delete_link(cfg.url_history, node);
+    }
+}
+
+GtkWidget *
+util_add_url_dialog_new(const gchar * caption, GCallback enqueue_func)
+{
+    GtkWidget *win, *vbox, *bbox, *enqueue, *cancel, *combo, *entry;
+    GList *url;
+
+    win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+    gtk_window_set_title(GTK_WINDOW(win), caption);
+    gtk_window_set_type_hint(GTK_WINDOW(win), GDK_WINDOW_TYPE_HINT_DIALOG);
+    gtk_window_set_position(GTK_WINDOW(win), GTK_WIN_POS_CENTER);
+    gtk_window_set_default_size(GTK_WINDOW(win), 400, -1);
+    gtk_container_set_border_width(GTK_CONTAINER(win), 12);
+
+    vbox = gtk_vbox_new(FALSE, 10);
+    gtk_container_add(GTK_CONTAINER(win), vbox);
+
+    combo = gtk_combo_box_entry_new_text();
+    gtk_box_pack_start(GTK_BOX(vbox), combo, FALSE, FALSE, 0);
+
+    entry = gtk_bin_get_child(GTK_BIN(combo));
+    gtk_window_set_focus(GTK_WINDOW(win), entry);
+    gtk_entry_set_text(GTK_ENTRY(entry), "");
+
+    for (url = cfg.url_history; url; url = g_list_next(url))
+        gtk_combo_box_append_text(GTK_COMBO_BOX(combo), (const gchar *) url->data);
+
+    g_signal_connect(entry, "activate",
+                     G_CALLBACK(util_add_url_callback),
+                     entry);
+    g_signal_connect(entry, "activate",
+                     G_CALLBACK(enqueue_func),
+                     entry);
+    g_signal_connect_swapped(entry, "activate",
+                             G_CALLBACK(gtk_widget_destroy),
+                             win);
+
+    bbox = gtk_hbutton_box_new();
+    gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
+    gtk_button_box_set_spacing(GTK_BUTTON_BOX(bbox), 5);
+    gtk_box_pack_start(GTK_BOX(vbox), bbox, FALSE, FALSE, 0);
+
+    enqueue = gtk_button_new_from_stock(GTK_STOCK_ADD);
+    gtk_box_pack_start(GTK_BOX(bbox), enqueue, FALSE, FALSE, 0);
+
+    g_signal_connect(enqueue, "clicked",
+                     G_CALLBACK(util_add_url_callback),
+                     entry);
+    g_signal_connect(enqueue, "clicked",
+                     G_CALLBACK(enqueue_func),
+                     entry);
+    g_signal_connect_swapped(enqueue, "clicked",
+                             G_CALLBACK(gtk_widget_destroy),
+                             win);
+
+    cancel = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
+    gtk_box_pack_start(GTK_BOX(bbox), cancel, FALSE, FALSE, 0);
+
+    g_signal_connect_swapped(cancel, "clicked",
+                             G_CALLBACK(gtk_widget_destroy),
+                             win);
+
+    gtk_widget_show_all(vbox);
+
+    return win;
+}
+
+static void
+filebrowser_add_files(GtkFileChooser * browser,
+                      GSList * files)
+{
+    GSList *cur;
+    gchar *ptr;
+    guint ctr = 0;
+
+    if (GTK_IS_WIDGET(mainwin_jtf))
+        gtk_widget_set_sensitive(mainwin_jtf, FALSE);
+
+    for (cur = files; cur; cur = g_slist_next(cur)) {
+
+        if (g_file_test(cur->data,G_FILE_TEST_IS_DIR)) {
+            playlist_add_dir((const gchar *) cur->data);
+        } else {
+            playlist_add((const gchar *) cur->data);
+        }       
+
+        if (++ctr == 20) {
+            playlistwin_update_list();
+            ctr = 0;
+            while (gtk_events_pending() ) gtk_main_iteration();
+        }
+    } 
+
+    playlistwin_update_list();
+
+    if (GTK_IS_WIDGET(mainwin_jtf))
+        gtk_widget_set_sensitive(mainwin_jtf, TRUE);
+
+#ifdef HAVE_GNOME_VFS
+    ptr = gtk_file_chooser_get_current_folder_uri(GTK_FILE_CHOOSER(browser));
+#else
+    ptr = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(browser));
+#endif
+
+    g_free(cfg.filesel_path);
+    cfg.filesel_path = ptr;
+}
+
+static void
+filebrowser_add(GtkFileChooser * browser)
+{
+    GSList *files;
+
+#ifdef HAVE_GNOME_VFS
+    files = gtk_file_chooser_get_uris(GTK_FILE_CHOOSER(browser));
+#else
+    files = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(browser));
+#endif
+
+    if (!files) {
+        return;
+    }
+
+    filebrowser_add_files(browser, files);
+    g_slist_foreach(files, (GFunc) g_free, NULL);
+    g_slist_free(files);
+}
+
+static void
+filebrowser_play(GtkFileChooser * browser)
+{
+    GSList *files;
+
+#ifdef HAVE_GNOME_VFS
+    files = gtk_file_chooser_get_uris(GTK_FILE_CHOOSER(browser));
+#else
+    files = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(browser));
+#endif
+
+    if (!files) return;
+
+    playlist_clear();
+
+    filebrowser_add_files(browser, files);
+    g_slist_foreach(files, (GFunc) g_free, NULL);
+    g_slist_free(files);
+
+    bmp_playback_initiate();
+}
+
+
+static void
+_filebrowser_add(GtkWidget *widget,
+                 gpointer data)
+{
+    filebrowser_add(data);
+    gtk_file_chooser_unselect_all(GTK_FILE_CHOOSER(data));
+}
+
+static void
+_filebrowser_play(GtkWidget *widget, gpointer data)
+{
+    filebrowser_play(data);
+    gtk_file_chooser_unselect_all(data);
+}
+
+#if 0
+static void
+filebrowser_on_response(GtkFileChooser * browser,
+                        gint response,
+                        gpointer data)
+{
+    gtk_widget_hide(GTK_WIDGET(browser));
+    switch (response) {
+    case GTK_RESPONSE_OK:
+        break;
+    case GTK_RESPONSE_ACCEPT:
+        break;
+    case GTK_RESPONSE_CLOSE:
+        break;
+    }
+    gtk_widget_destroy(GTK_WIDGET(browser));
+
+}
+
+#endif
+
+static void
+_filebrowser_check_hide_add(GtkWidget * widget,
+                            gpointer data)
+{
+    cfg.close_dialog_add = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
+}
+
+static void
+_filebrowser_check_hide_open(GtkWidget * widget,
+                             gpointer data)
+{
+    cfg.close_dialog_open = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
+}
+
+
+
+static gboolean
+filebrowser_on_keypress(GtkWidget * browser,
+                        GdkEventKey * event,
+                        gpointer data)
+{
+    if (event->keyval == GDK_Escape) {
+        /* FIXME: this crashes BMP for some reason */
+        /* g_signal_emit_by_name(browser, "delete-event"); */
+        gtk_widget_hide(browser);
+        return TRUE;
+    }
+
+    return FALSE;
+}
+
+static void
+_filebrowser_do_hide_add(GtkWidget *widget,
+                         gpointer data)
+{
+    if (cfg.close_dialog_add)
+        gtk_widget_hide(data);
+}
+
+static void
+_filebrowser_do_hide_open(GtkWidget *widget,
+                          gpointer data)
+{
+    if (cfg.close_dialog_open)
+        gtk_widget_hide(data);
+}
+
+void
+util_run_filebrowser(gboolean play_button)
+{
+    static GladeXML *xml = NULL;
+    static GtkWidget *dialog = NULL;
+    static GtkWidget *chooser = NULL;
+    
+    static GtkWidget *button_add;
+    static GtkWidget *button_select_all, *button_deselect_all;
+    static GtkWidget *toggle;
+
+    static gulong handlerid, handlerid_check, handlerid_do;
+    static gulong handlerid_activate, handlerid_do_activate;
+
+    if (!xml) {
+        /* FIXME: Creating a file chooser dialog manually using
+           libglade because there's no way to stop
+           GtkFileChooserDialog from resizing the buttons to the same
+           size. The long toggle button title causes the buttons to
+           turn unnecessarily elongated and fugly. */
+
+        GtkWidget *alignment;
+
+        xml = glade_xml_new_or_die(_("Add/Open Files dialog"),
+                                   DATA_DIR "/glade/addfiles.glade",
+                                   NULL, NULL);
+        glade_xml_signal_autoconnect(xml);
+
+        dialog = glade_xml_get_widget(xml, "add_files_dialog");
+
+        /* FIXME: Creating file chooser widget here because libglade <= 2.4.0 does
+           not support GtkFileChooserWidget */
+
+        chooser = gtk_file_chooser_widget_new(GTK_FILE_CHOOSER_ACTION_OPEN);    
+        gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(chooser), TRUE);
+        
+#ifdef HAVE_GNOME_VFS
+        gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(chooser), FALSE);
+#endif
+        
+        if (cfg.filesel_path)
+#ifdef HAVE_GNOME_VFS
+            gtk_file_chooser_set_current_folder_uri(GTK_FILE_CHOOSER(chooser),
+                                                    cfg.filesel_path);
+#else
+            gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(chooser),
+                                                cfg.filesel_path);
+#endif
+
+        alignment = glade_xml_get_widget(xml, "alignment2");
+        gtk_container_add(GTK_CONTAINER(alignment), chooser);
+
+        toggle = glade_xml_get_widget(xml, "close_on_action");
+        button_select_all = glade_xml_get_widget(xml, "select_all");
+        button_deselect_all = glade_xml_get_widget(xml, "deselect_all");
+        button_add = glade_xml_get_widget(xml, "action");
+
+        g_signal_connect_swapped(button_select_all, "clicked", 
+                                 G_CALLBACK(gtk_file_chooser_select_all),
+                                 chooser);
+        g_signal_connect_swapped(button_deselect_all, "clicked",
+                                 G_CALLBACK(gtk_file_chooser_unselect_all),
+                                 chooser);
+
+        g_signal_connect(dialog, "key_press_event",
+                         G_CALLBACK(filebrowser_on_keypress),
+                         NULL);
+
+        gtk_widget_show_all(dialog);
+    } /* !xml */
+    else {
+        g_signal_handler_disconnect(button_add, handlerid);
+        g_signal_handler_disconnect(toggle, handlerid_check);
+        g_signal_handler_disconnect(chooser, handlerid_activate);
+        g_signal_handler_disconnect(button_add, handlerid_do);
+        g_signal_handler_disconnect(chooser, handlerid_do_activate);
+    }
+    
+    if (play_button) {
+        cfg.close_dialog_open = TRUE;
+        
+        gtk_window_set_title(GTK_WINDOW(dialog), _("Open Files"));
+
+        gtk_button_set_label(GTK_BUTTON(button_add), GTK_STOCK_OPEN);
+
+        gtk_button_set_label(GTK_BUTTON(toggle), _("Close dialog on Open"));
+        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle), cfg.close_dialog_open);
+
+        handlerid = g_signal_connect(button_add, "clicked", G_CALLBACK(_filebrowser_play), chooser);
+        handlerid_check = g_signal_connect(toggle, "toggled", G_CALLBACK(_filebrowser_check_hide_open), NULL);
+        handlerid_do = g_signal_connect_after(button_add, "clicked", G_CALLBACK(_filebrowser_do_hide_open), dialog);
+        handlerid_activate = g_signal_connect(chooser, "file-activated", G_CALLBACK(_filebrowser_play), chooser);
+        handlerid_do_activate = g_signal_connect_after(chooser,"file_activated", G_CALLBACK(_filebrowser_do_hide_open), dialog);
+    }
+    else {
+        cfg.close_dialog_add = TRUE;
+
+        gtk_window_set_title(GTK_WINDOW(dialog), _("Add Files"));
+
+        gtk_button_set_label(GTK_BUTTON(button_add), GTK_STOCK_ADD);
+
+        gtk_button_set_label(GTK_BUTTON(toggle), _("Close dialog on Add"));
+        gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle), cfg.close_dialog_add);
+
+        handlerid = g_signal_connect(button_add, "clicked", G_CALLBACK(_filebrowser_add), chooser);
+        handlerid_check = g_signal_connect(toggle, "toggled", G_CALLBACK(_filebrowser_check_hide_add), NULL);
+        handlerid_do = g_signal_connect_after(button_add, "clicked", G_CALLBACK(_filebrowser_do_hide_add), dialog);
+        handlerid_activate = g_signal_connect(chooser, "file-activated", G_CALLBACK(_filebrowser_add), chooser);
+        handlerid_do_activate = g_signal_connect_after(chooser,"file_activated", G_CALLBACK(_filebrowser_do_hide_add), dialog);
+    }
+    
+    gtk_window_present(GTK_WINDOW(dialog));
+}
+
+GdkFont *
+util_font_load(const gchar * name)
+{
+    GdkFont *font;
+    PangoFontDescription *desc;
+
+    desc = pango_font_description_from_string(name);
+    font = gdk_font_from_description(desc);
+
+    return font;
+}
+
+#ifdef ENABLE_NLS
+gchar *
+bmp_menu_translate(const gchar * path, gpointer func_data)
+{
+    gchar *translation = gettext(path);
+
+    if (!translation || *translation != '/') {
+        g_warning("Bad translation for menupath: %s", path);
+        translation = (gchar *) path;
+    }
+
+    return translation;
+}
+#endif
+
+void
+util_set_cursor(GtkWidget * window)
+{
+    static GdkCursor *cursor = NULL;
+
+    if (!window) {
+        if (cursor) {
+            gdk_cursor_destroy(cursor);
+            cursor = NULL;
+        }
+        return;
+    }
+
+    if (!cursor)
+        cursor = gdk_cursor_new(GDK_LEFT_PTR);
+
+    gdk_window_set_cursor(window->window, cursor);
+}
+
+/* text_get_extents() taken from The GIMP (C) Spencer Kimball, Peter
+ * Mattis et al */
+gboolean
+text_get_extents(const gchar * fontname,
+                 const gchar * text,
+                 gint * width, gint * height, gint * ascent, gint * descent)
+{
+    PangoFontDescription *font_desc;
+    PangoLayout *layout;
+    PangoRectangle rect;
+
+    g_return_val_if_fail(fontname != NULL, FALSE);
+    g_return_val_if_fail(text != NULL, FALSE);
+
+    /* FIXME: resolution */
+    layout = gtk_widget_create_pango_layout(GTK_WIDGET(mainwin), text);
+
+    font_desc = pango_font_description_from_string(fontname);
+    pango_layout_set_font_description(layout, font_desc);
+    pango_font_description_free(font_desc);
+    pango_layout_get_pixel_extents(layout, NULL, &rect);
+
+    if (width)
+        *width = rect.width;
+    if (height)
+        *height = rect.height;
+
+    if (ascent || descent) {
+        PangoLayoutIter *iter;
+        PangoLayoutLine *line;
+
+        iter = pango_layout_get_iter(layout);
+        line = pango_layout_iter_get_line(iter);
+        pango_layout_iter_free(iter);
+
+        pango_layout_line_get_pixel_extents(line, NULL, &rect);
+
+        if (ascent)
+            *ascent = PANGO_ASCENT(rect);
+        if (descent)
+            *descent = -PANGO_DESCENT(rect);
+    }
+
+    g_object_unref(layout);
+
+    return TRUE;
+}
+
+/* counts number of digits in a gint */
+guint
+gint_count_digits(gint n)
+{
+    guint count = 0;
+
+    n = ABS(n);
+    do {
+        count++;
+        n /= 10;
+    } while (n > 0);
+
+    return count;
+}
+
+static gchar *
+str_twenty_to_space(gchar * str)
+{
+    gchar *match, *match_end;
+
+    g_return_val_if_fail(str != NULL, NULL);
+
+    while ((match = strstr(str, "%20"))) {
+        match_end = match + 3;
+        *match++ = ' ';
+        while (*match_end)
+            *match++ = *match_end++;
+        *match = 0;
+    }
+
+    return str;
+}
+
+static gchar *
+str_replace_char(gchar * str, gchar old, gchar new)
+{
+    gchar *match;
+
+    g_return_val_if_fail(str != NULL, NULL);
+
+    match = str;
+    while ((match = strchr(match, old)))
+        *match = new;
+
+    return str;
+}
+
+gchar *
+str_append(gchar * str, const gchar * add_str)
+{
+    return str_replace(str, g_strconcat(str, add_str, NULL));
+}
+
+gchar *
+str_replace(gchar * str, gchar * new_str)
+{
+    g_free(str);
+    return new_str;
+}
+
+void
+str_replace_in(gchar ** str, gchar * new_str)
+{
+    *str = str_replace(*str, new_str);
+}
+
+
+gboolean
+str_has_prefix_nocase(const gchar * str, const gchar * prefix)
+{
+    return (strncasecmp(str, prefix, strlen(prefix)) == 0);
+}
+
+gboolean
+str_has_suffix_nocase(const gchar * str, const gchar * suffix)
+{
+    return (strcasecmp(str + strlen(str) - strlen(suffix), suffix) == 0);
+}
+
+gboolean
+str_has_suffixes_nocase(const gchar * str, gchar * const *suffixes)
+{
+    gchar *const *suffix;
+
+    g_return_val_if_fail(str != NULL, FALSE);
+    g_return_val_if_fail(suffixes != NULL, FALSE);
+
+    for (suffix = suffixes; *suffix; suffix++)
+        if (str_has_suffix_nocase(str, *suffix))
+            return TRUE;
+
+    return FALSE;
+}
+
+gchar *
+str_to_utf8_fallback(const gchar * str)
+{
+    gchar *out_str, *convert_str, *chr;
+
+    /* NULL in NULL out */
+    if (!str)
+        return NULL;
+
+    convert_str = g_strdup(str);
+    for (chr = convert_str; *chr; chr++) {
+        if (*chr & 0x80)
+            *chr = '?';
+    }
+
+    out_str = g_strconcat(convert_str, _("  (invalid UTF-8)"), NULL);
+    g_free(convert_str);
+
+    return out_str;
+}
+
+gchar *
+filename_to_utf8(const gchar * filename)
+{
+    gchar *out_str;
+
+    /* NULL in NULL out */
+    if (!filename)
+        return NULL;
+
+    if ((out_str = g_filename_to_utf8(filename, -1, NULL, NULL, NULL)))
+        return out_str;
+
+    return str_to_utf8_fallback(filename);
+}
+
+gchar *
+str_to_utf8(const gchar * str)
+{
+    gchar *out_str;
+
+    /* NULL in NULL out */
+    if (!str)
+        return NULL;
+
+    /* already UTF-8? */
+    if (g_utf8_validate(str, -1, NULL))
+        return g_strdup(str);
+
+    /* assume encoding associated with locale */
+    if ((out_str = g_locale_to_utf8(str, -1, NULL, NULL, NULL)))
+        return out_str;
+
+    /* all else fails, we mask off character codes >= 128,
+       replace with '?' */
+    return str_to_utf8_fallback(str);
+}
+
+
+const gchar *
+str_skip_chars(const gchar * str, const gchar * chars)
+{
+    while (strchr(chars, *str))
+        str++;
+    return str;
+}
+
+gchar *
+convert_title_text(gchar * title)
+{
+    g_return_val_if_fail(title != NULL, NULL);
+
+    if (cfg.convert_underscore)
+        str_replace_char(title, '_', ' ');
+
+    if (cfg.convert_twenty)
+        str_twenty_to_space(title);
+
+    return title;
+}
+
+
+gboolean
+dir_foreach(const gchar * path, DirForeachFunc function,
+            gpointer user_data, GError ** error)
+{
+    GError *error_out = NULL;
+    GDir *dir;
+    const gchar *entry;
+    gchar *entry_fullpath;
+
+    if (!(dir = g_dir_open(path, 0, &error_out))) {
+        g_propagate_error(error, error_out);
+        return FALSE;
+    }
+
+    while ((entry = g_dir_read_name(dir))) {
+        entry_fullpath = g_build_filename(path, entry, NULL);
+
+        if ((*function) (entry_fullpath, entry, user_data)) {
+            g_free(entry_fullpath);
+            break;
+        }
+
+        g_free(entry_fullpath);
+    }
+
+    g_dir_close(dir);
+
+    return TRUE;
+}
+
+GtkWidget *
+make_filebrowser(const gchar * title,
+                 gboolean save)
+{
+    GtkWidget *dialog;
+    GtkWidget *button;
+    GtkWidget *button_close;
+
+    g_return_val_if_fail(title != NULL, NULL);
+
+    dialog = gtk_file_chooser_dialog_new(title, GTK_WINDOW(mainwin),
+                                         GTK_FILE_CHOOSER_ACTION_OPEN, NULL);
+    if (save)
+        gtk_file_chooser_set_action(GTK_FILE_CHOOSER(dialog),
+                                    GTK_FILE_CHOOSER_ACTION_SAVE);
+
+    if (!save)
+        gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
+
+    g_signal_connect(dialog, "destroy",
+                     G_CALLBACK(gtk_widget_destroyed), &dialog);
+
+#ifdef HAVE_GNOME_VFS
+    gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(dialog), FALSE);
+#endif
+
+    button_close = gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_CANCEL,
+                                         GTK_RESPONSE_REJECT);
+    gtk_button_set_use_stock(GTK_BUTTON(button_close), TRUE);
+    GTK_WIDGET_SET_FLAGS(button_close, GTK_CAN_DEFAULT);
+    g_signal_connect_swapped(button_close, "clicked",
+                             G_CALLBACK(gtk_widget_destroy), dialog);
+
+    button = gtk_dialog_add_button(GTK_DIALOG(dialog), save ?
+                                   GTK_STOCK_SAVE : GTK_STOCK_OPEN,
+                                   GTK_RESPONSE_ACCEPT);
+    gtk_button_set_use_stock(GTK_BUTTON(button), TRUE);
+    GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
+    gtk_window_set_default(GTK_WINDOW(dialog), button);
+
+    gtk_widget_show(dialog);
+
+    return dialog;
+}
+
+
+GtkItemFactory *
+create_menu(GtkItemFactoryEntry *entries,
+            guint n_entries,
+            GtkAccelGroup *accel)
+{
+    GtkItemFactory *menu;
+
+    menu = gtk_item_factory_new(GTK_TYPE_MENU, "<Main>", accel);
+    gtk_item_factory_set_translate_func(menu, bmp_menu_translate, NULL,
+                                        NULL);
+    gtk_item_factory_create_items(menu, n_entries, entries, NULL);
+
+    return menu;
+}
+
+
+void
+make_submenu(GtkItemFactory *menu,
+             const gchar *item_path,
+             GtkItemFactory *submenu)
+{
+    GtkWidget *item, *menu_;
+
+    item = gtk_item_factory_get_widget(menu, item_path);
+    menu_ = gtk_item_factory_get_widget(submenu, "");
+    gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), menu_);
+}
+
+
+
+
+
+
+