Mercurial > audlegacy
view audacious/util.c @ 2209:ec7b60e91a14 trunk
[svn] - more work done on playlist manager: playlist renaming and keyboard shortcuts
author | giacomo |
---|---|
date | Sun, 24 Dec 2006 07:37:57 -0800 |
parents | 7d40f0a290b9 |
children | 165a62fdb49e |
line wrap: on
line source
/* 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; 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)); } 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 ok_func, GCallback enqueue_func) { GtkWidget *win, *vbox, *bbox, *enqueue, *ok, *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(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); bmp_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); bmp_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; } 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_); } /* * 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; }