hythmbox-2.98/lib/rb-util.c

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, &copy);
 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 }