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