1 /*************************************************************************/ 2 /* Copyright (C) 2007-2009 sujith <m.sujith@gmail.com> */ 3 /* Copyright (C) 2009-2013 matias <mati86dl@gmail.com> */ 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 3 of the License, or */ 8 /* (at your option) any later version. */ 9 /* */ 10 /* This program is distributed in the hope that it will be useful, */ 11 /* but WITHOUT ANY WARRANTY; without even the implied warranty of */ 12 /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ 13 /* GNU General Public License for more details. */ 14 /* */ 15 /* You should have received a copy of the GNU General Public License */ 16 /* along with this program. If not, see <http://www.gnu.org/licenses/>. */ 17 /*************************************************************************/ 18 19 #if HAVE_CONFIG_H 20 #include <config.h> 21 #endif 22 23 #include "pragha-utils.h" 24 25 #if defined(GETTEXT_PACKAGE) 26 #include <glib/gi18n-lib.h> 27 #else 28 #include <glib/gi18n.h> 29 #endif 30 31 #include <glib.h> 32 #include <glib/gprintf.h> 33 34 #ifdef G_OS_WIN32 35 #include <windows.h> 36 #endif 37 38 39 /** 40 @brief duplicate utf8 string, truncated after @a num characters if the string is longer than that 41 @param str the string to be duplicated 42 @param num maximum no. of characters in @a str to be processed 43 @return the duplicated string 44 * Based on emelfm2 code. 45 */ 46 gchar *e2_utf8_ndup (const gchar *str, glong num) 47 { 48 glong size = g_utf8_strlen (str, -1); 49 if (num > size) 50 num = size; 51 gchar *end = g_utf8_offset_to_pointer (str, num); 52 glong byte_size = end - str + 1; 53 gchar *utf8 = g_malloc (byte_size); 54 return g_utf8_strncpy (utf8, str, num); 55 } 56 57 /* Compare two strings and returns the levenshtein distance. 58 * Based on glyr code. Thanks to cpahl. */ 59 60 gsize levenshtein_strcmp(const gchar * s, const gchar * t) 61 { 62 int n = (s) ? g_utf8_strlen(s,-1)+1 : 0; 63 int m = (t) ? g_utf8_strlen(t,-1)+1 : 0; 64 65 // NOTE: Be sure to call g_utf8_validate(), might fail otherwise 66 // It's advisable to call g_utf8_normalize() too. 67 68 // Nothing to compute really.. 69 if (n < 2) return m; 70 if (m < 2) return n; 71 72 // String matrix 73 int d[n][m]; 74 int i,j; 75 76 // Init first row|column to 0...n|m 77 for (i=0; i<n; i++) d[i][0] = i; 78 for (j=0; j<m; j++) d[0][j] = j; 79 80 for (i=1; i<n; i++) 81 { 82 // Current char in string s 83 gunichar cats = g_utf8_get_char(g_utf8_offset_to_pointer(s,i-1)); 84 85 for (j=1; j<m; j++) 86 { 87 // Do -1 only once 88 int jm1 = j-1, 89 im1 = i-1; 90 91 gunichar tats = g_utf8_get_char(g_utf8_offset_to_pointer(t,jm1)); 92 93 // a = above cell, b = left cell, c = left above celli 94 int a = d[im1][j] + 1, 95 b = d[i][jm1] + 1, 96 c = d[im1][jm1] + (tats != cats); 97 98 // Now compute the minimum of a,b,c and set MIN(a,b,c) to cell d[i][j] 99 d[i][j] = (a < b) ? MIN(a,c) : MIN(b,c); 100 } 101 } 102 103 // The result is stored in the very right down cell 104 return d[n-1][m-1]; 105 } 106 107 gsize levenshtein_safe_strcmp(const gchar * s, const gchar * t) 108 { 109 gsize rc = 100; 110 111 if(g_utf8_validate(s,-1,NULL) == FALSE || 112 g_utf8_validate(t,-1,NULL) == FALSE) 113 return rc; 114 115 gchar * s_norm = g_utf8_normalize(s, -1 ,G_NORMALIZE_ALL_COMPOSE); 116 gchar * t_norm = g_utf8_normalize(t, -1, G_NORMALIZE_ALL_COMPOSE); 117 118 rc = levenshtein_strcmp(s_norm, t_norm); 119 120 g_free(s_norm); 121 g_free(t_norm); 122 123 return rc; 124 } 125 126 /* Searches the string haystack for the first occurrence of the string needle 127 * considering a maximum levenshtein distance. */ 128 129 gchar * 130 g_strstr_lv (gchar *haystack, gchar *needle, gsize lv_distance) 131 { 132 gint needle_len = 0, haystack_len = 0, count = 0; 133 gchar *needle_buf = NULL, *rv = NULL; 134 135 haystack_len = g_utf8_strlen(haystack, -1); 136 needle_len = g_utf8_strlen(needle, -1); 137 138 /* UTF-8 bytes are 4 bytes length in the worst case */ 139 needle_buf = g_malloc0(needle_len * 4 + 1); 140 141 do { 142 g_utf8_strncpy(needle_buf, haystack, needle_len); 143 144 if (needle_len > 3 && lv_distance != 0) { 145 if(levenshtein_safe_strcmp(needle_buf, needle) <= lv_distance) { 146 rv = haystack; 147 break; 148 } 149 } 150 else { 151 if(g_ascii_strcasecmp(needle_buf, needle) == 0) { 152 rv = haystack; 153 break; 154 } 155 } 156 haystack = g_utf8_next_char(haystack); 157 158 } while(needle_len + count++ < haystack_len); 159 160 g_free(needle_buf); 161 162 return rv; 163 } 164 165 /* Searches the string haystack for the first occurrence of the string needle, 166 * considering the aproximate_search option. */ 167 168 gchar * 169 pragha_strstr_lv(gchar *haystack, gchar *needle, PraghaPreferences *preferences) 170 { 171 gboolean aproximate_search; 172 aproximate_search = pragha_preferences_get_approximate_search(preferences); 173 174 return g_strstr_lv(haystack, needle, 175 aproximate_search ? 1 : 0); 176 } 177 178 /* Set and remove the watch cursor to suggest background work.*/ 179 180 void 181 set_watch_cursor (GtkWidget *widget) 182 { 183 GdkCursor *cursor; 184 GtkWidget *toplevel; 185 186 toplevel = gtk_widget_get_toplevel(GTK_WIDGET(widget)); 187 if (G_LIKELY (toplevel != NULL)) { 188 cursor = gdk_cursor_new (GDK_WATCH); 189 190 gdk_window_set_cursor (gtk_widget_get_window (toplevel), cursor); 191 g_object_unref (cursor); 192 } 193 } 194 195 void 196 remove_watch_cursor (GtkWidget *widget) 197 { 198 GtkWidget *toplevel; 199 200 toplevel = gtk_widget_get_toplevel(GTK_WIDGET(widget)); 201 if (G_LIKELY (toplevel != NULL)) 202 gdk_window_set_cursor (gtk_widget_get_window (toplevel), NULL); 203 } 204 205 GdkPixbuf * 206 pragha_gdk_pixbuf_new_from_memory (gconstpointer data, gsize size) 207 { 208 GError *error = NULL; 209 210 GdkPixbufLoader *loader = gdk_pixbuf_loader_new (); 211 gdk_pixbuf_loader_write (loader, data, size, &error); 212 GdkPixbuf *pixbuf = gdk_pixbuf_loader_get_pixbuf (loader); 213 if (pixbuf) 214 g_object_ref (pixbuf); 215 gdk_pixbuf_loader_close (loader, NULL); 216 g_object_unref (loader); 217 218 if (error) { 219 g_warning ("pragha_gdk_pixbuf_new_from_memory: %s\n", error->message); 220 g_error_free (error); 221 } 222 223 return pixbuf; 224 } 225 226 /* NB: Have to take care of longer lengths .. */ 227 228 gchar* convert_length_str(gint length) 229 { 230 static gchar *str, tmp[24]; 231 gint days = 0, hours = 0, minutes = 0, seconds = 0; 232 233 str = g_new0(char, 128); 234 memset(tmp, '\0', 24); 235 236 if (length > 86400) { 237 days = length/86400; 238 length = length%86400; 239 g_sprintf(tmp, "%d %s, ", days, ngettext("day", "days", days)); 240 g_strlcat(str, tmp, 24); 241 } 242 243 if (length > 3600) { 244 hours = length/3600; 245 length = length%3600; 246 memset(tmp, '\0', 24); 247 g_sprintf(tmp, "%d:", hours); 248 g_strlcat(str, tmp, 24); 249 } 250 251 if (length > 60) { 252 minutes = length/60; 253 length = length%60; 254 memset(tmp, '\0', 24); 255 g_sprintf(tmp, "%02d:", minutes); 256 g_strlcat(str, tmp, 24); 257 } 258 else 259 g_strlcat(str, "00:", 4); 260 261 seconds = length; 262 memset(tmp, '\0', 24); 263 g_sprintf(tmp, "%02d", seconds); 264 g_strlcat(str, tmp, 24); 265 266 return str; 267 } 268 269 /* Check if str is present in list ( containing gchar* elements in 'data' ) */ 270 271 gboolean is_present_str_list(const gchar *str, GSList *list) 272 { 273 GSList *i; 274 gchar *lstr; 275 gboolean ret = FALSE; 276 277 if (!str) 278 return FALSE; 279 280 if (list) { 281 for (i=list; i != NULL; i = i->next) { 282 lstr = i->data; 283 if (!g_ascii_strcasecmp(str, lstr)) { 284 ret = TRUE; 285 break; 286 } 287 } 288 } 289 else { 290 ret = FALSE; 291 } 292 293 return ret; 294 } 295 296 /* Delete str from list */ 297 298 GSList* delete_from_str_list(const gchar *str, GSList *list) 299 { 300 GSList *i = NULL; 301 gchar *lstr; 302 303 if (!str) 304 return list; 305 if (!list) 306 return NULL; 307 308 for (i = list; i != NULL; i = i->next) { 309 lstr = i->data; 310 if (!g_ascii_strcasecmp(str, lstr)) { 311 g_free(i->data); 312 return g_slist_delete_link(list, i); 313 } 314 } 315 316 return list; 317 } 318 319 gchar * 320 path_get_dir_as_uri (const gchar *path) 321 { 322 gchar *dir = g_path_get_dirname (path); 323 gchar *uri = g_filename_to_uri (dir, NULL, NULL); 324 g_free (dir); 325 return uri; 326 } 327 328 /* Returns either the basename of the given filename, or (if the parameter 329 * get_folder is set) the basename of the container folder of filename. In both 330 * cases the returned string is encoded in utf-8 format. If GLib can not make 331 * sense of the encoding of filename, as a last resort it replaces unknown 332 * characters with U+FFFD, the Unicode replacement character */ 333 334 gchar* get_display_filename(const gchar *filename, gboolean get_folder) 335 { 336 gchar *utf8_filename = NULL; 337 gchar *dir = NULL; 338 339 /* Get the containing folder of the file or the file itself ? */ 340 if (get_folder) { 341 dir = g_path_get_dirname(filename); 342 utf8_filename = g_filename_display_name(dir); 343 g_free(dir); 344 } 345 else { 346 utf8_filename = g_filename_display_basename(filename); 347 } 348 return utf8_filename; 349 } 350 351 gchar* get_display_name(PraghaMusicobject *mobj) 352 { 353 gchar *name = NULL; 354 355 if (!pragha_musicobject_is_local_file(mobj)) { 356 name = g_strdup(pragha_musicobject_get_file(mobj)); 357 } else { 358 name = get_display_filename(pragha_musicobject_get_file(mobj), FALSE); 359 } 360 return name; 361 } 362 363 /* Free a list of strings */ 364 365 void free_str_list(GSList *list) 366 { 367 g_slist_free_full(list, g_free); 368 } 369 370 /* Compare two UTF-8 strings */ 371 372 gint compare_utf8_str(const gchar *str1, const gchar *str2) 373 { 374 gchar *key1, *key2; 375 gint ret = 0; 376 377 if (!str1) 378 return 1; 379 380 if (!str2) 381 return -1; 382 383 key1 = g_utf8_collate_key(str1, -1); 384 key2 = g_utf8_collate_key(str2, -1); 385 386 ret = strcmp(key1, key2); 387 388 g_free(key1); 389 g_free(key2); 390 391 return ret; 392 } 393 394 gchar * 395 pragha_escape_slashes (const gchar *str) 396 { 397 gchar *dup = g_strdup (str); 398 gchar *i = dup; 399 while (*i) { 400 if (*i == '/' || *i == '\\') 401 *i = '|'; 402 i = g_utf8_next_char (i); 403 } 404 return dup; 405 } 406 407 gboolean validate_album_art_pattern(const gchar *pattern) 408 { 409 gchar **tokens; 410 gint i = 0; 411 gboolean ret = FALSE; 412 413 if (string_is_empty(pattern)) 414 return TRUE; 415 416 if (g_strrstr(pattern, "*")) { 417 g_warning("Contains wildcards"); 418 return FALSE; 419 } 420 421 tokens = g_strsplit(pattern, ";", 0); 422 while (tokens[i]) i++; 423 424 /* Check if more than six patterns are given */ 425 426 if (i <= ALBUM_ART_NO_PATTERNS) { 427 ret = TRUE; 428 } 429 else { 430 g_warning("More than six patterns"); 431 } 432 433 g_strfreev(tokens); 434 435 return ret; 436 } 437 438 void 439 pragha_process_gtk_events () 440 { 441 #ifdef DEBUG 442 extern GThread *pragha_main_thread; 443 if (g_thread_self () != pragha_main_thread) 444 g_warning ("THREAD SAFETY ERROR!"); 445 #endif 446 while (g_main_context_pending (NULL)) { 447 g_main_context_iteration (NULL, FALSE); 448 } 449 } 450 451 /* callback used to open default browser when URLs got clicked */ 452 void open_url(const gchar *url, GtkWidget *parent) 453 { 454 #ifdef G_OS_WIN32 455 if (g_file_test(url, G_FILE_TEST_IS_DIR)) 456 ShellExecute (0, "explore", url, NULL, NULL, SW_SHOWNORMAL); 457 else 458 ShellExecute (0, "open", url, NULL, NULL, SW_SHOWNORMAL); 459 #else 460 gboolean success = TRUE; 461 const gchar *argv[3]; 462 gchar *methods[] = {"xdg-open","firefox","mozilla","opera","konqueror",NULL}; 463 int i = 0; 464 465 /* First try gtk_show_uri() (will fail if gvfs is not installed) */ 466 if (!gtk_show_uri (NULL, url, gtk_get_current_event_time (), NULL)) { 467 success = FALSE; 468 argv[1] = url; 469 argv[2] = NULL; 470 /* Next try all available methods for opening the URL */ 471 while (methods[i] != NULL) { 472 argv[0] = methods[i++]; 473 if (g_spawn_async(NULL, (gchar**)argv, NULL, G_SPAWN_SEARCH_PATH, 474 NULL, NULL, NULL, NULL)) { 475 success = TRUE; 476 break; 477 } 478 } 479 } 480 /* No method was found to open the URL */ 481 if (!success) { 482 GtkWidget *d; 483 d = gtk_message_dialog_new (GTK_WINDOW (parent), 484 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, 485 GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, 486 "%s", _("Unable to open the browser")); 487 gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG (d), 488 "%s", "No methods supported"); 489 g_signal_connect (d, "response", G_CALLBACK (gtk_widget_destroy), NULL); 490 gtk_window_present (GTK_WINDOW (d)); 491 } 492 #endif 493 } 494 495 /* It gives the position of the menu on the 496 basis of the position of combo_order */ 497 498 void 499 pragha_utils_set_menu_position (GtkMenu *menu, 500 gint *x, 501 gint *y, 502 gboolean *push_in, 503 gpointer user_data) 504 { 505 GtkWidget *widget; 506 GtkAllocation allocation; 507 GtkRequisition requisition; 508 gint menu_xpos, menu_ypos; 509 510 widget = GTK_WIDGET (user_data); 511 512 gtk_widget_get_preferred_size (GTK_WIDGET(menu), &requisition, NULL); 513 514 gdk_window_get_origin (gtk_widget_get_window(widget), &menu_xpos, &menu_ypos); 515 516 gtk_widget_get_allocation(widget, &allocation); 517 518 menu_xpos += allocation.x; 519 menu_ypos += allocation.y; 520 521 if (menu_ypos > gdk_screen_get_height (gtk_widget_get_screen (widget)) / 2) 522 menu_ypos -= requisition.height; 523 else 524 menu_ypos += allocation.height; 525 526 *x = menu_xpos; 527 *y = menu_ypos - 5; 528 529 *push_in = TRUE; 530 } 531