No issues found
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2003 Colin Walters <walters@verbum.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
8 * any later version.
9 *
10 * The Rhythmbox authors hereby grant permission for non-GPL compatible
11 * GStreamer plugins to be used and distributed together with GStreamer
12 * and Rhythmbox. This permission is above and beyond the permissions granted
13 * by the GPL license by which Rhythmbox is covered. If you modify this code
14 * you may extend this exception to your version of the code, but you are not
15 * obligated to do so. If you do not wish to do so, delete this exception
16 * statement from your version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
26 *
27 */
28
29 /**
30 * SECTION:rb-util
31 * @short_description: assorted utility functions
32 *
33 * This is a dumping ground for utility functions that may or may not
34 * be generally useful in Rhythmbox or elsewhere. Things end up here
35 * if they're clever or if they're used all over the place.
36 */
37
38 #include "config.h"
39
40 #include <string.h>
41 #include <stdarg.h>
42 #include <stdlib.h>
43
44 #include <gtk/gtk.h>
45 #include <glib/gi18n.h>
46 #include <gobject/gvaluecollector.h>
47 #include <gio/gio.h>
48
49 #include "rb-util.h"
50 #include "rb-debug.h"
51
52 static GPrivate private_is_primary_thread;
53
54 /**
55 * rb_true_function: (skip):
56 * @dummy: unused
57 *
58 * Just returns %TRUE, useful as a callback function.
59 *
60 * Return value: %TRUE
61 */
62 gboolean
63 rb_true_function (gpointer dummy)
64 {
65 return TRUE;
66 }
67
68 /**
69 * rb_false_function: (skip):
70 * @dummy: unused
71 *
72 * Just returns %FALSE, useful as a callback function.
73 *
74 * Return value: %FALSE
75 */
76 gboolean
77 rb_false_function (gpointer dummy)
78 {
79 return FALSE;
80 }
81
82 /**
83 * rb_null_function: (skip):
84 * @dummy: unused
85 *
86 * Just returns NULL. Useful as a callback function.
87 *
88 * Return value: NULL
89 */
90 gpointer
91 rb_null_function (gpointer dummy)
92 {
93 return NULL;
94 }
95
96 /**
97 * rb_copy_function: (skip):
98 * @data: generic argument
99 *
100 * Just returns its first argument. Useful as a callback function.
101 *
102 * Return value: @data
103 */
104 gpointer
105 rb_copy_function (gpointer data)
106 {
107 return data;
108 }
109
110
111 /**
112 * rb_gvalue_compare: (skip):
113 * @a: left hand side
114 * @b: right hand size
115 *
116 * Compares @a and @b for sorting. @a and @b must contain the same value
117 * type for the comparison to be valid. Comparisons for some value types
118 * are not particularly useful.
119 *
120 * Return value: -1 if @a < @b, 0 if @a == @b, 1 if @a > @b
121 */
122 int
123 rb_gvalue_compare (GValue *a, GValue *b)
124 {
125 int retval;
126 const char *stra, *strb;
127
128 if (G_VALUE_TYPE (a) != G_VALUE_TYPE (b))
129 return -1;
130
131 switch (G_VALUE_TYPE (a))
132 {
133 case G_TYPE_BOOLEAN:
134 if (g_value_get_int (a) < g_value_get_int (b))
135 retval = -1;
136 else if (g_value_get_int (a) == g_value_get_int (b))
137 retval = 0;
138 else
139 retval = 1;
140 break;
141 case G_TYPE_CHAR:
142 if (g_value_get_schar (a) < g_value_get_schar (b))
143 retval = -1;
144 else if (g_value_get_schar (a) == g_value_get_schar (b))
145 retval = 0;
146 else
147 retval = 1;
148 break;
149 case G_TYPE_UCHAR:
150 if (g_value_get_uchar (a) < g_value_get_uchar (b))
151 retval = -1;
152 else if (g_value_get_uchar (a) == g_value_get_uchar (b))
153 retval = 0;
154 else
155 retval = 1;
156 break;
157 case G_TYPE_INT:
158 if (g_value_get_int (a) < g_value_get_int (b))
159 retval = -1;
160 else if (g_value_get_int (a) == g_value_get_int (b))
161 retval = 0;
162 else
163 retval = 1;
164 break;
165 case G_TYPE_UINT:
166 if (g_value_get_uint (a) < g_value_get_uint (b))
167 retval = -1;
168 else if (g_value_get_uint (a) == g_value_get_uint (b))
169 retval = 0;
170 else
171 retval = 1;
172 break;
173 case G_TYPE_LONG:
174 if (g_value_get_long (a) < g_value_get_long (b))
175 retval = -1;
176 else if (g_value_get_long (a) == g_value_get_long (b))
177 retval = 0;
178 else
179 retval = 1;
180 break;
181 case G_TYPE_ULONG:
182 if (g_value_get_ulong (a) < g_value_get_ulong (b))
183 retval = -1;
184 else if (g_value_get_ulong (a) == g_value_get_ulong (b))
185 retval = 0;
186 else
187 retval = 1;
188 break;
189 case G_TYPE_INT64:
190 if (g_value_get_int64 (a) < g_value_get_int64 (b))
191 retval = -1;
192 else if (g_value_get_int64 (a) == g_value_get_int64 (b))
193 retval = 0;
194 else
195 retval = 1;
196 break;
197 case G_TYPE_UINT64:
198 if (g_value_get_uint64 (a) < g_value_get_uint64 (b))
199 retval = -1;
200 else if (g_value_get_uint64 (a) == g_value_get_uint64 (b))
201 retval = 0;
202 else
203 retval = 1;
204 break;
205 case G_TYPE_ENUM:
206 /* this is somewhat bogus. */
207 if (g_value_get_enum (a) < g_value_get_enum (b))
208 retval = -1;
209 else if (g_value_get_enum (a) == g_value_get_enum (b))
210 retval = 0;
211 else
212 retval = 1;
213 break;
214 case G_TYPE_FLAGS:
215 /* this is even more bogus. */
216 if (g_value_get_flags (a) < g_value_get_flags (b))
217 retval = -1;
218 else if (g_value_get_flags (a) == g_value_get_flags (b))
219 retval = 0;
220 else
221 retval = 1;
222 break;
223 case G_TYPE_FLOAT:
224 if (g_value_get_float (a) < g_value_get_float (b))
225 retval = -1;
226 else if (g_value_get_float (a) == g_value_get_float (b))
227 retval = 0;
228 else
229 retval = 1;
230 break;
231 case G_TYPE_DOUBLE:
232 if (g_value_get_double (a) < g_value_get_double (b))
233 retval = -1;
234 else if (g_value_get_double (a) == g_value_get_double (b))
235 retval = 0;
236 else
237 retval = 1;
238 break;
239 case G_TYPE_STRING:
240 stra = g_value_get_string (a);
241 strb = g_value_get_string (b);
242 if (stra == NULL) stra = "";
243 if (strb == NULL) strb = "";
244 retval = g_utf8_collate (stra, strb);
245 break;
246 case G_TYPE_POINTER:
247 retval = (g_value_get_pointer (a) != g_value_get_pointer (b));
248 break;
249 case G_TYPE_BOXED:
250 retval = (g_value_get_boxed (a) != g_value_get_boxed (b));
251 break;
252 case G_TYPE_OBJECT:
253 retval = (g_value_get_object (a) != g_value_get_object (b));
254 break;
255 default:
256 g_assert_not_reached ();
257 retval = 0;
258 break;
259 }
260 return retval;
261 }
262
263 /**
264 * rb_compare_gtimeval:
265 * @a: left hand side
266 * @b: right hand size
267 *
268 * Compares two #GTimeVal structures for sorting.
269 *
270 * Return value: -1 if @a < @b, 0 if @a == @b, 1 if @a > @b
271 */
272 int
273 rb_compare_gtimeval (GTimeVal *a, GTimeVal *b)
274 {
275 if (a->tv_sec == b->tv_sec)
276 /* It's quite unlikely that microseconds are equal,
277 * so just ignore that case, we don't need a lot
278 * of precision.
279 */
280 return a->tv_usec > b->tv_usec ? 1 : -1;
281 else if (a->tv_sec > b->tv_sec)
282 return 1;
283 else
284 return -1;
285 }
286
287 /* this is obsoleted by g_strcmp0, don't use it */
288 int
289 rb_safe_strcmp (const char *a,
290 const char *b)
291 {
292 return (!a && !b) ? 0 : (a && !b) || (!a && b) ? 1 : strcmp (a, b);
293 }
294
295 /* Taken from totem/video-utils.c CVS HEAD 2004-04-22 */
296 static void
297 totem_pixbuf_mirror (GdkPixbuf *pixbuf)
298 {
299 int i, j, rowstride, offset, right;
300 guchar *pixels;
301 int width, height, size;
302 guint32 tmp;
303
304 pixels = gdk_pixbuf_get_pixels (pixbuf);
305 g_return_if_fail (pixels != NULL);
306
307 width = gdk_pixbuf_get_width (pixbuf);
308 height = gdk_pixbuf_get_height (pixbuf);
309 rowstride = gdk_pixbuf_get_rowstride (pixbuf);
310 size = height * width * sizeof (guint32);
311
312 for (i = 0; i < size; i += rowstride)
313 {
314 for (j = 0; j < rowstride; j += sizeof(guint32))
315 {
316 offset = i + j;
317 right = i + (((width - 1) * sizeof(guint32)) - j);
318
319 if (right <= offset)
320 break;
321
322 memcpy (&tmp, pixels + offset, sizeof(guint32));
323 memcpy (pixels + offset, pixels + right,
324 sizeof(guint32));
325 memcpy (pixels + right, &tmp, sizeof(guint32));
326 }
327 }
328 }
329
330
331
332 /**
333 * rb_image_new_from_stock:
334 * @stock_id: stock image id
335 * @size: requested icon size
336 *
337 * Same as @gtk_image_new_from_stock except that it mirrors the icons for RTL
338 * languages.
339 *
340 * Return value: (transfer full): a #GtkImage of the requested stock item
341 */
342 GtkWidget *
343 rb_image_new_from_stock (const gchar *stock_id, GtkIconSize size)
344 {
345 if (gtk_widget_get_default_direction () == GTK_TEXT_DIR_LTR) {
346 return gtk_image_new_from_stock (stock_id, size);
347 } else {
348
349 GtkWidget *image;
350 GdkPixbuf *pixbuf;
351 GdkPixbuf *mirror;
352
353 image = gtk_image_new ();
354
355 if (image == NULL) {
356 return NULL;
357 }
358
359 pixbuf = gtk_widget_render_icon_pixbuf (image, stock_id, size);
360 g_assert (pixbuf != NULL);
361
362
363 mirror = gdk_pixbuf_copy (pixbuf);
364 g_object_unref (pixbuf);
365
366 if (!mirror)
367 return NULL;
368
369 totem_pixbuf_mirror (mirror);
370 gtk_image_set_from_pixbuf (GTK_IMAGE (image), mirror);
371 g_object_unref (mirror);
372
373 return image;
374 }
375
376 return NULL;
377 }
378
379 /**
380 * rb_gtk_action_popup_menu: (skip):
381 * @uimanager: a #GtkUIManager
382 * @path: UI path for the popup to display
383 *
384 * Simple shortcut for getting a popup menu from a #GtkUIManager and
385 * displaying it.
386 */
387 void
388 rb_gtk_action_popup_menu (GtkUIManager *uimanager, const char *path)
389 {
390 GtkWidget *menu;
391
392 menu = gtk_ui_manager_get_widget (uimanager, path);
393 if (menu == NULL) {
394 g_warning ("Couldn't get menu widget for %s", path);
395 } else {
396 gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, 3,
397 gtk_get_current_event_time ());
398 }
399 }
400
401 /**
402 * rb_is_main_thread:
403 *
404 * Checks if currently executing on the main thread.
405 *
406 * Return value: %TRUE if on the main thread
407 */
408 gboolean
409 rb_is_main_thread (void)
410 {
411 if (g_thread_supported()) {
412 return GPOINTER_TO_UINT(g_private_get (&private_is_primary_thread)) == 1;
413 } else {
414 return TRUE;
415 }
416 }
417
418 static gboolean
419 purge_useless_threads (gpointer data)
420 {
421 g_thread_pool_stop_unused_threads ();
422 return TRUE;
423 }
424
425
426 static GRecMutex rb_gdk_mutex;
427 static gboolean mutex_recurses;
428
429 static void
430 _threads_enter (void)
431 {
432 g_rec_mutex_lock (&rb_gdk_mutex);
433 }
434
435 static void
436 _threads_leave (void)
437 {
438 g_rec_mutex_unlock (&rb_gdk_mutex);
439 }
440
441
442 /**
443 * rb_assert_locked: (skip):
444 * @mutex: a #GMutex
445 *
446 * Asserts that @mutex is currently locked. Does not work with all
447 * mutex implementations.
448 */
449 void
450 rb_assert_locked (GMutex *mutex)
451 {
452 if (!mutex_recurses)
453 g_assert (!g_mutex_trylock (mutex));
454 }
455
456 /**
457 * rb_threads_init: (skip):
458 *
459 * Initializes various thread helpers. Must be called on startup.
460 */
461 void
462 rb_threads_init (void)
463 {
464 GMutex m;
465
466 g_private_set (&private_is_primary_thread, GUINT_TO_POINTER (1));
467
468 g_rec_mutex_init (&rb_gdk_mutex);
469 gdk_threads_set_lock_functions (_threads_enter, _threads_leave);
470 gdk_threads_init ();
471
472 g_mutex_init (&m);
473 g_mutex_lock (&m);
474 mutex_recurses = g_mutex_trylock (&m);
475 if (mutex_recurses)
476 g_mutex_unlock (&m);
477 g_mutex_unlock (&m);
478
479 rb_debug ("GMutex %s recursive", mutex_recurses ? "is" : "isn't");
480
481 /* purge useless thread-pool threads occasionally */
482 g_timeout_add_seconds (30, purge_useless_threads, NULL);
483 }
484
485 /**
486 * rb_string_split_words:
487 * @string: the string to split
488 *
489 * Splits @string on word boundaries using Unicode character definitions.
490 *
491 * Return value: (array zero-terminated=1) (transfer full): NULL-terminated array of strings
492 */
493 gchar **
494 rb_string_split_words (const gchar *string)
495 {
496 /*return g_slist_prepend (NULL, g_strdup (string));*/
497
498 GSList *words, *current;
499 gunichar *unicode, *cur_write, *cur_read;
500 gchar **ret;
501 gchar *normalized;
502 gint i, wordcount = 1;
503 gboolean new_word = TRUE;
504
505 g_return_val_if_fail (string != NULL, NULL);
506
507 normalized = g_utf8_normalize(string, -1, G_NORMALIZE_DEFAULT);
508 cur_write = cur_read = unicode = g_utf8_to_ucs4_fast (normalized, -1, NULL);
509
510 /* we may fail here, we expect valid utf-8 */
511 g_return_val_if_fail (unicode != NULL, NULL);
512
513 words = g_slist_prepend (NULL, unicode);
514
515 /* now normalize this text */
516 while (*cur_read) {
517 switch (g_unichar_type (*cur_read)) {
518 case G_UNICODE_UNASSIGNED:
519 rb_debug ("unassigned unicode character type found");
520 /* fall through */
521 case G_UNICODE_CONTROL:
522 case G_UNICODE_FORMAT:
523 case G_UNICODE_PRIVATE_USE:
524
525 case G_UNICODE_SURROGATE:
526 case G_UNICODE_LINE_SEPARATOR:
527 case G_UNICODE_PARAGRAPH_SEPARATOR:
528 case G_UNICODE_SPACE_SEPARATOR:
529 /* remove these and start a new word */
530 if (!new_word) {
531 /* end current word if it isn't ended yet */
532 *cur_write++ = 0;
533 new_word = TRUE;
534 }
535
536 break;
537 case G_UNICODE_COMBINING_MARK:
538 case G_UNICODE_ENCLOSING_MARK:
539 case G_UNICODE_NON_SPACING_MARK:
540 case G_UNICODE_CONNECT_PUNCTUATION:
541 case G_UNICODE_DASH_PUNCTUATION:
542 case G_UNICODE_CLOSE_PUNCTUATION:
543 case G_UNICODE_FINAL_PUNCTUATION:
544 case G_UNICODE_INITIAL_PUNCTUATION:
545 case G_UNICODE_OTHER_PUNCTUATION:
546 case G_UNICODE_OPEN_PUNCTUATION:
547 /* remove these */
548 /*break;*/
549 case G_UNICODE_LOWERCASE_LETTER:
550 case G_UNICODE_MODIFIER_LETTER:
551 case G_UNICODE_OTHER_LETTER:
552 case G_UNICODE_TITLECASE_LETTER:
553 case G_UNICODE_UPPERCASE_LETTER:
554 case G_UNICODE_DECIMAL_NUMBER:
555 case G_UNICODE_LETTER_NUMBER:
556 case G_UNICODE_OTHER_NUMBER:
557 case G_UNICODE_CURRENCY_SYMBOL:
558 case G_UNICODE_MODIFIER_SYMBOL:
559 case G_UNICODE_MATH_SYMBOL:
560 case G_UNICODE_OTHER_SYMBOL:
561 /* keep these unchanged */
562 *cur_write = *cur_read;
563 if (new_word) {
564 if (cur_write != unicode) {/* first insert has been done above */
565 words = g_slist_prepend (words, cur_write);
566 wordcount++;
567 }
568 new_word = FALSE;
569 }
570 cur_write++;
571 break;
572 default:
573 g_warning ("unknown unicode character type found");
574 break;
575 }
576 cur_read++;
577 }
578
579 if (!new_word) {
580 *cur_write++ = 0;
581 }
582
583 ret = g_new (gchar *, wordcount + 1);
584 current = words;
585 for (i = wordcount - 1; i >= 0; i--) {
586 ret[i] = g_ucs4_to_utf8 (current->data, -1, NULL, NULL, NULL);
587 current = g_slist_next (current);
588 }
589 ret[wordcount] = NULL;
590
591 g_slist_free (words);
592 g_free (unicode);
593 g_free (normalized);
594
595 return ret;
596 }
597
598 /**
599 * rb_search_fold:
600 * @original: the string to fold
601 *
602 * Returns a case-folded and punctuation-stripped version of @original, useful
603 * for performing text searches.
604 *
605 * Return value: (transfer full): case-folded string
606 */
607 gchar*
608 rb_search_fold (const char *original)
609 {
610 GString *string;
611 gchar *normalized;
612 gunichar *unicode, *cur;
613
614 g_return_val_if_fail (original != NULL, NULL);
615
616 /* old behaviour is equivalent to: return g_utf8_casefold (original, -1); */
617
618 string = g_string_new (NULL);
619 normalized = g_utf8_normalize(original, -1, G_NORMALIZE_DEFAULT);
620 unicode = g_utf8_to_ucs4_fast (normalized, -1, NULL);
621
622
623 for (cur = unicode; *cur != 0; cur++) {
624 switch (g_unichar_type (*cur)) {
625 case G_UNICODE_COMBINING_MARK:
626 case G_UNICODE_ENCLOSING_MARK:
627 case G_UNICODE_NON_SPACING_MARK:
628 case G_UNICODE_CONNECT_PUNCTUATION:
629 case G_UNICODE_DASH_PUNCTUATION:
630 case G_UNICODE_CLOSE_PUNCTUATION:
631 case G_UNICODE_FINAL_PUNCTUATION:
632 case G_UNICODE_INITIAL_PUNCTUATION:
633 case G_UNICODE_OTHER_PUNCTUATION:
634 case G_UNICODE_OPEN_PUNCTUATION:
635 /* remove these */
636 break;
637
638 case G_UNICODE_LOWERCASE_LETTER:
639 case G_UNICODE_MODIFIER_LETTER:
640 case G_UNICODE_OTHER_LETTER:
641 case G_UNICODE_TITLECASE_LETTER:
642 case G_UNICODE_UPPERCASE_LETTER:
643 /* convert to lower case */
644 *cur = g_unichar_tolower (*cur);
645 /* ... and fall through */\
646 case G_UNICODE_DECIMAL_NUMBER:
647 case G_UNICODE_LETTER_NUMBER:
648 case G_UNICODE_OTHER_NUMBER:
649 /* should be keep symbols? */
650 case G_UNICODE_CURRENCY_SYMBOL:
651 case G_UNICODE_MODIFIER_SYMBOL:
652 case G_UNICODE_MATH_SYMBOL:
653 case G_UNICODE_OTHER_SYMBOL:
654 g_string_append_unichar (string, *cur);
655 break;
656
657 case G_UNICODE_UNASSIGNED:
658 rb_debug ("unassigned unicode character type found");
659 /* fall through */
660
661 default:
662 /* leave these in */
663 g_string_append_unichar (string, *cur);
664 }
665 }
666
667 g_free (unicode);
668 g_free (normalized);
669
670 return g_string_free (string, FALSE);
671 }
672
673 /**
674 * rb_make_time_string:
675 * @seconds: time in seconds
676 *
677 * Constructs a string describing the specified time.
678 *
679 * Return value: (transfer full): time string
680 */
681 char *
682 rb_make_time_string (guint nseconds)
683 {
684 int hours, minutes, seconds;
685
686 hours = nseconds / (60 * 60);
687 minutes = (nseconds - (hours * 60 * 60)) / 60;
688 seconds = nseconds % 60;
689
690 if (hours == 0)
691 return g_strdup_printf (_("%d:%02d"), minutes, seconds);
692 else
693 return g_strdup_printf (_("%d:%02d:%02d"), hours, minutes, seconds);
694 }
695
696
697 /**
698 * rb_make_duration_string:
699 * @duration: duration in seconds
700 *
701 * Constructs a string describing the specified duration. The string
702 * describes hours, minutes, and seconds, and its format is localised.
703 *
704 * Return value: (transfer full): duration string
705 */
706 char *
707 rb_make_duration_string (guint duration)
708 {
709 if (duration == 0)
710 return g_strdup (_("Unknown"));
711 else
712 return rb_make_time_string (duration);
713 }
714
715 /**
716 * rb_make_elapsed_time_string:
717 * @elapsed: elapsed time (in seconds)
718 * @duration: duration (in seconds)
719 * @show_remaining: if %TRUE, show the remaining time, otherwise show elapsed time
720 *
721 * Constructs a string describing a playback position. The string describes hours,
722 * minutes, and seconds, and its format is localised. The string can describe either
723 * the elapsed time or the time remaining.
724 *
725 * Return value: (transfer full): elapsed/remaining time string
726 */
727 char *
728 rb_make_elapsed_time_string (guint elapsed, guint duration, gboolean show_remaining)
729 {
730 int seconds = 0, minutes = 0, hours = 0;
731 int seconds2 = 0, minutes2 = 0, hours2 = 0;
732
733 if (duration == 0)
734 return rb_make_time_string (elapsed);
735
736 if (duration > 0) {
737 hours2 = duration / (60 * 60);
738 minutes2 = (duration - (hours2 * 60 * 60)) / 60;
739 seconds2 = duration % 60;
740 }
741
742 if (elapsed > 0) {
743 hours = elapsed / (60 * 60);
744 minutes = (elapsed - (hours * 60 * 60)) / 60;
745 seconds = elapsed % 60;
746 }
747
748 if (show_remaining) {
749 int remaining = duration - elapsed;
750 int remaining_hours = remaining / (60 * 60);
751 int remaining_minutes = (remaining - (remaining_hours * 60 * 60)) / 60;
752 /* remaining could conceivably be negative. This would
753 * be a bug, but the elapsed time will display right
754 * with the abs(). */
755 int remaining_seconds = abs (remaining % 60);
756 if (hours2 == 0)
757 return g_strdup_printf (_("%d:%02d of %d:%02d remaining"),
758 remaining_minutes, remaining_seconds,
759 minutes2, seconds2);
760 else
761 return g_strdup_printf (_("%d:%02d:%02d of %d:%02d:%02d remaining"),
762 remaining_hours, remaining_minutes, remaining_seconds,
763 hours2, minutes2, seconds2);
764 } else {
765 if (hours == 0 && hours2 == 0)
766 return g_strdup_printf (_("%d:%02d of %d:%02d"),
767 minutes, seconds,
768 minutes2, seconds2);
769 else
770 return g_strdup_printf (_("%d:%02d:%02d of %d:%02d:%02d"),
771 hours, minutes, seconds,
772 hours2, minutes2, seconds2);
773 }
774 }
775
776 /**
777 * rb_string_list_equal: (skip):
778 * @a: (element-type utf8): list of strings to compare
779 * @b: (element-type utf8): other list of strings to compare
780 *
781 * Checks if @a and @b contain exactly the same set of strings,
782 * regardless of order.
783 *
784 * Return value: %TRUE if the lists contain all the same strings
785 */
786 gboolean
787 rb_string_list_equal (GList *a, GList *b)
788 {
789 GList *sorted_a_keys;
790 GList *sorted_b_keys;
791 GList *a_ptr, *b_ptr;
792 gboolean ret = TRUE;
793
794 if (a == b)
795 return TRUE;
796
797 if (g_list_length (a) != g_list_length (b))
798 return FALSE;
799
800 for (sorted_a_keys = NULL; a; a = a->next) {
801 sorted_a_keys = g_list_prepend (sorted_a_keys,
802 g_utf8_collate_key (a->data, -1));
803 }
804 for (sorted_b_keys = NULL; b; b = b->next) {
805 sorted_b_keys = g_list_prepend (sorted_b_keys,
806 g_utf8_collate_key (b->data, -1));
807 }
808 sorted_a_keys = g_list_sort (sorted_a_keys, (GCompareFunc) strcmp);
809 sorted_b_keys = g_list_sort (sorted_b_keys, (GCompareFunc) strcmp);
810
811 for (a_ptr = sorted_a_keys, b_ptr = sorted_b_keys;
812 a_ptr && b_ptr; a_ptr = a_ptr->next, b_ptr = b_ptr->next) {
813 if (strcmp (a_ptr->data, b_ptr->data)) {
814 ret = FALSE;
815 break;
816 }
817 }
818 g_list_foreach (sorted_a_keys, (GFunc) g_free, NULL);
819 g_list_foreach (sorted_b_keys, (GFunc) g_free, NULL);
820 g_list_free (sorted_a_keys);
821 g_list_free (sorted_b_keys);
822 return ret;
823 }
824
825 static void
826 list_copy_cb (const char *s, GList **list)
827 {
828 *list = g_list_prepend (*list, g_strdup (s));
829 }
830
831 /**
832 * rb_string_list_copy: (skip):
833 * @list: (element-type utf8): list of strings to copy
834 *
835 * Creates a deep copy of @list.
836 *
837 * Return value: (element-type utf8) (transfer full): copied list
838 */
839 GList *
840 rb_string_list_copy (GList *list)
841 {
842 GList *copy = NULL;
843
844 if (list == NULL)
845 return NULL;
846
847 g_list_foreach (list, (GFunc)list_copy_cb, ©);
848 copy = g_list_reverse (copy);
849
850 return copy;
851 }
852
853 /**
854 * rb_string_list_contains: (skip):
855 * @list: (element-type utf8) list to check
856 * @s: string to check for
857 *
858 * Checks if @list contains the string @s.
859 *
860 * Return value: %TRUE if found
861 */
862 gboolean
863 rb_string_list_contains (GList *list, const char *s)
864 {
865 GList *l;
866
867 for (l = list; l != NULL; l = g_list_next (l)) {
868 if (strcmp ((const char *)l->data, s) == 0)
869 return TRUE;
870 }
871
872 return FALSE;
873 }
874
875 /**
876 * rb_list_destroy_free: (skip):
877 * @list: list to destroy
878 * @destroyer: function to call to free elements of @list
879 *
880 * Calls @destroyer for each element in @list, then frees @list.
881 */
882 void
883 rb_list_destroy_free (GList *list, GDestroyNotify destroyer)
884 {
885 g_list_foreach (list, (GFunc)destroyer, NULL);
886 g_list_free (list);
887 }
888
889 /**
890 * rb_list_deep_free: (skip):
891 * @list: (element-type any) (transfer full): list to free
892 *
893 * Frees each element of @list and @list itself.
894 */
895 void
896 rb_list_deep_free (GList *list)
897 {
898 rb_list_destroy_free (list, (GDestroyNotify)g_free);
899 }
900
901 /**
902 * rb_slist_deep_free: (skip):
903 * @list: (element-type any) (transfer full): list to free
904 *
905 * Frees each element of @list and @list itself.
906 */
907 void
908 rb_slist_deep_free (GSList *list)
909 {
910 g_slist_foreach (list, (GFunc)g_free, NULL);
911 g_slist_free (list);
912 }
913
914 static void
915 collate_keys_cb (gpointer key, gpointer value, GList **list)
916 {
917 *list = g_list_prepend (*list, key);
918 }
919
920 static void
921 collate_values_cb (gpointer key, gpointer value, GList **list)
922 {
923 *list = g_list_prepend (*list, value);
924 }
925
926 /**
927 * rb_collate_hash_table_keys: (skip):
928 * @table: #GHashTable to collate
929 *
930 * Returns a #GList containing all keys from @table. The keys are
931 * not copied.
932 *
933 * Return value: (element-type any) (transfer container): #GList of keys
934 */
935 GList*
936 rb_collate_hash_table_keys (GHashTable *table)
937 {
938 GList *list = NULL;
939
940 g_hash_table_foreach (table, (GHFunc)collate_keys_cb, &list);
941 list = g_list_reverse (list);
942
943 return list;
944 }
945
946 /**
947 * rb_collate_hash_table_values: (skip):
948 * @table: #GHashTable to collate
949 *
950 * Returns a #GList containing all values from @table. The values are
951 * not copied.
952 *
953 * Return value: (element-type any) (transfer container): #GList of values
954 */
955 GList*
956 rb_collate_hash_table_values (GHashTable *table)
957 {
958 GList *list = NULL;
959
960 g_hash_table_foreach (table, (GHFunc)collate_values_cb, &list);
961 list = g_list_reverse (list);
962
963 return list;
964 }
965
966 /**
967 * rb_uri_list_parse:
968 * @uri_list: string containing URIs to parse
969 *
970 * Converts a single string containing a list of URIs into
971 * a #GList of URI strings.
972 *
973 * Return value: (element-type utf8) (transfer full): #GList of URI strings
974 */
975 GList *
976 rb_uri_list_parse (const char *uri_list)
977 {
978 const gchar *p, *q;
979 gchar *retval;
980 GList *result = NULL;
981
982 g_return_val_if_fail (uri_list != NULL, NULL);
983
984 p = uri_list;
985
986 while (p != NULL) {
987 while (g_ascii_isspace (*p))
988 p++;
989
990 q = p;
991 while ((*q != '\0')
992 && (*q != '\n')
993 && (*q != '\r'))
994 q++;
995
996 if (q > p) {
997 q--;
998 while (q > p
999 && g_ascii_isspace (*q))
1000 q--;
1001
1002 retval = g_malloc (q - p + 2);
1003 strncpy (retval, p, q - p + 1);
1004 retval[q - p + 1] = '\0';
1005
1006 if (retval != NULL)
1007 result = g_list_prepend (result, retval);
1008 }
1009 p = strchr (p, '\n');
1010 if (p != NULL)
1011 p++;
1012 }
1013
1014 return g_list_reverse (result);
1015 }
1016
1017 /**
1018 * rb_signal_accumulator_object_handled: (skip):
1019 * @hint: a #GSignalInvocationHint
1020 * @return_accu: holds the accumulated return value
1021 * @handler_return: holds the return value to be accumulated
1022 * @dummy: user data (unused)
1023 *
1024 * A #GSignalAccumulator that aborts the signal emission after the
1025 * first handler to return a value, and returns the value returned by
1026 * that handler. This is the opposite behaviour from what you get when
1027 * no accumulator is specified, where the last signal handler wins.
1028 *
1029 * Return value: %FALSE to abort signal emission, %TRUE to continue
1030 */
1031 gboolean
1032 rb_signal_accumulator_object_handled (GSignalInvocationHint *hint,
1033 GValue *return_accu,
1034 const GValue *handler_return,
1035 gpointer dummy)
1036 {
1037 if (handler_return == NULL ||
1038 !G_VALUE_HOLDS_OBJECT (handler_return) ||
1039 g_value_get_object (handler_return) == NULL)
1040 return TRUE;
1041
1042 g_value_unset (return_accu);
1043 g_value_init (return_accu, G_VALUE_TYPE (handler_return));
1044 g_value_copy (handler_return, return_accu);
1045
1046 return FALSE;
1047 }
1048
1049 /**
1050 * rb_signal_accumulator_value_handled: (skip):
1051 * @hint: a #GSignalInvocationHint
1052 * @return_accu: holds the accumulated return value
1053 * @handler_return: holds the return value to be accumulated
1054 * @dummy: user data (unused)
1055 *
1056 * A #GSignalAccumulator that aborts the signal emission after the
1057 * first handler to return a value, and returns the value returned by
1058 * that handler. This is the opposite behaviour from what you get when
1059 * no accumulator is specified, where the last signal handler wins.
1060 *
1061 * Return value: %FALSE to abort signal emission, %TRUE to continue
1062 */
1063 gboolean
1064 rb_signal_accumulator_value_handled (GSignalInvocationHint *hint,
1065 GValue *return_accu,
1066 const GValue *handler_return,
1067 gpointer dummy)
1068 {
1069 if (handler_return == NULL ||
1070 !G_VALUE_HOLDS (handler_return, G_TYPE_VALUE) ||
1071 g_value_get_boxed (handler_return) == NULL)
1072 return TRUE;
1073
1074 g_value_unset (return_accu);
1075 g_value_init (return_accu, G_VALUE_TYPE (handler_return));
1076 g_value_copy (handler_return, return_accu);
1077
1078 return FALSE;
1079 }
1080
1081 /**
1082 * rb_signal_accumulator_value_array: (skip):
1083 * @hint: a #GSignalInvocationHint
1084 * @return_accu: holds the accumulated return value
1085 * @handler_return: holds the return value to be accumulated
1086 * @dummy: user data (unused)
1087 *
1088 * A #GSignalAccumulator used to combine all returned values into
1089 * a #GArray of #GValue instances.
1090 *
1091 * Return value: %FALSE to abort signal emission, %TRUE to continue
1092 */
1093 gboolean
1094 rb_signal_accumulator_value_array (GSignalInvocationHint *hint,
1095 GValue *return_accu,
1096 const GValue *handler_return,
1097 gpointer dummy)
1098 {
1099 GArray *a;
1100 GArray *b;
1101 int i;
1102
1103 if (handler_return == NULL)
1104 return TRUE;
1105
1106 a = g_array_sized_new (FALSE, TRUE, sizeof (GValue), 1);
1107 g_array_set_clear_func (a, (GDestroyNotify) g_value_unset);
1108 if (G_VALUE_HOLDS_BOXED (return_accu)) {
1109 b = g_value_get_boxed (return_accu);
1110 if (b != NULL) {
1111 g_array_append_vals (a, b->data, b->len);
1112 }
1113 }
1114
1115 if (G_VALUE_HOLDS_BOXED (handler_return)) {
1116 b = g_value_get_boxed (handler_return);
1117 for (i=0; i < b->len; i++) {
1118 a = g_array_append_val (a, g_array_index (b, GValue, i));
1119 }
1120 }
1121
1122 g_value_unset (return_accu);
1123 g_value_init (return_accu, G_TYPE_ARRAY);
1124 g_value_set_boxed (return_accu, a);
1125 return TRUE;
1126 }
1127
1128 /**
1129 * rb_signal_accumulator_boolean_or: (skip):
1130 * @hint: a #GSignalInvocationHint
1131 * @return_accu: holds the accumulated return value
1132 * @handler_return: holds the return value to be accumulated
1133 * @dummy: user data (unused)
1134 *
1135 * A #GSignalAccumulator used to return the boolean OR of all
1136 * returned (boolean) values.
1137 *
1138 * Return value: %FALSE to abort signal emission, %TRUE to continue
1139 */
1140 gboolean
1141 rb_signal_accumulator_boolean_or (GSignalInvocationHint *hint,
1142 GValue *return_accu,
1143 const GValue *handler_return,
1144 gpointer dummy)
1145 {
1146 if (handler_return != NULL && G_VALUE_HOLDS_BOOLEAN (handler_return)) {
1147 if (G_VALUE_HOLDS_BOOLEAN (return_accu) == FALSE ||
1148 g_value_get_boolean (return_accu) == FALSE) {
1149 g_value_unset (return_accu);
1150 g_value_init (return_accu, G_TYPE_BOOLEAN);
1151 g_value_set_boolean (return_accu, g_value_get_boolean (handler_return));
1152 }
1153 }
1154
1155 return TRUE;
1156 }
1157
1158 /**
1159 * rb_value_array_append_data: (skip):
1160 * @array: #GArray to append to
1161 * @type: #GType of the value being appended
1162 * @Varargs: value to append
1163 *
1164 * Appends a single value to @array, collecting it from @Varargs.
1165 */
1166 void
1167 rb_value_array_append_data (GArray *array, GType type, ...)
1168 {
1169 GValue val = {0,};
1170 va_list va;
1171 gchar *err = NULL;
1172
1173 va_start (va, type);
1174
1175 g_value_init (&val, type);
1176 G_VALUE_COLLECT (&val, va, 0, &err);
1177 g_array_append_val (array, val);
1178 g_value_unset (&val);
1179
1180 if (err)
1181 rb_debug ("unable to collect GValue: %s", err);
1182
1183 va_end (va);
1184 }
1185
1186 /**
1187 * rb_value_free: (skip):
1188 * @val: (transfer full): a #GValue
1189 *
1190 * Unsets and frees @val. @val must have been allocated using
1191 * @g_slice_new or @g_slice_new0.
1192 */
1193 void
1194 rb_value_free (GValue *val)
1195 {
1196 g_value_unset (val);
1197 g_slice_free (GValue, val);
1198 }
1199
1200 /**
1201 * rb_str_in_strv: (skip):
1202 * @needle: string to search for
1203 * @haystack: array of strings to search
1204 *
1205 * Checks if @needle is present in the NULL-terminated string
1206 * array @haystack.
1207 *
1208 * Return value: %TRUE if found
1209 */
1210 gboolean
1211 rb_str_in_strv (const char *needle, const char **haystack)
1212 {
1213 int i;
1214
1215 if (needle == NULL || haystack == NULL)
1216 return FALSE;
1217
1218 for (i = 0; haystack[i] != NULL; i++) {
1219 if (strcmp (needle, haystack[i]) == 0)
1220 return TRUE;
1221 }
1222
1223 return FALSE;
1224 }
1225
1226 /**
1227 * rb_set_tree_view_column_fixed_width:
1228 * @treeview: the #GtkTreeView containing the column
1229 * @column: the #GtkTreeViewColumn to size
1230 * @renderer: the #GtkCellRenderer used in the column
1231 * @strings: (array zero-terminated=1): a NULL-terminated set of strings to base the size on
1232 * @padding: a small amount of extra padding for the column
1233 *
1234 * Sets a fixed size for a tree view column based on
1235 * a set of strings to be displayed in the column.
1236 */
1237 void
1238 rb_set_tree_view_column_fixed_width (GtkWidget *treeview,
1239 GtkTreeViewColumn *column,
1240 GtkCellRenderer *renderer,
1241 const char **strings,
1242 int padding)
1243 {
1244 int max_width = 0;
1245 int i = 0;
1246
1247 while (strings[i] != NULL) {
1248 GtkRequisition natural_size;
1249 g_object_set (renderer, "text", strings[i], NULL);
1250 /* XXX should we use minimum size instead? */
1251 gtk_cell_renderer_get_preferred_size (renderer,
1252 GTK_WIDGET (treeview),
1253 NULL,
1254 &natural_size);
1255
1256 if (natural_size.width > max_width)
1257 max_width = natural_size.width;
1258
1259 i++;
1260 }
1261
1262 gtk_tree_view_column_set_fixed_width (column, max_width + padding);
1263 }
1264
1265 /**
1266 * rb_scale_pixbuf_to_size:
1267 * @pixbuf: the #GdkPixbuf containing the original image
1268 * @size: a stock icon size
1269 *
1270 * Creates a new #GdkPixbuf from the original one, for a target of
1271 * size, respecting the aspect ratio of the image.
1272 *
1273 * Return value: (transfer full): scaled #GdkPixbuf
1274 */
1275 GdkPixbuf *
1276 rb_scale_pixbuf_to_size (GdkPixbuf *pixbuf, GtkIconSize size)
1277 {
1278 int icon_size;
1279 int width, height;
1280 int d_width, d_height;
1281
1282 g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), NULL);
1283
1284 if (gtk_icon_size_lookup (size, &icon_size, NULL) == FALSE)
1285 return NULL;
1286
1287 width = gdk_pixbuf_get_width (pixbuf);
1288 height = gdk_pixbuf_get_height (pixbuf);
1289
1290 if (width > height) {
1291 d_width = icon_size;
1292 d_height = d_width * height / width;
1293 } else {
1294 d_height = icon_size;
1295 d_width = d_height * width / height;
1296 }
1297
1298 return gdk_pixbuf_scale_simple (pixbuf, d_width, d_height, GDK_INTERP_BILINEAR);
1299 }
1300
1301 #define DELAYED_SYNC_ITEM "rb-delayed-sync"
1302 #define DELAYED_SYNC_FUNC_ITEM "rb-delayed-sync-func"
1303 #define DELAYED_SYNC_DATA_ITEM "rb-delayed-sync-data"
1304
1305
1306 static gboolean
1307 do_delayed_apply (GSettings *settings)
1308 {
1309 gpointer data;
1310 RBDelayedSyncFunc sync_func;
1311
1312 data = g_object_get_data (G_OBJECT (settings), DELAYED_SYNC_DATA_ITEM);
1313 sync_func = g_object_get_data (G_OBJECT (settings), DELAYED_SYNC_FUNC_ITEM);
1314 if (sync_func != NULL) {
1315 GDK_THREADS_ENTER ();
1316 sync_func (settings, data);
1317 GDK_THREADS_LEAVE ();
1318 }
1319
1320 g_object_set_data (G_OBJECT (settings), DELAYED_SYNC_ITEM, GUINT_TO_POINTER (0));
1321 g_object_set_data (G_OBJECT (settings), DELAYED_SYNC_FUNC_ITEM, NULL);
1322 g_object_set_data (G_OBJECT (settings), DELAYED_SYNC_DATA_ITEM, NULL);
1323 return FALSE;
1324 }
1325
1326 static void
1327 remove_delayed_sync (gpointer data)
1328 {
1329 g_source_remove (GPOINTER_TO_UINT (data));
1330 }
1331
1332 /**
1333 * rb_settings_delayed_sync:
1334 * @settings: #GSettings instance
1335 * @sync_func: (allow-none): function to call
1336 * @data: (allow-none): data to pass to @func
1337 * @destroy: (allow-none): function to use to free @data
1338 *
1339 * Synchronizes settings in the @settings instance after 500ms has elapsed
1340 * with no further changes.
1341 */
1342 void
1343 rb_settings_delayed_sync (GSettings *settings, RBDelayedSyncFunc sync_func, gpointer data, GDestroyNotify destroy)
1344 {
1345 if (sync_func == NULL) {
1346 do_delayed_apply (settings);
1347 } else {
1348 guint id = g_timeout_add (500, (GSourceFunc) do_delayed_apply, settings);
1349 g_object_set_data_full (G_OBJECT (settings), DELAYED_SYNC_ITEM, GUINT_TO_POINTER (id), remove_delayed_sync);
1350 g_object_set_data (G_OBJECT (settings), DELAYED_SYNC_FUNC_ITEM, sync_func);
1351 g_object_set_data_full (G_OBJECT (settings), DELAYED_SYNC_DATA_ITEM, data, destroy);
1352 }
1353 }