Mercurial > audlegacy
diff src/audacious/util.c @ 2313:3149d4b1a9a9 trunk
[svn] - objective-make autodepend fixes
- move all sourcecode into src/ and adjust Makefiles accordingly
author | nenolod |
---|---|
date | Fri, 12 Jan 2007 11:43:40 -0800 |
parents | |
children | d88558b0de0a |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/audacious/util.c Fri Jan 12 11:43:40 2007 -0800 @@ -0,0 +1,1569 @@ +/* Audacious - Cross-platform multimedia player + * Copyright (C) 2005-2007 Audacious development team + * + * Based on BMP: + * 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; under version 2 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#define WEIRD_UTF_16_PLAYLIST_ENCODING + +#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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> + +#include "platform/smartinclude.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 "ui_playlist.h" + +#ifdef USE_CHARDET + #include "../libguess/libguess.h" + #include "../librcd/librcd.h" +#ifdef HAVE_UDET + #include <libudet_c.h> +#endif +#endif + +static GQuark quark_popup_data; + + +/* + * 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; + mode_t mode755 = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; + + if ((type = archive_get_type(filename)) <= ARCHIVE_DIR) + return NULL; + +#ifdef HAVE_MKDTEMP + tmpdir = g_build_filename(g_get_tmp_dir(), "audacious.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; + } +#else + tmpdir = g_strdup_printf("%s/audacious.%ld", g_get_tmp_dir(), rand()); + make_directory(tmpdir, mode755); +#endif + + 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) +{ + static gchar *buffer = NULL; + static gchar *open_buffer = NULL; + gchar *ret_buffer = NULL; + gint found_section = 0, len = 0; + static gsize filesize = 0; + gsize off = 0; + gchar *outbuf; + unsigned char x[] = { 0xff, 0xfe, 0x00 }; + guint counter; + + if (!filename) + return NULL; + + /* + * We optimise for the condition that we may be reading from the + * same ini-file multiple times. This is fairly common; it happens + * on .pls playlist loads. To do otherwise would take entirely too + * long, as fstat() can be very slow when done 21,000 times too many. + * + * Therefore, we optimise by keeping the last ini file in memory. + * Yes, this is a memory leak, but it is not too bad, hopefully. + * - nenolod + */ + if (open_buffer == NULL || strcasecmp(filename, open_buffer)) + { + if (buffer != NULL) + { + g_free(buffer); + buffer = NULL; + } + + if (open_buffer != NULL) + { + g_free(open_buffer); + open_buffer = NULL; + } + + if (!g_file_get_contents(filename, &buffer, &filesize, NULL)) + return NULL; + + open_buffer = g_strdup(filename); + } + + /* + * Convert UTF-16 into something useful. Original implementation + * by incomp@#audacious. Cleanups \nenolod + */ + if (!memcmp(&buffer[0],&x,2)) { + outbuf = g_malloc (filesize); /* it's safe to waste memory. */ + + for (counter = 2; counter < filesize; counter += 2) + if (!memcmp(&buffer[counter+1], &x[2], 1)) + outbuf[(counter-2)/2] = buffer[counter]; + else + return NULL; + + outbuf[(counter-2)/2] = '\0'; + + if ((filesize - 2) / 2 == (counter - 2) / 2) { + g_free(buffer); + buffer = outbuf; + } else { + g_free(outbuf); + return NULL; /* XXX wrong encoding */ + } + } + + 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++; + } + + 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((int) *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)); +} + +#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 ok_func, + GCallback enqueue_func) +{ + GtkWidget *win, *vbox, *bbox, *enqueue, *ok, *cancel, *combo, *entry, + *label; + GList *url; + + win = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(win), _("Add/Open URL Dialog")); + 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); + + label = gtk_label_new(caption); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0); + + 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(ok_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); + + ok = gtk_button_new_from_stock(GTK_STOCK_OPEN); + g_signal_connect(ok, "clicked", + G_CALLBACK(util_add_url_callback), entry); + g_signal_connect(ok, "clicked", + G_CALLBACK(ok_func), entry); + g_signal_connect_swapped(ok, "clicked", + G_CALLBACK(gtk_widget_destroy), + win); + gtk_box_pack_start(GTK_BOX(bbox), ok, 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; + Playlist *playlist = playlist_get_active(); + + 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(playlist, (const gchar *) cur->data); + } else { + playlist_add(playlist, (const gchar *) cur->data); + } + + if (++ctr == 20) { + playlistwin_update_list(playlist); + ctr = 0; + while (gtk_events_pending() ) gtk_main_iteration(); + } + } + + playlistwin_update_list(playlist); + + if (GTK_IS_WIDGET(mainwin_jtf)) + gtk_widget_set_sensitive(mainwin_jtf, TRUE); + + ptr = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(browser)); + + g_free(cfg.filesel_path); + cfg.filesel_path = ptr; +} + +static void +filebrowser_add(GtkFileChooser *browser) +{ + GSList *files; + + files = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(browser)); + + 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; + + files = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(browser)); + + if (!files) return; + + playlist_clear(playlist_get_active()); + + filebrowser_add_files(browser, files); + g_slist_foreach(files, (GFunc) g_free, NULL); + g_slist_free(files); + + playback_initiate(); +} + + +static void +_filebrowser_add_gtk2(GtkWidget *widget, + gpointer data) +{ + filebrowser_add(data); + gtk_file_chooser_unselect_all(GTK_FILE_CHOOSER(data)); +} + +static void +_filebrowser_play_gtk2(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_gtk2style(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); + + if (cfg.filesel_path) + gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(chooser), + cfg.filesel_path); + + 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 (cfg.refresh_file_list) + { + gchar *tmp = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(chooser)); + gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(chooser), tmp); + + g_free(tmp); + } + } + + if (play_button) { + 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_gtk2), 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_gtk2), chooser); + handlerid_do_activate = g_signal_connect_after(chooser,"file_activated", G_CALLBACK(_filebrowser_do_hide_open), dialog); + } + else { + 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_gtk2), 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_gtk2), chooser); + handlerid_do_activate = g_signal_connect_after(chooser,"file_activated", G_CALLBACK(_filebrowser_do_hide_add), dialog); + } + + gtk_window_present(GTK_WINDOW(dialog)); +} + +/* + * Derived from Beep Media Player 0.9.6.1. + * Which is (C) 2003 - 2006 Milosz Derezynski &c + * + * Although I changed it quite a bit. -nenolod + */ +static void filebrowser_changed_classic(GtkFileSelection * filesel) +{ + GList *list; + GList *node; + char *filename = (char *) + gtk_file_selection_get_filename(GTK_FILE_SELECTION(filesel)); + GtkListStore *store; + GtkTreeIter iter; + + if ((list = input_scan_dir(filename)) != NULL) { + /* + * We enter a directory that has been "hijacked" by an + * input-plugin. This is used by the CDDA plugin + */ + store = + GTK_LIST_STORE(gtk_tree_view_get_model + (GTK_TREE_VIEW(filesel->file_list))); + gtk_list_store_clear(store); + + node = list; + while (node) { + + gtk_list_store_append(store, &iter); + gtk_list_store_set(store, &iter, 0, node->data, -1); + g_free(node->data); + node = g_list_next(node); + } + g_list_free(list); + } +} + +static void filebrowser_entry_changed_classic(GtkEditable * entry, gpointer data) +{ + filebrowser_changed_classic(GTK_FILE_SELECTION(data)); +} + +gboolean util_filebrowser_is_dir_classic(GtkFileSelection * filesel) +{ + char *text; + struct stat buf; + gboolean retv = FALSE; + + text = g_strdup(gtk_file_selection_get_filename(filesel)); + + if (stat(text, &buf) == 0 && S_ISDIR(buf.st_mode)) { + /* Selected directory */ + int len = strlen(text); + if (len > 3 && !strcmp(text + len - 4, "/../")) { + if (len == 4) + /* At the root already */ + *(text + len - 3) = '\0'; + else { + char *ptr; + *(text + len - 4) = '\0'; + ptr = strrchr(text, '/'); + *(ptr + 1) = '\0'; + } + } else if (len > 2 && !strcmp(text + len - 3, "/./")) + *(text + len - 2) = '\0'; + gtk_file_selection_set_filename(filesel, text); + retv = TRUE; + } + g_free(text); + return retv; +} + +static void filebrowser_add_files_classic(gchar ** files, + GtkFileSelection * filesel) +{ + int ctr = 0; + char *ptr; + Playlist *playlist = playlist_get_active(); + + if (GTK_IS_WIDGET(mainwin_jtf)) + gtk_widget_set_sensitive(mainwin_jtf, FALSE); + + while (files[ctr] != NULL) { + playlist_add(playlist, files[ctr++]); + } + playlistwin_update_list(playlist); + + if (GTK_IS_WIDGET(mainwin_jtf)) + gtk_widget_set_sensitive(mainwin_jtf, TRUE); + + gtk_label_get(GTK_LABEL(GTK_BIN(filesel->history_pulldown)->child), + &ptr); + + /* This will give an extra slash if the current dir is the root. */ + cfg.filesel_path = g_strconcat(ptr, "/", NULL); +} + +static void filebrowser_ok_classic(GtkWidget * w, GtkWidget * filesel) +{ + gchar **files; + + if (util_filebrowser_is_dir_classic(GTK_FILE_SELECTION(filesel))) + return; + files = gtk_file_selection_get_selections(GTK_FILE_SELECTION(filesel)); + filebrowser_add_files_classic(files, GTK_FILE_SELECTION(filesel)); + gtk_widget_destroy(filesel); +} + +static void filebrowser_play_classic(GtkWidget * w, GtkWidget * filesel) +{ + gchar **files; + + if (util_filebrowser_is_dir_classic + (GTK_FILE_SELECTION(GTK_FILE_SELECTION(filesel)))) + return; + playlist_clear(playlist_get_active()); + files = gtk_file_selection_get_selections(GTK_FILE_SELECTION(filesel)); + filebrowser_add_files_classic(files, GTK_FILE_SELECTION(filesel)); + gtk_widget_destroy(filesel); + playback_initiate(); +} + +static void filebrowser_add_selected_files_classic(GtkWidget * w, gpointer data) +{ + gchar **files; + + GtkFileSelection *filesel = GTK_FILE_SELECTION(data); + files = gtk_file_selection_get_selections(filesel); + + filebrowser_add_files_classic(files, filesel); + gtk_tree_selection_unselect_all(gtk_tree_view_get_selection + (GTK_TREE_VIEW(filesel->file_list))); + + gtk_entry_set_text(GTK_ENTRY(filesel->selection_entry), ""); +} + +static void filebrowser_add_all_files_classic(GtkWidget * w, gpointer data) +{ + gchar **files; + GtkFileSelection *filesel; + + filesel = data; + gtk_tree_selection_select_all(gtk_tree_view_get_selection + (GTK_TREE_VIEW(filesel->file_list))); + files = gtk_file_selection_get_selections(filesel); + filebrowser_add_files_classic(files, filesel); + gtk_tree_selection_unselect_all(gtk_tree_view_get_selection + (GTK_TREE_VIEW(filesel->file_list))); + gtk_entry_set_text(GTK_ENTRY(filesel->selection_entry), ""); +} + +void +util_run_filebrowser_classic(gboolean play_button) +{ + static GtkWidget *dialog; + GtkWidget *button_add_selected, *button_add_all, *button_close, + *button_add; + char *title; + + if (dialog != NULL) { + gtk_window_present(GTK_WINDOW(dialog)); + return; + } + + if (play_button) + title = _("Play files"); + else + title = _("Load files"); + + dialog = gtk_file_selection_new(title); + + gtk_file_selection_set_select_multiple + (GTK_FILE_SELECTION(dialog), TRUE); + + if (cfg.filesel_path) + gtk_file_selection_set_filename(GTK_FILE_SELECTION(dialog), + cfg.filesel_path); + + gtk_file_selection_hide_fileop_buttons(GTK_FILE_SELECTION(dialog)); + gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER); + + gtk_widget_hide(GTK_FILE_SELECTION(dialog)->ok_button); + gtk_widget_destroy(GTK_FILE_SELECTION(dialog)->cancel_button); + + /* + * The mnemonics are quite unorthodox, but that should guarantee they're unique in any locale + * plus kinda easy to use + */ + button_add_selected = + gtk_dialog_add_button(GTK_DIALOG(dialog), "Add selected", + GTK_RESPONSE_NONE); + gtk_button_set_use_underline(GTK_BUTTON(button_add_selected), TRUE); + g_signal_connect(G_OBJECT(button_add_selected), "clicked", + G_CALLBACK(filebrowser_add_selected_files_classic), dialog); + + button_add_all = + gtk_dialog_add_button(GTK_DIALOG(dialog), "Add all", + GTK_RESPONSE_NONE); + gtk_button_set_use_underline(GTK_BUTTON(button_add_all), TRUE); + g_signal_connect(G_OBJECT(button_add_all), "clicked", + G_CALLBACK(filebrowser_add_all_files_classic), dialog); + + if (play_button) { + button_add = + gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_MEDIA_PLAY, + GTK_RESPONSE_NONE); + gtk_button_set_use_stock(GTK_BUTTON(button_add), TRUE); + g_signal_connect(G_OBJECT(button_add), "clicked", + G_CALLBACK(filebrowser_play_classic), dialog); + g_signal_connect(G_OBJECT(GTK_FILE_SELECTION(dialog)->ok_button), + "clicked", G_CALLBACK(filebrowser_play_classic), dialog); + } else { + button_add = + gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_ADD, + GTK_RESPONSE_NONE); + gtk_button_set_use_stock(GTK_BUTTON(button_add), TRUE); + g_signal_connect(G_OBJECT(button_add), "clicked", + G_CALLBACK(filebrowser_ok_classic), dialog); + g_signal_connect(G_OBJECT(GTK_FILE_SELECTION(dialog)->ok_button), + "clicked", G_CALLBACK(filebrowser_ok_classic), dialog); + } + + button_close = + gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_CLOSE, + GTK_RESPONSE_NONE); + gtk_button_set_use_stock(GTK_BUTTON(button_close), TRUE); + g_signal_connect_swapped(G_OBJECT(button_close), "clicked", + G_CALLBACK(gtk_widget_destroy), + G_OBJECT(dialog)); + + gtk_widget_set_size_request(dialog, 600, 450); + gtk_widget_realize(dialog); + + g_signal_connect(G_OBJECT + (GTK_FILE_SELECTION(dialog)->history_pulldown), + "changed", G_CALLBACK(filebrowser_entry_changed_classic), + dialog); + + g_signal_connect(G_OBJECT(dialog), "destroy", + G_CALLBACK(gtk_widget_destroyed), &dialog); + + filebrowser_changed_classic(GTK_FILE_SELECTION(dialog)); + + gtk_widget_show(dialog); +} + +/* + * util_run_filebrowser(gboolean play_button) + * + * Inputs: + * - whether or not a play button should be used + * + * Outputs: + * - none + * + * Side Effects: + * - either a GTK1 or a GTK2 fileselector is launched + */ +void +util_run_filebrowser(gboolean play_button) +{ + if (!cfg.use_xmms_style_fileselector) + util_run_filebrowser_gtk2style(play_button); + else + util_run_filebrowser_classic(play_button); +} + +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_unref(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; +} + +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, 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); + + 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; +} + +/* + * Resizes a GDK pixmap. + */ +GdkPixmap *audacious_pixmap_resize(GdkWindow *src, GdkGC *src_gc, GdkPixmap *in, gint width, gint height) +{ + GdkPixmap *out; + gint owidth, oheight; + + g_return_val_if_fail(src != NULL, NULL); + g_return_val_if_fail(src_gc != NULL, NULL); + g_return_val_if_fail(in != NULL, NULL); + g_return_val_if_fail(width > 0 && height > 0, NULL); + + gdk_drawable_get_size(in, &owidth, &oheight); + + if (oheight == height && owidth == width) + return NULL; + + out = gdk_pixmap_new(src, width, height, -1); + + gdk_draw_rectangle(out, src_gc, TRUE, 0, 0, width, height); + + gdk_window_copy_area(out, src_gc, 0, 0, in, 0, 0, owidth, oheight); + g_object_unref(src); + + return out; +} + +GdkImage *create_dblsize_image(GdkImage * img) +{ + GdkImage *dblimg; + register guint x, y; + + /* + * This needs to be optimized + */ + + dblimg = + gdk_image_new(GDK_IMAGE_NORMAL, gdk_visual_get_system(), + img->width << 1, img->height << 1); + if (dblimg->bpp == 1) { + register guint8 *srcptr, *ptr, *ptr2, pix; + + srcptr = GDK_IMAGE_XIMAGE(img)->data; + ptr = GDK_IMAGE_XIMAGE(dblimg)->data; + ptr2 = ptr + dblimg->bpl; + + for (y = 0; y < img->height; y++) { + for (x = 0; x < img->width; x++) { + pix = *srcptr++; + *ptr++ = pix; + *ptr++ = pix; + *ptr2++ = pix; + *ptr2++ = pix; + } + srcptr += img->bpl - img->width; + ptr += (dblimg->bpl << 1) - dblimg->width; + ptr2 += (dblimg->bpl << 1) - dblimg->width; + } + } + if (dblimg->bpp == 2) { + guint16 *srcptr, *ptr, *ptr2, pix; + + srcptr = (guint16 *) GDK_IMAGE_XIMAGE(img)->data; + ptr = (guint16 *) GDK_IMAGE_XIMAGE(dblimg)->data; + ptr2 = ptr + (dblimg->bpl >> 1); + + for (y = 0; y < img->height; y++) { + for (x = 0; x < img->width; x++) { + pix = *srcptr++; + *ptr++ = pix; + *ptr++ = pix; + *ptr2++ = pix; + *ptr2++ = pix; + } + srcptr += (img->bpl >> 1) - img->width; + ptr += (dblimg->bpl) - dblimg->width; + ptr2 += (dblimg->bpl) - dblimg->width; + } + } + if (dblimg->bpp == 3) { + register guint8 *srcptr, *ptr, *ptr2, pix1, pix2, pix3; + + srcptr = GDK_IMAGE_XIMAGE(img)->data; + ptr = GDK_IMAGE_XIMAGE(dblimg)->data; + ptr2 = ptr + dblimg->bpl; + + for (y = 0; y < img->height; y++) { + for (x = 0; x < img->width; x++) { + pix1 = *srcptr++; + pix2 = *srcptr++; + pix3 = *srcptr++; + *ptr++ = pix1; + *ptr++ = pix2; + *ptr++ = pix3; + *ptr++ = pix1; + *ptr++ = pix2; + *ptr++ = pix3; + *ptr2++ = pix1; + *ptr2++ = pix2; + *ptr2++ = pix3; + *ptr2++ = pix1; + *ptr2++ = pix2; + *ptr2++ = pix3; + + } + srcptr += img->bpl - (img->width * 3); + ptr += (dblimg->bpl << 1) - (dblimg->width * 3); + ptr2 += (dblimg->bpl << 1) - (dblimg->width * 3); + } + } + if (dblimg->bpp == 4) { + register guint32 *srcptr, *ptr, *ptr2, pix; + + srcptr = (guint32 *) GDK_IMAGE_XIMAGE(img)->data; + ptr = (guint32 *) GDK_IMAGE_XIMAGE(dblimg)->data; + ptr2 = ptr + (dblimg->bpl >> 2); + + for (y = 0; y < img->height; y++) { + for (x = 0; x < img->width; x++) { + pix = *srcptr++; + *ptr++ = pix; + *ptr++ = pix; + *ptr2++ = pix; + *ptr2++ = pix; + } + srcptr += (img->bpl >> 2) - img->width; + ptr += (dblimg->bpl >> 1) - dblimg->width; + ptr2 += (dblimg->bpl >> 1) - dblimg->width; + } + } + return dblimg; +} + +/* URL-decode a file: URL path, return NULL if it's not what we expect */ +gchar * +xmms_urldecode_path(const gchar * encoded_path) +{ + const gchar *cur, *ext; + gchar *path, *tmp; + gint realchar; + + if (!encoded_path) + return NULL; + + if (!str_has_prefix_nocase(encoded_path, "file:")) + return NULL; + + cur = encoded_path + 5; + + if (str_has_prefix_nocase(cur, "//localhost")) + cur += 11; + + if (*cur == '/') + while (cur[1] == '/') + cur++; + + tmp = g_malloc0(strlen(cur) + 1); + + while ((ext = strchr(cur, '%')) != NULL) { + strncat(tmp, cur, ext - cur); + ext++; + cur = ext + 2; + if (!sscanf(ext, "%2x", &realchar)) { + /* Assume it is a literal '%'. Several file + * managers send unencoded file: urls on drag + * and drop. */ + realchar = '%'; + cur -= 2; + } + tmp[strlen(tmp)] = realchar; + } + + path = g_strconcat(tmp, cur, NULL); + g_free(tmp); + return path; +} +