comparison audacious/util.c @ 0:cb178e5ad177 trunk

[svn] Import audacious source.
author nenolod
date Mon, 24 Oct 2005 03:06:47 -0700
parents
children 2d8234ea45e8
comparison
equal deleted inserted replaced
-1:000000000000 0:cb178e5ad177
1 /* BMP - Cross-platform multimedia player
2 * Copyright (C) 2003-2004 BMP development team.
3 *
4 * Based on XMMS:
5 * Copyright (C) 1998-2003 XMMS development team.
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 */
21
22 #ifdef HAVE_CONFIG_H
23 # include "config.h"
24 #endif
25
26 #define NEED_GLADE
27 #include "util.h"
28
29 #include <glib.h>
30 #include <glib/gi18n.h>
31 #include <glade/glade.h>
32 #include <gtk/gtk.h>
33 #include <gdk/gdk.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <ctype.h>
38
39 #include <gdk/gdkx.h>
40 #include <gdk/gdkkeysyms.h>
41 #include <X11/Xlib.h>
42 #include <sys/ipc.h>
43 #include <unistd.h>
44 #include <errno.h>
45
46 #ifdef HAVE_FTS_H
47 # include <fts.h>
48 #endif
49
50 #include "glade.h"
51 #include "input.h"
52 #include "main.h"
53 #include "playback.h"
54 #include "playlist.h"
55 #include "playlistwin.h"
56
57
58 static GQuark quark_popup_data;
59
60
61 /*
62 * escape_shell_chars()
63 *
64 * Escapes characters that are special to the shell inside double quotes.
65 */
66
67 gchar *
68 escape_shell_chars(const gchar * string)
69 {
70 const gchar *special = "$`\"\\"; /* Characters to escape */
71 const gchar *in = string;
72 gchar *out, *escaped;
73 gint num = 0;
74
75 while (*in != '\0')
76 if (strchr(special, *in++))
77 num++;
78
79 escaped = g_malloc(strlen(string) + num + 1);
80
81 in = string;
82 out = escaped;
83
84 while (*in != '\0') {
85 if (strchr(special, *in))
86 *out++ = '\\';
87 *out++ = *in++;
88 }
89 *out = '\0';
90
91 return escaped;
92 }
93
94
95 /*
96 * find <file> in directory <dirname> or subdirectories. return
97 * pointer to complete filename which has to be freed by calling
98 * "g_free()" after use. Returns NULL if file could not be found.
99 */
100
101 typedef struct {
102 const gchar *to_match;
103 gchar *match;
104 gboolean found;
105 } FindFileContext;
106
107 static gboolean
108 find_file_func(const gchar * path, const gchar * basename, gpointer data)
109 {
110 FindFileContext *context = data;
111
112 if (strlen(path) > FILENAME_MAX) {
113 g_warning("Ignoring path: name too long (%s)", path);
114 return TRUE;
115 }
116
117 if (g_file_test(path, G_FILE_TEST_IS_REGULAR)) {
118 if (!strcasecmp(basename, context->to_match)) {
119 context->match = g_strdup(path);
120 context->found = TRUE;
121 return TRUE;
122 }
123 }
124 else if (g_file_test(path, G_FILE_TEST_IS_DIR)) {
125 dir_foreach(path, find_file_func, context, NULL);
126 if (context->found)
127 return TRUE;
128 }
129
130 return FALSE;
131 }
132
133 gchar *
134 find_file_recursively(const gchar * path, const gchar * filename)
135 {
136 FindFileContext context;
137
138 context.to_match = filename;
139 context.match = NULL;
140 context.found = FALSE;
141
142 dir_foreach(path, find_file_func, &context, NULL);
143 return context.match;
144 }
145
146
147 typedef enum {
148 ARCHIVE_UNKNOWN = 0,
149 ARCHIVE_DIR,
150 ARCHIVE_TAR,
151 ARCHIVE_TGZ,
152 ARCHIVE_ZIP,
153 ARCHIVE_TBZ2
154 } ArchiveType;
155
156 typedef gchar *(*ArchiveExtractFunc) (const gchar *, const gchar *);
157
158 typedef struct {
159 ArchiveType type;
160 const gchar *ext;
161 } ArchiveExtensionType;
162
163 static ArchiveExtensionType archive_extensions[] = {
164 {ARCHIVE_TAR, ".tar"},
165 {ARCHIVE_ZIP, ".wsz"},
166 {ARCHIVE_ZIP, ".zip"},
167 {ARCHIVE_TGZ, ".tar.gz"},
168 {ARCHIVE_TGZ, ".tgz"},
169 {ARCHIVE_TBZ2, ".tar.bz2"},
170 {ARCHIVE_TBZ2, ".bz2"},
171 {ARCHIVE_UNKNOWN, NULL}
172 };
173
174 static gchar *archive_extract_tar(const gchar * archive, const gchar * dest);
175 static gchar *archive_extract_zip(const gchar * archive, const gchar * dest);
176 static gchar *archive_extract_tgz(const gchar * archive, const gchar * dest);
177 static gchar *archive_extract_tbz2(const gchar * archive, const gchar * dest);
178
179 static ArchiveExtractFunc archive_extract_funcs[] = {
180 NULL,
181 NULL,
182 archive_extract_tar,
183 archive_extract_tgz,
184 archive_extract_zip,
185 archive_extract_tbz2
186 };
187
188
189 /* FIXME: these functions can be generalised into a function using a
190 * command lookup table */
191
192 static const gchar *
193 get_tar_command(void)
194 {
195 static const gchar *command = NULL;
196
197 if (!command) {
198 if (!(command = getenv("TARCMD")))
199 command = "tar";
200 }
201
202 return command;
203 }
204
205 static const gchar *
206 get_unzip_command(void)
207 {
208 static const gchar *command = NULL;
209
210 if (!command) {
211 if (!(command = getenv("UNZIPCMD")))
212 command = "unzip";
213 }
214
215 return command;
216 }
217
218
219 static gchar *
220 archive_extract_tar(const gchar * archive, const gchar * dest)
221 {
222 return g_strdup_printf("%s >/dev/null xf \"%s\" -C %s",
223 get_tar_command(), archive, dest);
224 }
225
226 static gchar *
227 archive_extract_zip(const gchar * archive, const gchar * dest)
228 {
229 return g_strdup_printf("%s >/dev/null -o -j \"%s\" -d %s",
230 get_unzip_command(), archive, dest);
231 }
232
233 static gchar *
234 archive_extract_tgz(const gchar * archive, const gchar * dest)
235 {
236 return g_strdup_printf("%s >/dev/null xzf \"%s\" -C %s",
237 get_tar_command(), archive, dest);
238 }
239
240 static gchar *
241 archive_extract_tbz2(const gchar * archive, const gchar * dest)
242 {
243 return g_strdup_printf("bzip2 -dc \"%s\" | %s >/dev/null xf - -C %s",
244 archive, get_tar_command(), dest);
245 }
246
247
248 ArchiveType
249 archive_get_type(const gchar * filename)
250 {
251 gint i = 0;
252
253 if (g_file_test(filename, G_FILE_TEST_IS_DIR))
254 return ARCHIVE_DIR;
255
256 while (archive_extensions[i].ext) {
257 if (g_str_has_suffix(filename, archive_extensions[i].ext)) {
258 return archive_extensions[i].type;
259 }
260 i++;
261 }
262
263 return ARCHIVE_UNKNOWN;
264 }
265
266 gboolean
267 file_is_archive(const gchar * filename)
268 {
269 return (archive_get_type(filename) > ARCHIVE_DIR);
270 }
271
272 gchar *
273 archive_basename(const gchar * str)
274 {
275 gint i = 0;
276
277 while (archive_extensions[i].ext) {
278 if (str_has_suffix_nocase(str, archive_extensions[i].ext)) {
279 const gchar *end = g_strrstr(str, archive_extensions[i].ext);
280 if (end) {
281 return g_strndup(str, end - str);
282 }
283 break;
284 }
285 i++;
286 }
287
288 return NULL;
289 }
290
291 /*
292 decompress_archive
293
294 Decompresses the archive "filename" to a temporary directory,
295 returns the path to the temp dir, or NULL if failed,
296 watch out tho, doesn't actually check if the system command succeeds :-|
297 */
298
299 gchar *
300 archive_decompress(const gchar * filename)
301 {
302 gchar *tmpdir, *cmd, *escaped_filename;
303 ArchiveType type;
304
305 if ((type = archive_get_type(filename)) <= ARCHIVE_DIR)
306 return NULL;
307
308 tmpdir = g_build_filename(g_get_tmp_dir(), "bmp.XXXXXXXX", NULL);
309 if (!mkdtemp(tmpdir)) {
310 g_free(tmpdir);
311 g_message("Unable to load skin: Failed to create temporary "
312 "directory: %s", g_strerror(errno));
313 return NULL;
314 }
315
316 escaped_filename = escape_shell_chars(filename);
317 cmd = archive_extract_funcs[type] (escaped_filename, tmpdir);
318 g_free(escaped_filename);
319
320 if (!cmd) {
321 g_message("extraction function is NULL!");
322 g_free(tmpdir);
323 return NULL;
324 }
325
326 system(cmd);
327 g_free(cmd);
328
329 return tmpdir;
330 }
331
332
333 #ifdef HAVE_FTS_H
334
335 void
336 del_directory(const gchar * dirname)
337 {
338 gchar *const argv[2] = { (gchar *) dirname, NULL };
339 FTS *fts;
340 FTSENT *p;
341
342 fts = fts_open(argv, FTS_PHYSICAL, (gint(*)())NULL);
343 while ((p = fts_read(fts))) {
344 switch (p->fts_info) {
345 case FTS_D:
346 break;
347 case FTS_DNR:
348 case FTS_ERR:
349 break;
350 case FTS_DP:
351 rmdir(p->fts_accpath);
352 break;
353 default:
354 unlink(p->fts_accpath);
355 break;
356 }
357 }
358 fts_close(fts);
359 }
360
361 #else /* !HAVE_FTS */
362
363 gboolean
364 del_directory_func(const gchar * path, const gchar * basename,
365 gpointer params)
366 {
367 if (!strcmp(basename, ".") || !strcmp(path, ".."))
368 return FALSE;
369
370 if (g_file_test(path, G_FILE_TEST_IS_DIR)) {
371 dir_foreach(path, del_directory_func, NULL, NULL);
372 rmdir(path);
373 return FALSE;
374 }
375
376 unlink(path);
377
378 return FALSE;
379 }
380
381 void
382 del_directory(const gchar * path)
383 {
384 dir_foreach(path, del_directory_func, NULL, NULL);
385 rmdir(path);
386 }
387
388 #endif /* ifdef HAVE_FTS */
389
390 gchar *
391 read_ini_string(const gchar * filename, const gchar * section,
392 const gchar * key)
393 {
394 gchar *buffer, *ret_buffer = NULL;
395 gint found_section = 0, off = 0, len = 0;
396 gsize filesize;
397
398 if (!filename)
399 return NULL;
400
401 if (!g_file_get_contents(filename, &buffer, &filesize, NULL))
402 return NULL;
403
404 while (!ret_buffer && off < filesize) {
405 while (off < filesize &&
406 (buffer[off] == '\r' || buffer[off] == '\n' ||
407 buffer[off] == ' ' || buffer[off] == '\t'))
408 off++;
409 if (off >= filesize)
410 break;
411 if (buffer[off] == '[') {
412 gint slen = strlen(section);
413 off++;
414 found_section = 0;
415 if (off + slen + 1 < filesize &&
416 !strncasecmp(section, &buffer[off], slen)) {
417 off += slen;
418 if (buffer[off] == ']') {
419 off++;
420 found_section = 1;
421 }
422 }
423 }
424 else if (found_section && off + strlen(key) < filesize &&
425 !strncasecmp(key, &buffer[off], strlen(key))) {
426 off += strlen(key);
427 while (off < filesize &&
428 (buffer[off] == ' ' || buffer[off] == '\t'))
429 off++;
430 if (off >= filesize)
431 break;
432 if (buffer[off] == '=') {
433 off++;
434 while (off < filesize &&
435 (buffer[off] == ' ' || buffer[off] == '\t'))
436 off++;
437 if (off >= filesize)
438 break;
439 len = 0;
440 while (off + len < filesize &&
441 buffer[off + len] != '\r' &&
442 buffer[off + len] != '\n' && buffer[off + len] != ';')
443 len++;
444 ret_buffer = g_strndup(&buffer[off], len);
445 off += len;
446 }
447 }
448 while (off < filesize && buffer[off] != '\r' && buffer[off] != '\n')
449 off++;
450 }
451
452 g_free(buffer);
453 return ret_buffer;
454 }
455
456 GArray *
457 string_to_garray(const gchar * str)
458 {
459 GArray *array;
460 gint temp;
461 const gchar *ptr = str;
462 gchar *endptr;
463
464 array = g_array_new(FALSE, TRUE, sizeof(gint));
465 for (;;) {
466 temp = strtol(ptr, &endptr, 10);
467 if (ptr == endptr)
468 break;
469 g_array_append_val(array, temp);
470 ptr = endptr;
471 while (!isdigit(*ptr) && (*ptr) != '\0')
472 ptr++;
473 if (*ptr == '\0')
474 break;
475 }
476 return (array);
477 }
478
479 GArray *
480 read_ini_array(const gchar * filename, const gchar * section,
481 const gchar * key)
482 {
483 gchar *temp;
484 GArray *a;
485
486 if ((temp = read_ini_string(filename, section, key)) == NULL) {
487 g_free(temp);
488 return NULL;
489 }
490 a = string_to_garray(temp);
491 g_free(temp);
492 return a;
493 }
494
495 void
496 glist_movedown(GList * list)
497 {
498 gpointer temp;
499
500 if (g_list_next(list)) {
501 temp = list->data;
502 list->data = list->next->data;
503 list->next->data = temp;
504 }
505 }
506
507 void
508 glist_moveup(GList * list)
509 {
510 gpointer temp;
511
512 if (g_list_previous(list)) {
513 temp = list->data;
514 list->data = list->prev->data;
515 list->prev->data = temp;
516 }
517 }
518
519
520 void
521 util_menu_position(GtkMenu * menu, gint * x, gint * y,
522 gboolean * push_in, gpointer data)
523 {
524 GtkRequisition requisition;
525 gint screen_width;
526 gint screen_height;
527 MenuPos *pos = data;
528
529 gtk_widget_size_request(GTK_WIDGET(menu), &requisition);
530
531 screen_width = gdk_screen_width();
532 screen_height = gdk_screen_height();
533
534 *x = CLAMP(pos->x - 2, 0, MAX(0, screen_width - requisition.width));
535 *y = CLAMP(pos->y - 2, 0, MAX(0, screen_height - requisition.height));
536 }
537
538 static void
539 util_menu_delete_popup_data(GtkObject * object, GtkItemFactory * ifactory)
540 {
541 gtk_signal_disconnect_by_func(object,
542 GTK_SIGNAL_FUNC
543 (util_menu_delete_popup_data), ifactory);
544 gtk_object_remove_data_by_id(GTK_OBJECT(ifactory), quark_popup_data);
545 }
546
547
548 /*
549 * util_item_factory_popup[_with_data]() is a replacement for
550 * gtk_item_factory_popup[_with_data]().
551 *
552 * The difference is that the menu is always popped up whithin the
553 * screen. This means it does not neccesarily pop up at (x,y).
554 */
555
556 void
557 util_item_factory_popup_with_data(GtkItemFactory * ifactory,
558 gpointer data,
559 GtkDestroyNotify destroy, guint x,
560 guint y, guint mouse_button, guint32 time)
561 {
562 static GQuark quark_user_menu_pos = 0;
563 MenuPos *pos;
564
565 if (!quark_user_menu_pos)
566 quark_user_menu_pos = g_quark_from_static_string("user_menu_pos");
567
568 if (!quark_popup_data)
569 quark_popup_data =
570 g_quark_from_static_string("GtkItemFactory-popup-data");
571
572 pos = g_object_get_qdata(G_OBJECT(ifactory), quark_user_menu_pos);
573 if (!pos) {
574 pos = g_new0(MenuPos, 1);
575
576 g_object_set_qdata_full(G_OBJECT(ifactory->widget),
577 quark_user_menu_pos, pos, g_free);
578 }
579 pos->x = x;
580 pos->y = y;
581
582
583 if (data != NULL) {
584 g_object_set_qdata_full(G_OBJECT(ifactory),
585 quark_popup_data, data, destroy);
586 g_signal_connect(G_OBJECT(ifactory->widget),
587 "selection-done",
588 G_CALLBACK(util_menu_delete_popup_data), ifactory);
589 }
590
591 gtk_menu_popup(GTK_MENU(ifactory->widget), NULL, NULL,
592 (GtkMenuPositionFunc) util_menu_position,
593 pos, mouse_button, time);
594 }
595
596 void
597 util_item_factory_popup(GtkItemFactory * ifactory, guint x, guint y,
598 guint mouse_button, guint32 time)
599 {
600 util_item_factory_popup_with_data(ifactory, NULL, NULL, x, y,
601 mouse_button, time);
602 }
603
604
605 #define URL_HISTORY_MAX_SIZE 30
606
607 static void
608 util_add_url_callback(GtkWidget * widget,
609 GtkEntry * entry)
610 {
611 const gchar *text;
612
613 text = gtk_entry_get_text(entry);
614 if (g_list_find_custom(cfg.url_history, text, (GCompareFunc) strcasecmp))
615 return;
616
617 cfg.url_history = g_list_prepend(cfg.url_history, g_strdup(text));
618
619 while (g_list_length(cfg.url_history) > URL_HISTORY_MAX_SIZE) {
620 GList *node = g_list_last(cfg.url_history);
621 g_free(node->data);
622 cfg.url_history = g_list_delete_link(cfg.url_history, node);
623 }
624 }
625
626 GtkWidget *
627 util_add_url_dialog_new(const gchar * caption, GCallback enqueue_func)
628 {
629 GtkWidget *win, *vbox, *bbox, *enqueue, *cancel, *combo, *entry;
630 GList *url;
631
632 win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
633 gtk_window_set_title(GTK_WINDOW(win), caption);
634 gtk_window_set_type_hint(GTK_WINDOW(win), GDK_WINDOW_TYPE_HINT_DIALOG);
635 gtk_window_set_position(GTK_WINDOW(win), GTK_WIN_POS_CENTER);
636 gtk_window_set_default_size(GTK_WINDOW(win), 400, -1);
637 gtk_container_set_border_width(GTK_CONTAINER(win), 12);
638
639 vbox = gtk_vbox_new(FALSE, 10);
640 gtk_container_add(GTK_CONTAINER(win), vbox);
641
642 combo = gtk_combo_box_entry_new_text();
643 gtk_box_pack_start(GTK_BOX(vbox), combo, FALSE, FALSE, 0);
644
645 entry = gtk_bin_get_child(GTK_BIN(combo));
646 gtk_window_set_focus(GTK_WINDOW(win), entry);
647 gtk_entry_set_text(GTK_ENTRY(entry), "");
648
649 for (url = cfg.url_history; url; url = g_list_next(url))
650 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), (const gchar *) url->data);
651
652 g_signal_connect(entry, "activate",
653 G_CALLBACK(util_add_url_callback),
654 entry);
655 g_signal_connect(entry, "activate",
656 G_CALLBACK(enqueue_func),
657 entry);
658 g_signal_connect_swapped(entry, "activate",
659 G_CALLBACK(gtk_widget_destroy),
660 win);
661
662 bbox = gtk_hbutton_box_new();
663 gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
664 gtk_button_box_set_spacing(GTK_BUTTON_BOX(bbox), 5);
665 gtk_box_pack_start(GTK_BOX(vbox), bbox, FALSE, FALSE, 0);
666
667 enqueue = gtk_button_new_from_stock(GTK_STOCK_ADD);
668 gtk_box_pack_start(GTK_BOX(bbox), enqueue, FALSE, FALSE, 0);
669
670 g_signal_connect(enqueue, "clicked",
671 G_CALLBACK(util_add_url_callback),
672 entry);
673 g_signal_connect(enqueue, "clicked",
674 G_CALLBACK(enqueue_func),
675 entry);
676 g_signal_connect_swapped(enqueue, "clicked",
677 G_CALLBACK(gtk_widget_destroy),
678 win);
679
680 cancel = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
681 gtk_box_pack_start(GTK_BOX(bbox), cancel, FALSE, FALSE, 0);
682
683 g_signal_connect_swapped(cancel, "clicked",
684 G_CALLBACK(gtk_widget_destroy),
685 win);
686
687 gtk_widget_show_all(vbox);
688
689 return win;
690 }
691
692 static void
693 filebrowser_add_files(GtkFileChooser * browser,
694 GSList * files)
695 {
696 GSList *cur;
697 gchar *ptr;
698 guint ctr = 0;
699
700 if (GTK_IS_WIDGET(mainwin_jtf))
701 gtk_widget_set_sensitive(mainwin_jtf, FALSE);
702
703 for (cur = files; cur; cur = g_slist_next(cur)) {
704
705 if (g_file_test(cur->data,G_FILE_TEST_IS_DIR)) {
706 playlist_add_dir((const gchar *) cur->data);
707 } else {
708 playlist_add((const gchar *) cur->data);
709 }
710
711 if (++ctr == 20) {
712 playlistwin_update_list();
713 ctr = 0;
714 while (gtk_events_pending() ) gtk_main_iteration();
715 }
716 }
717
718 playlistwin_update_list();
719
720 if (GTK_IS_WIDGET(mainwin_jtf))
721 gtk_widget_set_sensitive(mainwin_jtf, TRUE);
722
723 #ifdef HAVE_GNOME_VFS
724 ptr = gtk_file_chooser_get_current_folder_uri(GTK_FILE_CHOOSER(browser));
725 #else
726 ptr = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(browser));
727 #endif
728
729 g_free(cfg.filesel_path);
730 cfg.filesel_path = ptr;
731 }
732
733 static void
734 filebrowser_add(GtkFileChooser * browser)
735 {
736 GSList *files;
737
738 #ifdef HAVE_GNOME_VFS
739 files = gtk_file_chooser_get_uris(GTK_FILE_CHOOSER(browser));
740 #else
741 files = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(browser));
742 #endif
743
744 if (!files) {
745 return;
746 }
747
748 filebrowser_add_files(browser, files);
749 g_slist_foreach(files, (GFunc) g_free, NULL);
750 g_slist_free(files);
751 }
752
753 static void
754 filebrowser_play(GtkFileChooser * browser)
755 {
756 GSList *files;
757
758 #ifdef HAVE_GNOME_VFS
759 files = gtk_file_chooser_get_uris(GTK_FILE_CHOOSER(browser));
760 #else
761 files = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(browser));
762 #endif
763
764 if (!files) return;
765
766 playlist_clear();
767
768 filebrowser_add_files(browser, files);
769 g_slist_foreach(files, (GFunc) g_free, NULL);
770 g_slist_free(files);
771
772 bmp_playback_initiate();
773 }
774
775
776 static void
777 _filebrowser_add(GtkWidget *widget,
778 gpointer data)
779 {
780 filebrowser_add(data);
781 gtk_file_chooser_unselect_all(GTK_FILE_CHOOSER(data));
782 }
783
784 static void
785 _filebrowser_play(GtkWidget *widget, gpointer data)
786 {
787 filebrowser_play(data);
788 gtk_file_chooser_unselect_all(data);
789 }
790
791 #if 0
792 static void
793 filebrowser_on_response(GtkFileChooser * browser,
794 gint response,
795 gpointer data)
796 {
797 gtk_widget_hide(GTK_WIDGET(browser));
798 switch (response) {
799 case GTK_RESPONSE_OK:
800 break;
801 case GTK_RESPONSE_ACCEPT:
802 break;
803 case GTK_RESPONSE_CLOSE:
804 break;
805 }
806 gtk_widget_destroy(GTK_WIDGET(browser));
807
808 }
809
810 #endif
811
812 static void
813 _filebrowser_check_hide_add(GtkWidget * widget,
814 gpointer data)
815 {
816 cfg.close_dialog_add = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
817 }
818
819 static void
820 _filebrowser_check_hide_open(GtkWidget * widget,
821 gpointer data)
822 {
823 cfg.close_dialog_open = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
824 }
825
826
827
828 static gboolean
829 filebrowser_on_keypress(GtkWidget * browser,
830 GdkEventKey * event,
831 gpointer data)
832 {
833 if (event->keyval == GDK_Escape) {
834 /* FIXME: this crashes BMP for some reason */
835 /* g_signal_emit_by_name(browser, "delete-event"); */
836 gtk_widget_hide(browser);
837 return TRUE;
838 }
839
840 return FALSE;
841 }
842
843 static void
844 _filebrowser_do_hide_add(GtkWidget *widget,
845 gpointer data)
846 {
847 if (cfg.close_dialog_add)
848 gtk_widget_hide(data);
849 }
850
851 static void
852 _filebrowser_do_hide_open(GtkWidget *widget,
853 gpointer data)
854 {
855 if (cfg.close_dialog_open)
856 gtk_widget_hide(data);
857 }
858
859 void
860 util_run_filebrowser(gboolean play_button)
861 {
862 static GladeXML *xml = NULL;
863 static GtkWidget *dialog = NULL;
864 static GtkWidget *chooser = NULL;
865
866 static GtkWidget *button_add;
867 static GtkWidget *button_select_all, *button_deselect_all;
868 static GtkWidget *toggle;
869
870 static gulong handlerid, handlerid_check, handlerid_do;
871 static gulong handlerid_activate, handlerid_do_activate;
872
873 if (!xml) {
874 /* FIXME: Creating a file chooser dialog manually using
875 libglade because there's no way to stop
876 GtkFileChooserDialog from resizing the buttons to the same
877 size. The long toggle button title causes the buttons to
878 turn unnecessarily elongated and fugly. */
879
880 GtkWidget *alignment;
881
882 xml = glade_xml_new_or_die(_("Add/Open Files dialog"),
883 DATA_DIR "/glade/addfiles.glade",
884 NULL, NULL);
885 glade_xml_signal_autoconnect(xml);
886
887 dialog = glade_xml_get_widget(xml, "add_files_dialog");
888
889 /* FIXME: Creating file chooser widget here because libglade <= 2.4.0 does
890 not support GtkFileChooserWidget */
891
892 chooser = gtk_file_chooser_widget_new(GTK_FILE_CHOOSER_ACTION_OPEN);
893 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(chooser), TRUE);
894
895 #ifdef HAVE_GNOME_VFS
896 gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(chooser), FALSE);
897 #endif
898
899 if (cfg.filesel_path)
900 #ifdef HAVE_GNOME_VFS
901 gtk_file_chooser_set_current_folder_uri(GTK_FILE_CHOOSER(chooser),
902 cfg.filesel_path);
903 #else
904 gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(chooser),
905 cfg.filesel_path);
906 #endif
907
908 alignment = glade_xml_get_widget(xml, "alignment2");
909 gtk_container_add(GTK_CONTAINER(alignment), chooser);
910
911 toggle = glade_xml_get_widget(xml, "close_on_action");
912 button_select_all = glade_xml_get_widget(xml, "select_all");
913 button_deselect_all = glade_xml_get_widget(xml, "deselect_all");
914 button_add = glade_xml_get_widget(xml, "action");
915
916 g_signal_connect_swapped(button_select_all, "clicked",
917 G_CALLBACK(gtk_file_chooser_select_all),
918 chooser);
919 g_signal_connect_swapped(button_deselect_all, "clicked",
920 G_CALLBACK(gtk_file_chooser_unselect_all),
921 chooser);
922
923 g_signal_connect(dialog, "key_press_event",
924 G_CALLBACK(filebrowser_on_keypress),
925 NULL);
926
927 gtk_widget_show_all(dialog);
928 } /* !xml */
929 else {
930 g_signal_handler_disconnect(button_add, handlerid);
931 g_signal_handler_disconnect(toggle, handlerid_check);
932 g_signal_handler_disconnect(chooser, handlerid_activate);
933 g_signal_handler_disconnect(button_add, handlerid_do);
934 g_signal_handler_disconnect(chooser, handlerid_do_activate);
935 }
936
937 if (play_button) {
938 cfg.close_dialog_open = TRUE;
939
940 gtk_window_set_title(GTK_WINDOW(dialog), _("Open Files"));
941
942 gtk_button_set_label(GTK_BUTTON(button_add), GTK_STOCK_OPEN);
943
944 gtk_button_set_label(GTK_BUTTON(toggle), _("Close dialog on Open"));
945 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle), cfg.close_dialog_open);
946
947 handlerid = g_signal_connect(button_add, "clicked", G_CALLBACK(_filebrowser_play), chooser);
948 handlerid_check = g_signal_connect(toggle, "toggled", G_CALLBACK(_filebrowser_check_hide_open), NULL);
949 handlerid_do = g_signal_connect_after(button_add, "clicked", G_CALLBACK(_filebrowser_do_hide_open), dialog);
950 handlerid_activate = g_signal_connect(chooser, "file-activated", G_CALLBACK(_filebrowser_play), chooser);
951 handlerid_do_activate = g_signal_connect_after(chooser,"file_activated", G_CALLBACK(_filebrowser_do_hide_open), dialog);
952 }
953 else {
954 cfg.close_dialog_add = TRUE;
955
956 gtk_window_set_title(GTK_WINDOW(dialog), _("Add Files"));
957
958 gtk_button_set_label(GTK_BUTTON(button_add), GTK_STOCK_ADD);
959
960 gtk_button_set_label(GTK_BUTTON(toggle), _("Close dialog on Add"));
961 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle), cfg.close_dialog_add);
962
963 handlerid = g_signal_connect(button_add, "clicked", G_CALLBACK(_filebrowser_add), chooser);
964 handlerid_check = g_signal_connect(toggle, "toggled", G_CALLBACK(_filebrowser_check_hide_add), NULL);
965 handlerid_do = g_signal_connect_after(button_add, "clicked", G_CALLBACK(_filebrowser_do_hide_add), dialog);
966 handlerid_activate = g_signal_connect(chooser, "file-activated", G_CALLBACK(_filebrowser_add), chooser);
967 handlerid_do_activate = g_signal_connect_after(chooser,"file_activated", G_CALLBACK(_filebrowser_do_hide_add), dialog);
968 }
969
970 gtk_window_present(GTK_WINDOW(dialog));
971 }
972
973 GdkFont *
974 util_font_load(const gchar * name)
975 {
976 GdkFont *font;
977 PangoFontDescription *desc;
978
979 desc = pango_font_description_from_string(name);
980 font = gdk_font_from_description(desc);
981
982 return font;
983 }
984
985 #ifdef ENABLE_NLS
986 gchar *
987 bmp_menu_translate(const gchar * path, gpointer func_data)
988 {
989 gchar *translation = gettext(path);
990
991 if (!translation || *translation != '/') {
992 g_warning("Bad translation for menupath: %s", path);
993 translation = (gchar *) path;
994 }
995
996 return translation;
997 }
998 #endif
999
1000 void
1001 util_set_cursor(GtkWidget * window)
1002 {
1003 static GdkCursor *cursor = NULL;
1004
1005 if (!window) {
1006 if (cursor) {
1007 gdk_cursor_destroy(cursor);
1008 cursor = NULL;
1009 }
1010 return;
1011 }
1012
1013 if (!cursor)
1014 cursor = gdk_cursor_new(GDK_LEFT_PTR);
1015
1016 gdk_window_set_cursor(window->window, cursor);
1017 }
1018
1019 /* text_get_extents() taken from The GIMP (C) Spencer Kimball, Peter
1020 * Mattis et al */
1021 gboolean
1022 text_get_extents(const gchar * fontname,
1023 const gchar * text,
1024 gint * width, gint * height, gint * ascent, gint * descent)
1025 {
1026 PangoFontDescription *font_desc;
1027 PangoLayout *layout;
1028 PangoRectangle rect;
1029
1030 g_return_val_if_fail(fontname != NULL, FALSE);
1031 g_return_val_if_fail(text != NULL, FALSE);
1032
1033 /* FIXME: resolution */
1034 layout = gtk_widget_create_pango_layout(GTK_WIDGET(mainwin), text);
1035
1036 font_desc = pango_font_description_from_string(fontname);
1037 pango_layout_set_font_description(layout, font_desc);
1038 pango_font_description_free(font_desc);
1039 pango_layout_get_pixel_extents(layout, NULL, &rect);
1040
1041 if (width)
1042 *width = rect.width;
1043 if (height)
1044 *height = rect.height;
1045
1046 if (ascent || descent) {
1047 PangoLayoutIter *iter;
1048 PangoLayoutLine *line;
1049
1050 iter = pango_layout_get_iter(layout);
1051 line = pango_layout_iter_get_line(iter);
1052 pango_layout_iter_free(iter);
1053
1054 pango_layout_line_get_pixel_extents(line, NULL, &rect);
1055
1056 if (ascent)
1057 *ascent = PANGO_ASCENT(rect);
1058 if (descent)
1059 *descent = -PANGO_DESCENT(rect);
1060 }
1061
1062 g_object_unref(layout);
1063
1064 return TRUE;
1065 }
1066
1067 /* counts number of digits in a gint */
1068 guint
1069 gint_count_digits(gint n)
1070 {
1071 guint count = 0;
1072
1073 n = ABS(n);
1074 do {
1075 count++;
1076 n /= 10;
1077 } while (n > 0);
1078
1079 return count;
1080 }
1081
1082 static gchar *
1083 str_twenty_to_space(gchar * str)
1084 {
1085 gchar *match, *match_end;
1086
1087 g_return_val_if_fail(str != NULL, NULL);
1088
1089 while ((match = strstr(str, "%20"))) {
1090 match_end = match + 3;
1091 *match++ = ' ';
1092 while (*match_end)
1093 *match++ = *match_end++;
1094 *match = 0;
1095 }
1096
1097 return str;
1098 }
1099
1100 static gchar *
1101 str_replace_char(gchar * str, gchar old, gchar new)
1102 {
1103 gchar *match;
1104
1105 g_return_val_if_fail(str != NULL, NULL);
1106
1107 match = str;
1108 while ((match = strchr(match, old)))
1109 *match = new;
1110
1111 return str;
1112 }
1113
1114 gchar *
1115 str_append(gchar * str, const gchar * add_str)
1116 {
1117 return str_replace(str, g_strconcat(str, add_str, NULL));
1118 }
1119
1120 gchar *
1121 str_replace(gchar * str, gchar * new_str)
1122 {
1123 g_free(str);
1124 return new_str;
1125 }
1126
1127 void
1128 str_replace_in(gchar ** str, gchar * new_str)
1129 {
1130 *str = str_replace(*str, new_str);
1131 }
1132
1133
1134 gboolean
1135 str_has_prefix_nocase(const gchar * str, const gchar * prefix)
1136 {
1137 return (strncasecmp(str, prefix, strlen(prefix)) == 0);
1138 }
1139
1140 gboolean
1141 str_has_suffix_nocase(const gchar * str, const gchar * suffix)
1142 {
1143 return (strcasecmp(str + strlen(str) - strlen(suffix), suffix) == 0);
1144 }
1145
1146 gboolean
1147 str_has_suffixes_nocase(const gchar * str, gchar * const *suffixes)
1148 {
1149 gchar *const *suffix;
1150
1151 g_return_val_if_fail(str != NULL, FALSE);
1152 g_return_val_if_fail(suffixes != NULL, FALSE);
1153
1154 for (suffix = suffixes; *suffix; suffix++)
1155 if (str_has_suffix_nocase(str, *suffix))
1156 return TRUE;
1157
1158 return FALSE;
1159 }
1160
1161 gchar *
1162 str_to_utf8_fallback(const gchar * str)
1163 {
1164 gchar *out_str, *convert_str, *chr;
1165
1166 /* NULL in NULL out */
1167 if (!str)
1168 return NULL;
1169
1170 convert_str = g_strdup(str);
1171 for (chr = convert_str; *chr; chr++) {
1172 if (*chr & 0x80)
1173 *chr = '?';
1174 }
1175
1176 out_str = g_strconcat(convert_str, _(" (invalid UTF-8)"), NULL);
1177 g_free(convert_str);
1178
1179 return out_str;
1180 }
1181
1182 gchar *
1183 filename_to_utf8(const gchar * filename)
1184 {
1185 gchar *out_str;
1186
1187 /* NULL in NULL out */
1188 if (!filename)
1189 return NULL;
1190
1191 if ((out_str = g_filename_to_utf8(filename, -1, NULL, NULL, NULL)))
1192 return out_str;
1193
1194 return str_to_utf8_fallback(filename);
1195 }
1196
1197 gchar *
1198 str_to_utf8(const gchar * str)
1199 {
1200 gchar *out_str;
1201
1202 /* NULL in NULL out */
1203 if (!str)
1204 return NULL;
1205
1206 /* already UTF-8? */
1207 if (g_utf8_validate(str, -1, NULL))
1208 return g_strdup(str);
1209
1210 /* assume encoding associated with locale */
1211 if ((out_str = g_locale_to_utf8(str, -1, NULL, NULL, NULL)))
1212 return out_str;
1213
1214 /* all else fails, we mask off character codes >= 128,
1215 replace with '?' */
1216 return str_to_utf8_fallback(str);
1217 }
1218
1219
1220 const gchar *
1221 str_skip_chars(const gchar * str, const gchar * chars)
1222 {
1223 while (strchr(chars, *str))
1224 str++;
1225 return str;
1226 }
1227
1228 gchar *
1229 convert_title_text(gchar * title)
1230 {
1231 g_return_val_if_fail(title != NULL, NULL);
1232
1233 if (cfg.convert_underscore)
1234 str_replace_char(title, '_', ' ');
1235
1236 if (cfg.convert_twenty)
1237 str_twenty_to_space(title);
1238
1239 return title;
1240 }
1241
1242
1243 gboolean
1244 dir_foreach(const gchar * path, DirForeachFunc function,
1245 gpointer user_data, GError ** error)
1246 {
1247 GError *error_out = NULL;
1248 GDir *dir;
1249 const gchar *entry;
1250 gchar *entry_fullpath;
1251
1252 if (!(dir = g_dir_open(path, 0, &error_out))) {
1253 g_propagate_error(error, error_out);
1254 return FALSE;
1255 }
1256
1257 while ((entry = g_dir_read_name(dir))) {
1258 entry_fullpath = g_build_filename(path, entry, NULL);
1259
1260 if ((*function) (entry_fullpath, entry, user_data)) {
1261 g_free(entry_fullpath);
1262 break;
1263 }
1264
1265 g_free(entry_fullpath);
1266 }
1267
1268 g_dir_close(dir);
1269
1270 return TRUE;
1271 }
1272
1273 GtkWidget *
1274 make_filebrowser(const gchar * title,
1275 gboolean save)
1276 {
1277 GtkWidget *dialog;
1278 GtkWidget *button;
1279 GtkWidget *button_close;
1280
1281 g_return_val_if_fail(title != NULL, NULL);
1282
1283 dialog = gtk_file_chooser_dialog_new(title, GTK_WINDOW(mainwin),
1284 GTK_FILE_CHOOSER_ACTION_OPEN, NULL);
1285 if (save)
1286 gtk_file_chooser_set_action(GTK_FILE_CHOOSER(dialog),
1287 GTK_FILE_CHOOSER_ACTION_SAVE);
1288
1289 if (!save)
1290 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
1291
1292 g_signal_connect(dialog, "destroy",
1293 G_CALLBACK(gtk_widget_destroyed), &dialog);
1294
1295 #ifdef HAVE_GNOME_VFS
1296 gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(dialog), FALSE);
1297 #endif
1298
1299 button_close = gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_CANCEL,
1300 GTK_RESPONSE_REJECT);
1301 gtk_button_set_use_stock(GTK_BUTTON(button_close), TRUE);
1302 GTK_WIDGET_SET_FLAGS(button_close, GTK_CAN_DEFAULT);
1303 g_signal_connect_swapped(button_close, "clicked",
1304 G_CALLBACK(gtk_widget_destroy), dialog);
1305
1306 button = gtk_dialog_add_button(GTK_DIALOG(dialog), save ?
1307 GTK_STOCK_SAVE : GTK_STOCK_OPEN,
1308 GTK_RESPONSE_ACCEPT);
1309 gtk_button_set_use_stock(GTK_BUTTON(button), TRUE);
1310 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
1311 gtk_window_set_default(GTK_WINDOW(dialog), button);
1312
1313 gtk_widget_show(dialog);
1314
1315 return dialog;
1316 }
1317
1318
1319 GtkItemFactory *
1320 create_menu(GtkItemFactoryEntry *entries,
1321 guint n_entries,
1322 GtkAccelGroup *accel)
1323 {
1324 GtkItemFactory *menu;
1325
1326 menu = gtk_item_factory_new(GTK_TYPE_MENU, "<Main>", accel);
1327 gtk_item_factory_set_translate_func(menu, bmp_menu_translate, NULL,
1328 NULL);
1329 gtk_item_factory_create_items(menu, n_entries, entries, NULL);
1330
1331 return menu;
1332 }
1333
1334
1335 void
1336 make_submenu(GtkItemFactory *menu,
1337 const gchar *item_path,
1338 GtkItemFactory *submenu)
1339 {
1340 GtkWidget *item, *menu_;
1341
1342 item = gtk_item_factory_get_widget(menu, item_path);
1343 menu_ = gtk_item_factory_get_widget(submenu, "");
1344 gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), menu_);
1345 }
1346
1347
1348
1349
1350
1351
1352