1 /*
2 * Copyright (C) 2001 Ximian, Inc.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 *
18 * Authors:
19 * Chema Celorio <chema@celorio.com>
20 */
21
22 #include <config.h>
23
24 /**
25 * SECTION:glade-utils
26 * @Title: Glade Utils
27 * @Short_Description: Welcome to the zoo.
28 *
29 * This is where all of that really usefull miscalanious stuff lands up.
30 */
31
32 #include "glade.h"
33 #include "glade-project.h"
34 #include "glade-command.h"
35 #include "glade-debug.h"
36 #include "glade-placeholder.h"
37 #include "glade-widget.h"
38 #include "glade-widget-adaptor.h"
39 #include "glade-property.h"
40 #include "glade-property-class.h"
41 #include "glade-clipboard.h"
42 #include "glade-private.h"
43
44 #include <string.h>
45 #include <gdk/gdkkeysyms.h>
46 #include <gmodule.h>
47 #include <glib/gi18n-lib.h>
48 #include <glib/gstdio.h>
49 #include <errno.h>
50
51 #ifdef G_OS_WIN32
52 #define WIN32_LEAN_AND_MEAN
53 #include <windows.h>
54 #include <shellapi.h>
55 #endif
56
57 #define GLADE_UTIL_COPY_BUFFSIZE 1024
58
59
60 /**
61 * glade_util_compose_get_type_func:
62 * @name:
63 *
64 * TODO: write me
65 *
66 * Returns:
67 */
68 static gchar *
glade_util_compose_get_type_func(const gchar * name)69 glade_util_compose_get_type_func (const gchar *name)
70 {
71 gchar *retval;
72 GString *tmp;
73 gint i = 1, j;
74
75 tmp = g_string_new (name);
76
77 while (tmp->str[i])
78 {
79 if (g_ascii_isupper (tmp->str[i]))
80 {
81 tmp = g_string_insert_c (tmp, i++, '_');
82
83 j = 0;
84 while (g_ascii_isupper (tmp->str[i++]))
85 j++;
86
87 if (j > 2)
88 g_string_insert_c (tmp, i - 2, '_');
89
90 continue;
91 }
92 i++;
93 }
94
95 tmp = g_string_append (tmp, "_get_type");
96 retval = g_ascii_strdown (tmp->str, tmp->len);
97 g_string_free (tmp, TRUE);
98
99 return retval;
100 }
101
102 /**
103 * glade_util_get_type_from_name:
104 * @name: the name of the #GType - like 'GtkWidget' or a "get-type" function.
105 * @have_func: function-name flag -- true if the name is a "get-type" function.
106 *
107 * Returns the type using the "get type" function name based on @name.
108 * If the @have_func flag is true,@name is used directly, otherwise the get-type
109 * function is contrived from @name then used.
110 *
111 * Returns: the new #GType
112 */
113 GType
glade_util_get_type_from_name(const gchar * name,gboolean have_func)114 glade_util_get_type_from_name (const gchar *name, gboolean have_func)
115 {
116 static GModule *allsymbols = NULL;
117 GType (*get_type) (void);
118 GType type = 0;
119 gchar *func_name = (gchar *) name;
120
121 if ((type = g_type_from_name (name)) == 0 &&
122 (have_func ||
123 (func_name = glade_util_compose_get_type_func (name)) != NULL))
124 {
125
126 if (!allsymbols)
127 allsymbols = g_module_open (NULL, 0);
128
129 if (g_module_symbol (allsymbols, func_name, (gpointer) & get_type))
130 {
131 g_assert (get_type);
132 type = get_type ();
133 }
134 else
135 {
136 g_warning (_("We could not find the symbol \"%s\""), func_name);
137 }
138
139 if (!have_func)
140 g_free (func_name);
141 }
142
143 if (type == 0)
144 g_warning (_("Could not get the type from \"%s\""), name);
145
146 return type;
147 }
148
149 /**
150 * glade_utils_get_pspec_from_funcname:
151 * @funcname: the symbol name of a function to generate a #GParamSpec
152 *
153 * Returns: A #GParamSpec created by the delagate function
154 * specified by @funcname
155 */
156 GParamSpec *
glade_utils_get_pspec_from_funcname(const gchar * funcname)157 glade_utils_get_pspec_from_funcname (const gchar *funcname)
158 {
159 static GModule *allsymbols = NULL;
160 GParamSpec *pspec = NULL;
161 GParamSpec *(*get_pspec) (void) = NULL;
162
163 if (!allsymbols)
164 allsymbols = g_module_open (NULL, 0);
165
166 if (!g_module_symbol (allsymbols, funcname, (gpointer) & get_pspec))
167 {
168 g_warning (_("We could not find the symbol \"%s\""), funcname);
169 return NULL;
170 }
171
172 g_assert (get_pspec);
173 pspec = get_pspec ();
174
175 return pspec;
176 }
177
178 void
_glade_util_dialog_set_hig(GtkDialog * dialog)179 _glade_util_dialog_set_hig (GtkDialog *dialog)
180 {
181 GtkWidget *vbox, *action_area;
182
183 /* HIG spacings */
184 vbox = gtk_dialog_get_content_area (dialog);
185 gtk_container_set_border_width (GTK_CONTAINER (dialog), 5);
186 gtk_box_set_spacing (GTK_BOX (vbox), 2); /* 2 * 5 + 2 = 12 */
187
188 action_area = gtk_dialog_get_action_area (dialog);
189 gtk_container_set_border_width (GTK_CONTAINER (action_area), 5);
190 gtk_box_set_spacing (GTK_BOX (action_area), 6);
191 }
192
193 /**
194 * glade_util_ui_message:
195 * @parent: a #GtkWindow cast as a #GtkWidget
196 * @type: a #GladeUIMessageType
197 * @widget: a #GtkWidget to append to the dialog vbox
198 * @format: a printf style format string
199 * @...: args for the format.
200 *
201 * Creates a new warning dialog window as a child of @parent containing
202 * the text of @format, runs it, then destroys it on close. Depending
203 * on @type, a cancel button may apear or the icon may change.
204 *
205 * Returns: True if the @type was GLADE_UI_ARE_YOU_SURE and the user
206 * selected "OK", True if the @type was GLADE_UI_YES_OR_NO and
207 * the user selected "YES"; False otherwise.
208 */
209 gint
glade_util_ui_message(GtkWidget * parent,GladeUIMessageType type,GtkWidget * widget,const gchar * format,...)210 glade_util_ui_message (GtkWidget *parent,
211 GladeUIMessageType type,
212 GtkWidget *widget,
213 const gchar *format,
214 ...)
215 {
216 GtkWidget *dialog;
217 GtkMessageType message_type = GTK_MESSAGE_INFO;
218 GtkButtonsType buttons_type = GTK_BUTTONS_OK;
219 va_list args;
220 gchar *string;
221 gint response;
222
223 va_start (args, format);
224 string = g_strdup_vprintf (format, args);
225 va_end (args);
226
227 /* Get message_type */
228 switch (type)
229 {
230 case GLADE_UI_INFO:
231 message_type = GTK_MESSAGE_INFO;
232 break;
233 case GLADE_UI_WARN:
234 case GLADE_UI_ARE_YOU_SURE:
235 message_type = GTK_MESSAGE_WARNING;
236 break;
237 case GLADE_UI_ERROR:
238 message_type = GTK_MESSAGE_ERROR;
239 break;
240 case GLADE_UI_YES_OR_NO:
241 message_type = GTK_MESSAGE_QUESTION;
242 break;
243 break;
244 default:
245 g_critical ("Bad arg for glade_util_ui_message");
246 break;
247 }
248
249
250 /* Get buttons_type */
251 switch (type)
252 {
253 case GLADE_UI_INFO:
254 case GLADE_UI_WARN:
255 case GLADE_UI_ERROR:
256 buttons_type = GTK_BUTTONS_OK;
257 break;
258 case GLADE_UI_ARE_YOU_SURE:
259 buttons_type = GTK_BUTTONS_OK_CANCEL;
260 break;
261 case GLADE_UI_YES_OR_NO:
262 buttons_type = GTK_BUTTONS_YES_NO;
263 break;
264 break;
265 default:
266 g_critical ("Bad arg for glade_util_ui_message");
267 break;
268 }
269
270 dialog = gtk_message_dialog_new (GTK_WINDOW (parent),
271 GTK_DIALOG_DESTROY_WITH_PARENT,
272 message_type, buttons_type, NULL);
273
274 gtk_message_dialog_set_markup (GTK_MESSAGE_DIALOG (dialog), string);
275
276 if (widget)
277 {
278 gtk_box_pack_end (GTK_BOX
279 (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
280 widget, TRUE, TRUE, 2);
281 gtk_widget_show (widget);
282
283 /* If theres additional content, make it resizable */
284 gtk_window_set_resizable (GTK_WINDOW (dialog), TRUE);
285 }
286
287 response = gtk_dialog_run (GTK_DIALOG (dialog));
288
289 gtk_widget_destroy (dialog);
290 g_free (string);
291
292 return (response == GTK_RESPONSE_OK || response == GTK_RESPONSE_YES);
293 }
294
295
296 gboolean
glade_util_check_and_warn_scrollable(GladeWidget * parent,GladeWidgetAdaptor * child_adaptor,GtkWidget * parent_widget)297 glade_util_check_and_warn_scrollable (GladeWidget *parent,
298 GladeWidgetAdaptor *child_adaptor,
299 GtkWidget *parent_widget)
300 {
301 if (GTK_IS_SCROLLED_WINDOW (glade_widget_get_object (parent)) &&
302 GWA_SCROLLABLE_WIDGET (child_adaptor) == FALSE)
303 {
304 GladeWidgetAdaptor *vadaptor =
305 glade_widget_adaptor_get_by_type (GTK_TYPE_VIEWPORT);
306 GladeWidgetAdaptor *parent_adaptor = glade_widget_get_adaptor (parent);
307
308 glade_util_ui_message (parent_widget,
309 GLADE_UI_INFO, NULL,
310 _("Cannot add non scrollable %s widget to a %s directly.\n"
311 "Add a %s first."),
312 glade_widget_adaptor_get_title (child_adaptor),
313 glade_widget_adaptor_get_title (parent_adaptor),
314 glade_widget_adaptor_get_title (vadaptor));
315 return TRUE;
316 }
317 return FALSE;
318 }
319
320 typedef struct
321 {
322 GtkStatusbar *statusbar;
323 guint context_id;
324 guint message_id;
325 } FlashInfo;
326
327 static const guint flash_length = 3;
328
329 static gboolean
remove_message_timeout(FlashInfo * fi)330 remove_message_timeout (FlashInfo *fi)
331 {
332 gtk_statusbar_remove (fi->statusbar, fi->context_id, fi->message_id);
333 g_slice_free (FlashInfo, fi);
334
335 /* remove the timeout */
336 return FALSE;
337 }
338
339 /**
340 * glade_utils_flash_message:
341 * @statusbar: The statusbar
342 * @context_id: The message context_id
343 * @format: The message to flash on the statusbar
344 *
345 * Flash a temporary message on the statusbar.
346 */
347 void
glade_util_flash_message(GtkWidget * statusbar,guint context_id,gchar * format,...)348 glade_util_flash_message (GtkWidget *statusbar,
349 guint context_id,
350 gchar *format,
351 ...)
352 {
353 va_list args;
354 FlashInfo *fi;
355 gchar *message;
356
357 g_return_if_fail (GTK_IS_STATUSBAR (statusbar));
358 g_return_if_fail (format != NULL);
359
360 va_start (args, format);
361 message = g_strdup_vprintf (format, args);
362 va_end (args);
363
364 fi = g_slice_new0 (FlashInfo);
365 fi->statusbar = GTK_STATUSBAR (statusbar);
366 fi->context_id = context_id;
367 fi->message_id = gtk_statusbar_push (fi->statusbar, fi->context_id, message);
368
369 g_timeout_add_seconds (flash_length, (GSourceFunc) remove_message_timeout,
370 fi);
371
372 g_free (message);
373 }
374
375 static gint
glade_util_compare_uline_labels(const gchar * labela,const gchar * labelb)376 glade_util_compare_uline_labels (const gchar *labela, const gchar *labelb)
377 {
378 for (;;)
379 {
380 gunichar c1, c2;
381
382 if (*labela == '\0')
383 return (*labelb == '\0') ? 0 : -1;
384 if (*labelb == '\0')
385 return 1;
386
387 c1 = g_utf8_get_char (labela);
388 if (c1 == '_')
389 {
390 labela = g_utf8_next_char (labela);
391 c1 = g_utf8_get_char (labela);
392 }
393
394 c2 = g_utf8_get_char (labelb);
395 if (c2 == '_')
396 {
397 labelb = g_utf8_next_char (labelb);
398 c2 = g_utf8_get_char (labelb);
399 }
400
401 if (c1 < c2)
402 return -1;
403 if (c1 > c2)
404 return 1;
405
406 labela = g_utf8_next_char (labela);
407 labelb = g_utf8_next_char (labelb);
408 }
409
410 /* Shouldn't be reached. */
411 return 0;
412 }
413
414 /**
415 * glade_util_compare_stock_labels:
416 * @a: a #gconstpointer to a #GtkStockItem
417 * @b: a #gconstpointer to a #GtkStockItem
418 *
419 * This is a #GCompareFunc that compares the labels of two stock items,
420 * ignoring any '_' characters. It isn't particularly efficient.
421 *
422 * Returns: negative value if @a < @b; zero if @a = @b;
423 * positive value if @a > @b
424 */
425 gint
glade_util_compare_stock_labels(gconstpointer a,gconstpointer b)426 glade_util_compare_stock_labels (gconstpointer a, gconstpointer b)
427 {
428 const gchar *stock_ida = a, *stock_idb = b;
429 GtkStockItem itema, itemb;
430 gboolean founda, foundb;
431 gint retval;
432
433 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
434 founda = gtk_stock_lookup (stock_ida, &itema);
435 foundb = gtk_stock_lookup (stock_idb, &itemb);
436 G_GNUC_END_IGNORE_DEPRECATIONS
437
438 if (founda)
439 {
440 if (!foundb)
441 retval = -1;
442 else
443 /* FIXME: Not ideal for UTF-8. */
444 retval = glade_util_compare_uline_labels (itema.label, itemb.label);
445 }
446 else
447 {
448 if (!foundb)
449 retval = 0;
450 else
451 retval = 1;
452 }
453
454 return retval;
455 }
456
457 /**
458 * glade_util_file_dialog_new:
459 * @title: dialog title
460 * @project: a #GladeProject used when saving
461 * @parent: a parent #GtkWindow for the dialog
462 * @action: a #GladeUtilFileDialogType to say if the dialog will open or save
463 *
464 * Returns: a "glade file" file chooser dialog. The caller is responsible
465 * for showing the dialog
466 */
467 GtkWidget *
glade_util_file_dialog_new(const gchar * title,GladeProject * project,GtkWindow * parent,GladeUtilFileDialogType action)468 glade_util_file_dialog_new (const gchar *title,
469 GladeProject *project,
470 GtkWindow *parent,
471 GladeUtilFileDialogType action)
472 {
473 GtkWidget *file_dialog;
474 GtkFileFilter *file_filter;
475
476 g_return_val_if_fail ((action == GLADE_FILE_DIALOG_ACTION_OPEN ||
477 action == GLADE_FILE_DIALOG_ACTION_SAVE), NULL);
478
479 g_return_val_if_fail ((action != GLADE_FILE_DIALOG_ACTION_SAVE ||
480 GLADE_IS_PROJECT (project)), NULL);
481
482 file_dialog = gtk_file_chooser_dialog_new (title, parent, action,
483 _("_Cancel"), GTK_RESPONSE_CANCEL,
484 action ==
485 GLADE_FILE_DIALOG_ACTION_OPEN ?
486 _("_Open") : _("_Save"),
487 GTK_RESPONSE_OK, NULL);
488
489 file_filter = gtk_file_filter_new ();
490 gtk_file_filter_add_pattern (file_filter, "*");
491 gtk_file_filter_set_name (file_filter, _("All Files"));
492 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (file_dialog), file_filter);
493
494 file_filter = gtk_file_filter_new ();
495 gtk_file_filter_add_pattern (file_filter, "*.glade");
496 gtk_file_filter_set_name (file_filter, _("Libglade Files"));
497 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (file_dialog), file_filter);
498
499 file_filter = gtk_file_filter_new ();
500 gtk_file_filter_add_pattern (file_filter, "*.ui");
501 gtk_file_filter_set_name (file_filter, _("GtkBuilder Files"));
502 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (file_dialog), file_filter);
503
504 file_filter = gtk_file_filter_new ();
505 gtk_file_filter_add_pattern (file_filter, "*.ui");
506 gtk_file_filter_add_pattern (file_filter, "*.glade");
507 gtk_file_filter_set_name (file_filter, _("All Glade Files"));
508 gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (file_dialog), file_filter);
509
510 gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (file_dialog), file_filter);
511
512 gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER
513 (file_dialog), TRUE);
514 gtk_dialog_set_default_response (GTK_DIALOG (file_dialog), GTK_RESPONSE_OK);
515
516 return file_dialog;
517 }
518
519 /**
520 * glade_util_replace:
521 * @str: a string
522 * @a: a #gchar
523 * @b: a #gchar
524 *
525 * Replaces each occurance of the character @a in @str to @b.
526 */
527 void
glade_util_replace(gchar * str,gchar a,gchar b)528 glade_util_replace (gchar *str, gchar a, gchar b)
529 {
530 g_return_if_fail (str != NULL);
531
532 while (*str != 0)
533 {
534 if (*str == a)
535 *str = b;
536
537 str = g_utf8_next_char (str);
538 }
539 }
540
541 /**
542 * _glade_util_strreplace:
543 * @str: a string
544 * @free_str: wheter to free str or not
545 * @key: the key string to search for
546 * @replacement: string to replace key
547 *
548 * Replaces each occurance of the string @key in @str to @replacement.
549 */
550 gchar *
_glade_util_strreplace(gchar * str,gboolean free_str,const gchar * key,const gchar * replacement)551 _glade_util_strreplace (gchar *str,
552 gboolean free_str,
553 const gchar *key,
554 const gchar *replacement)
555 {
556 gchar *retval, **array;
557
558 if ((array = g_strsplit (str, key, -1)) && array[0])
559 retval = g_strjoinv (replacement, array);
560 else
561 retval = g_strdup (str);
562
563 g_strfreev (array);
564
565 if (free_str)
566 g_free (str);
567
568 return retval;
569 }
570
571 /**
572 * glade_util_read_prop_name:
573 * @str: a string
574 *
575 * Return a usable version of a property identifier as found
576 * in a freshly parserd #GladeInterface
577 */
578 gchar *
glade_util_read_prop_name(const gchar * str)579 glade_util_read_prop_name (const gchar *str)
580 {
581 gchar *id;
582
583 g_return_val_if_fail (str != NULL, NULL);
584
585 id = g_strdup (str);
586
587 glade_util_replace (id, '_', '-');
588
589 return id;
590 }
591
592
593 /**
594 * glade_util_duplicate_underscores:
595 * @name: a string
596 *
597 * Duplicates @name, but the copy has two underscores in place of any single
598 * underscore in the original.
599 *
600 * Returns: a newly allocated string
601 */
602 gchar *
glade_util_duplicate_underscores(const gchar * name)603 glade_util_duplicate_underscores (const gchar *name)
604 {
605 const gchar *tmp;
606 const gchar *last_tmp = name;
607 gchar *underscored_name = g_malloc (strlen (name) * 2 + 1);
608 gchar *tmp_underscored = underscored_name;
609
610 for (tmp = last_tmp; *tmp; tmp = g_utf8_next_char (tmp))
611 {
612 if (*tmp == '_')
613 {
614 memcpy (tmp_underscored, last_tmp, tmp - last_tmp + 1);
615 tmp_underscored += tmp - last_tmp + 1;
616 last_tmp = tmp + 1;
617 *tmp_underscored++ = '_';
618 }
619 }
620
621 memcpy (tmp_underscored, last_tmp, tmp - last_tmp + 1);
622
623 return underscored_name;
624 }
625
626 /*
627 * taken from gtk... maybe someday we can convince them to
628 * expose gtk_container_get_all_children
629 */
630 static void
gtk_container_children_callback(GtkWidget * widget,gpointer client_data)631 gtk_container_children_callback (GtkWidget *widget, gpointer client_data)
632 {
633 GList **children;
634
635 children = (GList **) client_data;
636 if (!g_list_find (*children, widget))
637 *children = g_list_prepend (*children, widget);
638 }
639
640 /**
641 * glade_util_container_get_all_children:
642 * @container: a #GtkContainer
643 *
644 * Use this to itterate over all children in a GtkContainer,
645 * as it used _forall() instead of _foreach() (and the GTK+ version
646 * of this function is simply not exposed).
647 *
648 * Note that glade_widget_class_get_children() is the high-level
649 * abstraction and will usually end up calling this function.
650 *
651 * Returns: a #GList giving the contents of @container
652 */
653 GList *
glade_util_container_get_all_children(GtkContainer * container)654 glade_util_container_get_all_children (GtkContainer *container)
655 {
656 GList *children = NULL;
657
658 g_return_val_if_fail (GTK_IS_CONTAINER (container), NULL);
659
660 gtk_container_forall (container, gtk_container_children_callback, &children);
661 gtk_container_foreach (container, gtk_container_children_callback, &children);
662
663 /* Preserve the natural order by reversing the list */
664 return g_list_reverse (children);
665 }
666
667 /**
668 * glade_util_count_placeholders:
669 * @parent: a #GladeWidget
670 *
671 * Returns: the amount of #GladePlaceholders parented by @parent
672 */
673 gint
glade_util_count_placeholders(GladeWidget * parent)674 glade_util_count_placeholders (GladeWidget *parent)
675 {
676 gint placeholders = 0;
677 GList *list, *children;
678
679 /* count placeholders */
680 if ((children =
681 glade_widget_adaptor_get_children (glade_widget_get_adaptor (parent),
682 glade_widget_get_object (parent))) != NULL)
683 {
684 for (list = children; list && list->data; list = list->next)
685 {
686 if (GLADE_IS_PLACEHOLDER (list->data))
687 placeholders++;
688 }
689 g_list_free (children);
690 }
691
692 return placeholders;
693 }
694
695 static GtkTreeIter *
glade_util_find_iter(GtkTreeModel * model,GtkTreeIter * iter,GladeWidget * findme,gint column)696 glade_util_find_iter (GtkTreeModel *model,
697 GtkTreeIter *iter,
698 GladeWidget *findme,
699 gint column)
700 {
701 GtkTreeIter *retval = NULL;
702 GObject *object = NULL;
703 GtkTreeIter *next;
704
705 g_return_val_if_fail (GTK_IS_TREE_MODEL (model), NULL);
706 g_return_val_if_fail (iter != NULL, NULL);
707
708 next = gtk_tree_iter_copy (iter);
709 g_return_val_if_fail (next != NULL, NULL);
710
711 while (retval == NULL)
712 {
713 GladeWidget *widget;
714
715 gtk_tree_model_get (model, next, column, &object, -1);
716 if (object &&
717 gtk_tree_model_get_column_type (model, column) == G_TYPE_OBJECT)
718 g_object_unref (object);
719
720 widget = glade_widget_get_from_gobject (object);
721
722 if (widget == findme)
723 {
724 retval = gtk_tree_iter_copy (next);
725 break;
726 }
727 else if (glade_widget_is_ancestor (findme, widget))
728 {
729 if (gtk_tree_model_iter_has_child (model, next))
730 {
731 GtkTreeIter child;
732 gtk_tree_model_iter_children (model, &child, next);
733 if ((retval = glade_util_find_iter
734 (model, &child, findme, column)) != NULL)
735 break;
736 }
737
738 /* Only search the branches where the searched widget
739 * is actually a child of the this row, optimize the
740 * searching this way
741 */
742 break;
743 }
744
745 if (!gtk_tree_model_iter_next (model, next))
746 break;
747 }
748 gtk_tree_iter_free (next);
749
750 return retval;
751 }
752
753 /**
754 * glade_util_find_iter_by_widget:
755 * @model: a #GtkTreeModel
756 * @findme: a #GladeWidget
757 * @column: a #gint
758 *
759 * Looks through @model for the #GtkTreeIter corresponding to
760 * @findme under @column.
761 *
762 * Returns: a newly allocated #GtkTreeIter from @model corresponding
763 * to @findme which should be freed with gtk_tree_iter_free()
764 *
765 */
766 GtkTreeIter *
glade_util_find_iter_by_widget(GtkTreeModel * model,GladeWidget * findme,gint column)767 glade_util_find_iter_by_widget (GtkTreeModel *model,
768 GladeWidget *findme,
769 gint column)
770 {
771 GtkTreeIter iter;
772 if (gtk_tree_model_get_iter_first (model, &iter))
773 {
774 return glade_util_find_iter (model, &iter, findme, column);
775 }
776 return NULL;
777 }
778
779 /**
780 * glade_util_purify_list:
781 * @list: A #GList
782 *
783 * Returns: A newly allocated version of @list with no
784 * duplicate data entries
785 */
786 GList *
glade_util_purify_list(GList * list)787 glade_util_purify_list (GList * list)
788 {
789 GList *l, *newlist = NULL;
790
791 for (l = list; l; l = l->next)
792 if (!g_list_find (newlist, l->data))
793 newlist = g_list_prepend (newlist, l->data);
794
795 g_list_free (list);
796
797 return g_list_reverse (newlist);
798 }
799
800 /**
801 * glade_util_added_in_list:
802 * @old_list: the old #GList
803 * @new_list: the new #GList
804 *
805 * Returns: A newly allocated #GList of elements that
806 * are in @new but not in @old
807 *
808 */
809 GList *
glade_util_added_in_list(GList * old_list,GList * new_list)810 glade_util_added_in_list (GList *old_list, GList *new_list)
811 {
812 GList *added = NULL, *list;
813
814 for (list = new_list; list; list = list->next)
815 {
816 if (!g_list_find (old_list, list->data))
817 added = g_list_prepend (added, list->data);
818 }
819
820 return g_list_reverse (added);
821 }
822
823 /**
824 * glade_util_removed_from_list:
825 * @old_list: the old #GList
826 * @new_list: the new #GList
827 *
828 * Returns: A newly allocated #GList of elements that
829 * are in @old no longer in @new
830 *
831 */
832 GList *
glade_util_removed_from_list(GList * old_list,GList * new_list)833 glade_util_removed_from_list (GList *old_list, GList *new_list)
834 {
835 GList *added = NULL, *list;
836
837 for (list = old_list; list; list = list->next)
838 {
839 if (!g_list_find (new_list, list->data))
840 added = g_list_prepend (added, list->data);
841 }
842
843 return g_list_reverse (added);
844 }
845
846
847 /**
848 * glade_util_canonical_path:
849 * @path: any path that may contain ".." or "." components
850 *
851 * Returns: an absolute path to the specified file or directory
852 * that contains no ".." or "." components (this does
853 * not call readlink like realpath() does).
854 *
855 * Note: on some systems; I think its possible that we dont have
856 * permission to execute in the directory in which the glade
857 * file resides; I decided finally to do it this way anyway
858 * since libc's realpath() does exactly the same.
859 */
860 gchar *
glade_util_canonical_path(const gchar * path)861 glade_util_canonical_path (const gchar *path)
862 {
863 gchar *orig_dir, *dirname, *basename, *direct_dir, *direct_name = NULL;
864
865 g_return_val_if_fail (path != NULL, NULL);
866
867 basename = g_path_get_basename (path);
868
869 if ((orig_dir = g_get_current_dir ()) != NULL)
870 {
871 if ((dirname = g_path_get_dirname (path)) != NULL)
872 {
873 if (g_chdir (dirname) == 0)
874 {
875 if ((direct_dir = g_get_current_dir ()) != NULL)
876 {
877 direct_name = g_build_filename (direct_dir, basename, NULL);
878 g_free (direct_dir);
879 }
880 else
881 g_warning ("g_path");
882
883 if (g_chdir (orig_dir) != 0)
884 g_warning ("Unable to chdir back to %s directory (%s)",
885 orig_dir, g_strerror (errno));
886
887 }
888 else
889 g_warning ("Unable to chdir to %s directory (%s)",
890 dirname, g_strerror (errno));
891
892 g_free (dirname);
893 }
894 else
895 g_warning ("Unable to get directory component of %s\n", path);
896 g_free (orig_dir);
897 }
898
899 if (basename)
900 g_free (basename);
901
902 return direct_name;
903 }
904
905 static GModule *
try_load_library(const gchar * library_path,const gchar * library_name)906 try_load_library (const gchar *library_path, const gchar *library_name)
907 {
908 gchar *path = g_module_build_path (library_path, library_name);
909 GModule *module = NULL;
910
911 if (!library_path || g_file_test (path, G_FILE_TEST_EXISTS))
912 {
913 if (!(module = g_module_open (path, G_MODULE_BIND_LAZY)))
914 g_warning ("Failed to load %s: %s", path, g_module_error ());
915 }
916
917 g_free (path);
918
919 return module;
920 }
921
922 /**
923 * glade_util_load_library:
924 * @library_name: name of the library
925 *
926 * Loads the named library from the Glade modules and lib directory or failing that
927 * from the standard platform specific directories. (Including /usr/local/lib for unices)
928 *
929 * The @library_name should not include any platform specifix prefix or suffix,
930 * those are automatically added, if needed, by g_module_build_path()
931 *
932 * Returns: a #GModule on success, or %NULL on failure.
933 */
934 GModule *
glade_util_load_library(const gchar * library_name)935 glade_util_load_library (const gchar *library_name)
936 {
937 GModule *module = NULL;
938 const gchar *search_path;
939 gint i;
940
941 if ((search_path = g_getenv (GLADE_ENV_MODULE_PATH)) != NULL)
942 {
943 gchar **split;
944
945 if ((split = g_strsplit (search_path, ":", 0)) != NULL)
946 {
947 for (i = 0; split[i] != NULL; i++)
948 if ((module = try_load_library (split[i], library_name)) != NULL)
949 break;
950
951 g_strfreev (split);
952 }
953 }
954
955 if (g_getenv (GLADE_ENV_TESTING) == NULL && !module)
956 {
957 const gchar *paths[] = { glade_app_get_modules_dir (),
958 glade_app_get_lib_dir (),
959 #ifndef G_OS_WIN32 /* Try local lib dir on Unices */
960 "/usr/local/lib",
961 #endif
962 NULL}; /* Use default system paths */
963
964
965 for (i = 0; i < G_N_ELEMENTS (paths); i++)
966 if ((module = try_load_library (paths[i], library_name)) != NULL)
967 break;
968 }
969
970 return module;
971 }
972
973 /**
974 * glade_util_file_is_writeable:
975 * @path: the path to the file
976 *
977 * Checks whether the file at @path is writeable
978 *
979 * Returns: TRUE if file is writeable
980 */
981 gboolean
glade_util_file_is_writeable(const gchar * path)982 glade_util_file_is_writeable (const gchar *path)
983 {
984 GIOChannel *channel;
985 g_return_val_if_fail (path != NULL, FALSE);
986
987 /* The only way to really know if the file is writable */
988 if ((channel = g_io_channel_new_file (path, "a+", NULL)) != NULL)
989 {
990 g_io_channel_unref (channel);
991 return TRUE;
992 }
993 return FALSE;
994 }
995
996 /**
997 * glade_util_have_devhelp:
998 *
999 * Returns: whether the devhelp module is loaded
1000 */
1001 gboolean
glade_util_have_devhelp(void)1002 glade_util_have_devhelp (void)
1003 {
1004 static gint have_devhelp = -1;
1005 gchar *ptr;
1006 gint cnt, ret, major, minor;
1007 GError *error = NULL;
1008
1009 #define DEVHELP_OLD_MESSAGE \
1010 "The DevHelp installed on your system is too old, " \
1011 "devhelp feature will be disabled."
1012
1013 #define DEVHELP_MISSING_MESSAGE \
1014 "No DevHelp installed on your system, " \
1015 "devhelp feature will be disabled."
1016
1017 if (have_devhelp >= 0)
1018 return have_devhelp;
1019
1020 have_devhelp = 0;
1021
1022 if ((ptr = g_find_program_in_path ("devhelp")) != NULL)
1023 {
1024 g_free (ptr);
1025
1026 if (g_spawn_command_line_sync ("devhelp --version",
1027 &ptr, NULL, &ret, &error))
1028 {
1029 /* If we have a successfull return code.. parse the output.
1030 */
1031 if (ret == 0)
1032 {
1033 gchar name[16];
1034 if ((cnt = sscanf (ptr, "%15s %d.%d\n",
1035 name, &major, &minor)) == 3)
1036 {
1037 /* Devhelp 0.12 required.
1038 */
1039 if (major >= 2 || (major >= 0 && minor >= 12))
1040 have_devhelp = 1;
1041 else
1042 g_message (DEVHELP_OLD_MESSAGE);
1043 }
1044 else
1045
1046 {
1047 if (ptr != NULL || strlen (ptr) > 0)
1048 g_warning ("devhelp had unparsable output: "
1049 "'%s' (parsed %d elements)", ptr, cnt);
1050 else
1051 g_message (DEVHELP_OLD_MESSAGE);
1052 }
1053 }
1054 else
1055 g_warning ("devhelp had bad return code: '%d'", ret);
1056 }
1057 else
1058 {
1059 g_warning ("Error trying to launch devhelp: %s", error->message);
1060 g_error_free (error);
1061 }
1062 }
1063 else
1064 g_message (DEVHELP_MISSING_MESSAGE);
1065
1066 return have_devhelp;
1067 }
1068
1069 /**
1070 * glade_util_get_devhelp_icon:
1071 * @size: the preferred icon size
1072 *
1073 * Creates an image displaying the devhelp icon.
1074 *
1075 * Returns: a #GtkImage
1076 */
1077 GtkWidget *
glade_util_get_devhelp_icon(GtkIconSize size)1078 glade_util_get_devhelp_icon (GtkIconSize size)
1079 {
1080 GtkIconTheme *icon_theme;
1081 GdkScreen *screen;
1082 GtkWidget *image;
1083 gchar *path;
1084
1085 image = gtk_image_new ();
1086 screen = gtk_widget_get_screen (GTK_WIDGET (image));
1087 icon_theme = gtk_icon_theme_get_for_screen (screen);
1088
1089 if (gtk_icon_theme_has_icon (icon_theme, GLADE_DEVHELP_ICON_NAME))
1090 {
1091 gtk_image_set_from_icon_name (GTK_IMAGE (image), GLADE_DEVHELP_ICON_NAME,
1092 size);
1093 }
1094 else
1095 {
1096 path =
1097 g_build_filename (glade_app_get_pixmaps_dir (),
1098 GLADE_DEVHELP_FALLBACK_ICON_FILE, NULL);
1099
1100 gtk_image_set_from_file (GTK_IMAGE (image), path);
1101
1102 g_free (path);
1103 }
1104
1105 return image;
1106 }
1107
1108 /**
1109 * glade_util_search_devhep:
1110 * @devhelp: the devhelp widget created by the devhelp module.
1111 * @book: the devhelp book (or %NULL)
1112 * @page: the page in the book (or %NULL)
1113 * @search: the search string (or %NULL)
1114 *
1115 * Envokes devhelp with the appropriate search string
1116 *
1117 */
1118 void
glade_util_search_devhelp(const gchar * book,const gchar * page,const gchar * search)1119 glade_util_search_devhelp (const gchar *book,
1120 const gchar *page,
1121 const gchar *search)
1122 {
1123 GError *error = NULL;
1124 gchar *book_comm = NULL, *page_comm = NULL, *search_comm = NULL;
1125 gchar *string;
1126
1127 g_return_if_fail (glade_util_have_devhelp ());
1128
1129 if (book)
1130 book_comm = g_strdup_printf ("book:%s", book);
1131 if (page)
1132 page_comm = g_strdup_printf (" page:%s", page);
1133 if (search)
1134 search_comm = g_strdup_printf (" %s", search);
1135
1136 string = g_strdup_printf ("devhelp -s \"%s%s%s\"",
1137 book_comm ? book_comm : "",
1138 page_comm ? page_comm : "",
1139 search_comm ? search_comm : "");
1140
1141 if (g_spawn_command_line_async (string, &error) == FALSE)
1142 {
1143 g_warning ("Error envoking devhelp: %s", error->message);
1144 g_error_free (error);
1145 }
1146
1147 g_free (string);
1148 if (book_comm)
1149 g_free (book_comm);
1150 if (page_comm)
1151 g_free (page_comm);
1152 if (search_comm)
1153 g_free (search_comm);
1154 }
1155
1156 GtkWidget *
glade_util_get_placeholder_from_pointer(GtkContainer * container)1157 glade_util_get_placeholder_from_pointer (GtkContainer *container)
1158 {
1159 GdkDeviceManager *manager;
1160 GdkDisplay *display;
1161 GdkDevice *device;
1162 GdkWindow *window;
1163
1164 if (((display = gtk_widget_get_display (GTK_WIDGET (container))) ||
1165 (display = gdk_display_get_default ())) &&
1166 (manager = gdk_display_get_device_manager (display)) &&
1167 (device = gdk_device_manager_get_client_pointer (manager)) &&
1168 (window = gdk_device_get_window_at_position (device, NULL, NULL)))
1169 {
1170 gpointer widget;
1171 gdk_window_get_user_data (window, &widget);
1172
1173 return GLADE_IS_PLACEHOLDER (widget) ? GTK_WIDGET (widget) : NULL;
1174 }
1175
1176 return NULL;
1177 }
1178
1179 /**
1180 * glade_util_object_is_loading:
1181 * @object: A #GObject
1182 *
1183 * Returns: Whether the object's project is being loaded or not.
1184 *
1185 */
1186 gboolean
glade_util_object_is_loading(GObject * object)1187 glade_util_object_is_loading (GObject *object)
1188 {
1189 GladeProject *project;
1190 GladeWidget *widget;
1191
1192 g_return_val_if_fail (G_IS_OBJECT (object), FALSE);
1193
1194 widget = glade_widget_get_from_gobject (object);
1195 g_return_val_if_fail (GLADE_IS_WIDGET (widget), FALSE);
1196
1197 project = glade_widget_get_project (widget);
1198
1199 return project && glade_project_is_loading (project);
1200 }
1201
1202 /**
1203 * glade_util_url_show:
1204 * @url: An URL to display
1205 *
1206 * Portable function for showing an URL @url in a web browser.
1207 *
1208 * Returns: TRUE if a web browser was successfully launched, or FALSE
1209 *
1210 */
1211 gboolean
glade_util_url_show(const gchar * url)1212 glade_util_url_show (const gchar *url)
1213 {
1214 GtkWidget *widget;
1215 GError *error = NULL;
1216 gboolean ret;
1217
1218 g_return_val_if_fail (url != NULL, FALSE);
1219
1220 widget = glade_app_get_window ();
1221
1222 ret = gtk_show_uri (gtk_widget_get_screen (widget),
1223 url, gtk_get_current_event_time (), &error);
1224 if (error != NULL)
1225 {
1226 GtkWidget *dialog_widget;
1227
1228 dialog_widget = gtk_message_dialog_new (GTK_WINDOW (widget),
1229 GTK_DIALOG_DESTROY_WITH_PARENT,
1230 GTK_MESSAGE_ERROR,
1231 GTK_BUTTONS_CLOSE,
1232 "%s", _("Could not show link:"));
1233 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG
1234 (dialog_widget), "%s",
1235 error->message);
1236 g_error_free (error);
1237
1238 g_signal_connect (dialog_widget, "response",
1239 G_CALLBACK (gtk_widget_destroy), NULL);
1240
1241 gtk_window_present (GTK_WINDOW (dialog_widget));
1242 }
1243
1244 return ret;
1245 }
1246
1247 /**
1248 * glade_util_get_file_mtime:
1249 * @filename: A filename
1250 * @error: return location for errors
1251 *
1252 * Gets the UTC modification time of file @filename.
1253 *
1254 * Returns: The mtime of the file, or %0 if the file attributes
1255 * could not be read.
1256 */
1257 time_t
glade_util_get_file_mtime(const gchar * filename,GError ** error)1258 glade_util_get_file_mtime (const gchar *filename, GError **error)
1259 {
1260 struct stat info;
1261 gint retval;
1262
1263 retval = g_stat (filename, &info);
1264
1265 if (retval != 0)
1266 {
1267 g_set_error (error,
1268 G_FILE_ERROR,
1269 g_file_error_from_errno (errno),
1270 "could not stat file '%s': %s", filename,
1271 g_strerror (errno));
1272 return (time_t) 0;
1273 }
1274 else
1275 {
1276 return info.st_mtime;
1277 }
1278 }
1279
1280 gchar *
glade_util_filename_to_icon_name(const gchar * value)1281 glade_util_filename_to_icon_name (const gchar *value)
1282 {
1283 gchar *icon_name, *p;
1284 g_return_val_if_fail (value && value[0], NULL);
1285
1286 icon_name = g_strdup_printf ("glade-generated-%s", value);
1287
1288 if ((p = strrchr (icon_name, '.')) != NULL)
1289 *p = '-';
1290
1291 return icon_name;
1292 }
1293
1294 gchar *
glade_util_icon_name_to_filename(const gchar * value)1295 glade_util_icon_name_to_filename (const gchar *value)
1296 {
1297 /* sscanf makes us allocate a buffer */
1298 gchar filename[FILENAME_MAX], *p;
1299 g_return_val_if_fail (value && value[0], NULL);
1300
1301 sscanf (value, "glade-generated-%s", filename);
1302
1303 /* XXX: Filenames without an extention will evidently
1304 * break here
1305 */
1306 if ((p = strrchr (filename, '-')) != NULL)
1307 *p = '.';
1308
1309 return g_strdup (filename);
1310 }
1311
1312 gint
glade_utils_enum_value_from_string(GType enum_type,const gchar * strval)1313 glade_utils_enum_value_from_string (GType enum_type, const gchar *strval)
1314 {
1315 gint value = 0;
1316 const gchar *displayable;
1317 GValue *gvalue;
1318
1319 g_return_val_if_fail (strval && strval[0], 0);
1320
1321 if (((displayable =
1322 glade_get_value_from_displayable (enum_type, strval)) != NULL &&
1323 (gvalue =
1324 glade_utils_value_from_string (enum_type, displayable, NULL)) != NULL) ||
1325 (gvalue =
1326 glade_utils_value_from_string (enum_type, strval, NULL)) != NULL)
1327 {
1328 value = g_value_get_enum (gvalue);
1329 g_value_unset (gvalue);
1330 g_free (gvalue);
1331 }
1332 return value;
1333 }
1334
1335 static gchar *
glade_utils_enum_string_from_value_real(GType enum_type,gint value,gboolean displayable)1336 glade_utils_enum_string_from_value_real (GType enum_type,
1337 gint value,
1338 gboolean displayable)
1339 {
1340 GValue gvalue = { 0, };
1341 gchar *string;
1342
1343 g_value_init (&gvalue, enum_type);
1344 g_value_set_enum (&gvalue, value);
1345
1346 string = glade_utils_string_from_value (&gvalue);
1347 g_value_unset (&gvalue);
1348
1349 if (displayable && string)
1350 {
1351 const gchar *dstring = glade_get_displayable_value (enum_type, string);
1352 if (dstring)
1353 {
1354 g_free (string);
1355 return g_strdup (dstring);
1356 }
1357 }
1358
1359 return string;
1360 }
1361
1362 gchar *
glade_utils_enum_string_from_value(GType enum_type,gint value)1363 glade_utils_enum_string_from_value (GType enum_type, gint value)
1364 {
1365 return glade_utils_enum_string_from_value_real (enum_type, value, FALSE);
1366 }
1367
1368 gchar *
glade_utils_enum_string_from_value_displayable(GType enum_type,gint value)1369 glade_utils_enum_string_from_value_displayable (GType enum_type, gint value)
1370 {
1371 return glade_utils_enum_string_from_value_real (enum_type, value, TRUE);
1372 }
1373
1374
1375 gint
glade_utils_flags_value_from_string(GType flags_type,const gchar * strval)1376 glade_utils_flags_value_from_string (GType flags_type, const gchar *strval)
1377 {
1378 gint value = 0;
1379 const gchar *displayable;
1380 GValue *gvalue;
1381
1382 g_return_val_if_fail (strval && strval[0], 0);
1383
1384 if (((displayable =
1385 glade_get_value_from_displayable (flags_type, strval)) != NULL &&
1386 (gvalue =
1387 glade_utils_value_from_string (flags_type, displayable, NULL)) != NULL) ||
1388 (gvalue =
1389 glade_utils_value_from_string (flags_type, strval, NULL)) != NULL)
1390 {
1391 value = g_value_get_flags (gvalue);
1392 g_value_unset (gvalue);
1393 g_free (gvalue);
1394 }
1395 return value;
1396 }
1397
1398 static gchar *
glade_utils_flags_string_from_value_real(GType flags_type,gint value,gboolean displayable)1399 glade_utils_flags_string_from_value_real (GType flags_type,
1400 gint value,
1401 gboolean displayable)
1402 {
1403 GValue gvalue = { 0, };
1404 gchar *string;
1405
1406 g_value_init (&gvalue, flags_type);
1407 g_value_set_flags (&gvalue, value);
1408
1409 string = glade_utils_string_from_value (&gvalue);
1410 g_value_unset (&gvalue);
1411
1412 if (displayable && string)
1413 {
1414 const gchar *dstring = glade_get_displayable_value (flags_type, string);
1415 if (dstring)
1416 {
1417 g_free (string);
1418 return g_strdup (dstring);
1419 }
1420 }
1421
1422 return string;
1423 }
1424
1425 gchar *
glade_utils_flags_string_from_value(GType flags_type,gint value)1426 glade_utils_flags_string_from_value (GType flags_type, gint value)
1427 {
1428 return glade_utils_flags_string_from_value_real (flags_type, value, FALSE);
1429
1430 }
1431
1432
1433 gchar *
glade_utils_flags_string_from_value_displayable(GType flags_type,gint value)1434 glade_utils_flags_string_from_value_displayable (GType flags_type, gint value)
1435 {
1436 return glade_utils_flags_string_from_value_real (flags_type, value, TRUE);
1437 }
1438
1439
1440 /* A hash table of generically created property classes for
1441 * fundamental types, so we can easily use glade's conversion
1442 * system without using properties (only GTypes)
1443 */
1444 static GHashTable *generic_property_classes = NULL;
1445
1446
1447 static gboolean
utils_gtype_equal(gconstpointer v1,gconstpointer v2)1448 utils_gtype_equal (gconstpointer v1, gconstpointer v2)
1449 {
1450 return *((const GType *) v1) == *((const GType *) v2);
1451 }
1452
1453 static guint
utils_gtype_hash(gconstpointer v)1454 utils_gtype_hash (gconstpointer v)
1455 {
1456 return *(const GType *) v;
1457 }
1458
1459
1460 static GladePropertyClass *
pclass_from_gtype(GType type)1461 pclass_from_gtype (GType type)
1462 {
1463 GladePropertyClass *property_class = NULL;
1464 GParamSpec *pspec = NULL;
1465
1466 if (!generic_property_classes)
1467 generic_property_classes =
1468 g_hash_table_new_full (utils_gtype_hash, utils_gtype_equal, g_free,
1469 (GDestroyNotify) glade_property_class_free);
1470
1471 property_class = g_hash_table_lookup (generic_property_classes, &type);
1472
1473 if (!property_class)
1474 {
1475 /* Support enum and flag types, and a hardcoded list of fundamental types */
1476 if (type == G_TYPE_CHAR)
1477 pspec = g_param_spec_char ("dummy", "dummy", "dummy",
1478 G_MININT8, G_MAXINT8, 0,
1479 G_PARAM_READABLE | G_PARAM_WRITABLE);
1480 else if (type == G_TYPE_UCHAR)
1481 pspec = g_param_spec_char ("dummy", "dummy", "dummy",
1482 0, G_MAXUINT8, 0,
1483 G_PARAM_READABLE | G_PARAM_WRITABLE);
1484 else if (type == G_TYPE_BOOLEAN)
1485 pspec = g_param_spec_boolean ("dummy", "dummy", "dummy",
1486 FALSE,
1487 G_PARAM_READABLE | G_PARAM_WRITABLE);
1488 else if (type == G_TYPE_INT)
1489 pspec = g_param_spec_int ("dummy", "dummy", "dummy",
1490 G_MININT, G_MAXINT, 0,
1491 G_PARAM_READABLE | G_PARAM_WRITABLE);
1492 else if (type == G_TYPE_UINT)
1493 pspec = g_param_spec_uint ("dummy", "dummy", "dummy",
1494 0, G_MAXUINT, 0,
1495 G_PARAM_READABLE | G_PARAM_WRITABLE);
1496 else if (type == G_TYPE_LONG)
1497 pspec = g_param_spec_long ("dummy", "dummy", "dummy",
1498 G_MINLONG, G_MAXLONG, 0,
1499 G_PARAM_READABLE | G_PARAM_WRITABLE);
1500 else if (type == G_TYPE_ULONG)
1501 pspec = g_param_spec_ulong ("dummy", "dummy", "dummy",
1502 0, G_MAXULONG, 0,
1503 G_PARAM_READABLE | G_PARAM_WRITABLE);
1504 else if (type == G_TYPE_INT64)
1505 pspec = g_param_spec_int64 ("dummy", "dummy", "dummy",
1506 G_MININT64, G_MAXINT64, 0,
1507 G_PARAM_READABLE | G_PARAM_WRITABLE);
1508 else if (type == G_TYPE_UINT64)
1509 pspec = g_param_spec_uint64 ("dummy", "dummy", "dummy",
1510 0, G_MAXUINT64, 0,
1511 G_PARAM_READABLE | G_PARAM_WRITABLE);
1512 else if (type == G_TYPE_FLOAT)
1513 pspec = g_param_spec_float ("dummy", "dummy", "dummy",
1514 G_MINFLOAT, G_MAXFLOAT, 1.0F,
1515 G_PARAM_READABLE | G_PARAM_WRITABLE);
1516 else if (type == G_TYPE_DOUBLE)
1517 pspec = g_param_spec_double ("dummy", "dummy", "dummy",
1518 G_MINDOUBLE, G_MAXDOUBLE, 1.0F,
1519 G_PARAM_READABLE | G_PARAM_WRITABLE);
1520 else if (type == G_TYPE_STRING)
1521 pspec = g_param_spec_string ("dummy", "dummy", "dummy",
1522 NULL, G_PARAM_READABLE | G_PARAM_WRITABLE);
1523 else if (type == G_TYPE_OBJECT || g_type_is_a (type, G_TYPE_OBJECT))
1524 pspec = g_param_spec_object ("dummy", "dummy", "dummy",
1525 type, G_PARAM_READABLE | G_PARAM_WRITABLE);
1526 else if (G_TYPE_IS_ENUM (type))
1527 {
1528 GEnumClass *eclass = g_type_class_ref (type);
1529 pspec = g_param_spec_enum ("dummy", "dummy", "dummy",
1530 type, eclass->minimum,
1531 G_PARAM_READABLE | G_PARAM_WRITABLE);
1532 g_type_class_unref (eclass);
1533 }
1534 else if (G_TYPE_IS_FLAGS (type))
1535 pspec = g_param_spec_flags ("dummy", "dummy", "dummy",
1536 type, 0,
1537 G_PARAM_READABLE | G_PARAM_WRITABLE);
1538
1539 if (pspec)
1540 {
1541 if ((property_class =
1542 glade_property_class_new_from_spec_full (NULL, pspec,
1543 FALSE)) != NULL)
1544 {
1545 /* XXX If we ever free the hash table, property classes wont touch
1546 * the allocated pspecs, so they would theoretically be leaked.
1547 */
1548 g_hash_table_insert (generic_property_classes,
1549 g_memdup (&type, sizeof (GType)),
1550 property_class);
1551 }
1552 else
1553 g_warning ("Unable to create property class for type %s",
1554 g_type_name (type));
1555 }
1556 else
1557 g_warning ("No generic conversion support for type %s",
1558 g_type_name (type));
1559 }
1560 return property_class;
1561 }
1562
1563 /**
1564 * glade_utils_value_from_string:
1565 * @type: a #GType to convert with
1566 * @string: the string to convert
1567 * @project: the #GladeProject to look for formats of object names when needed
1568 * @widget: if the value is a gobject, this #GladeWidget will be used to look
1569 * for an object in the same widget tree.
1570 *
1571 * Allocates and sets a #GValue of type @type
1572 * set to @string (using glade conversion routines)
1573 *
1574 * Returns: A newly allocated and set #GValue
1575 */
1576 GValue *
glade_utils_value_from_string(GType type,const gchar * string,GladeProject * project)1577 glade_utils_value_from_string (GType type,
1578 const gchar *string,
1579 GladeProject *project)
1580 {
1581 GladePropertyClass *pclass;
1582
1583 g_return_val_if_fail (type != G_TYPE_INVALID, NULL);
1584 g_return_val_if_fail (string != NULL, NULL);
1585
1586 if ((pclass = pclass_from_gtype (type)) != NULL)
1587 return glade_property_class_make_gvalue_from_string (pclass, string, project);
1588
1589 return NULL;
1590 }
1591
1592 /**
1593 * glade_utils_boolean_from_string:
1594 * @string: the string to convert
1595 * @value: return location
1596 *
1597 * Parse a boolean value
1598 *
1599 * Returns: True if there was an error on the conversion.
1600 */
1601 gboolean
glade_utils_boolean_from_string(const gchar * string,gboolean * value)1602 glade_utils_boolean_from_string (const gchar *string, gboolean *value)
1603 {
1604 if (string)
1605 {
1606 const gchar *c = string;
1607
1608 /* Skip white spaces */
1609 while (g_ascii_isspace (*c))
1610 c++;
1611
1612 /* We only need the first char */
1613 switch (*c)
1614 {
1615 case '1':
1616 case 't':
1617 case 'T':
1618 case 'y':
1619 case 'Y':
1620 if (value)
1621 *value = TRUE;
1622 return FALSE;
1623 break;
1624
1625 case '0':
1626 case 'f':
1627 case 'F':
1628 case 'n':
1629 case 'N':
1630 if (value)
1631 *value = FALSE;
1632 return FALSE;
1633 break;
1634 }
1635 }
1636
1637 return TRUE;
1638 }
1639
1640 /**
1641 * glade_utils_string_from_value:
1642 * @value: a #GValue to convert
1643 *
1644 * Serializes #GValue into a string
1645 * (using glade conversion routines)
1646 *
1647 * Returns: A newly allocated string
1648 */
1649 gchar *
glade_utils_string_from_value(const GValue * value)1650 glade_utils_string_from_value (const GValue *value)
1651 {
1652 GladePropertyClass *pclass;
1653
1654 g_return_val_if_fail (value != NULL, NULL);
1655
1656 if ((pclass = pclass_from_gtype (G_VALUE_TYPE (value))) != NULL)
1657 return glade_property_class_make_string_from_gvalue (pclass, value);
1658
1659 return NULL;
1660 }
1661
1662
1663 /**
1664 * glade_utils_liststore_from_enum_type:
1665 * @enum_type: A #GType
1666 * @include_empty: wheather to prepend an "Unset" slot
1667 *
1668 * Creates a liststore suitable for comboboxes and such to
1669 * chose from a variety of types.
1670 *
1671 * Returns: A new #GtkListStore
1672 */
1673 GtkListStore *
glade_utils_liststore_from_enum_type(GType enum_type,gboolean include_empty)1674 glade_utils_liststore_from_enum_type (GType enum_type, gboolean include_empty)
1675 {
1676 GtkListStore *store;
1677 GtkTreeIter iter;
1678 GEnumClass *eclass;
1679 guint i;
1680
1681 eclass = g_type_class_ref (enum_type);
1682
1683 store = gtk_list_store_new (1, G_TYPE_STRING);
1684
1685 if (include_empty)
1686 {
1687 gtk_list_store_append (store, &iter);
1688 gtk_list_store_set (store, &iter, 0, _("None"), -1);
1689 }
1690
1691 for (i = 0; i < eclass->n_values; i++)
1692 {
1693 const gchar *displayable =
1694 glade_get_displayable_value (enum_type, eclass->values[i].value_nick);
1695
1696 gtk_list_store_append (store, &iter);
1697 gtk_list_store_set (store, &iter,
1698 0,
1699 displayable ? displayable : eclass->values[i].
1700 value_nick, -1);
1701 }
1702
1703 g_type_class_unref (eclass);
1704
1705 return store;
1706 }
1707
1708
1709
1710 /**
1711 * glade_utils_hijack_key_press:
1712 * @win: a #GtkWindow
1713 * event: the GdkEventKey
1714 * user_data: unused
1715 *
1716 * This function is meant to be attached to key-press-event of a toplevel,
1717 * it simply allows the window contents to treat key events /before/
1718 * accelerator keys come into play (this way widgets dont get deleted
1719 * when cutting text in an entry etc.).
1720 * Creates a liststore suitable for comboboxes and such to
1721 * chose from a variety of types.
1722 *
1723 * Returns: whether the event was handled
1724 */
1725 gint
glade_utils_hijack_key_press(GtkWindow * win,GdkEventKey * event,gpointer user_data)1726 glade_utils_hijack_key_press (GtkWindow *win,
1727 GdkEventKey *event,
1728 gpointer user_data)
1729 {
1730 GtkWidget *focus_widget;
1731
1732 focus_widget = gtk_window_get_focus (win);
1733 if (focus_widget && (event->keyval == GDK_KEY_Delete || /* Filter Delete from accelerator keys */
1734 ((event->state & GDK_CONTROL_MASK) && /* CNTL keys... */
1735 ((event->keyval == GDK_KEY_c || event->keyval == GDK_KEY_C) || /* CNTL-C (copy) */
1736 (event->keyval == GDK_KEY_x || event->keyval == GDK_KEY_X) || /* CNTL-X (cut) */
1737 (event->keyval == GDK_KEY_v || event->keyval == GDK_KEY_V) || /* CNTL-V (paste) */
1738 (event->keyval == GDK_KEY_n || event->keyval == GDK_KEY_N))))) /* CNTL-N (new project) */
1739 {
1740 return gtk_widget_event (focus_widget, (GdkEvent *) event);
1741 }
1742 return FALSE;
1743 }
1744
1745
1746 void
glade_utils_cairo_draw_line(cairo_t * cr,GdkColor * color,gint x1,gint y1,gint x2,gint y2)1747 glade_utils_cairo_draw_line (cairo_t *cr,
1748 GdkColor *color,
1749 gint x1, gint y1,
1750 gint x2, gint y2)
1751 {
1752 cairo_save (cr);
1753
1754 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
1755 gdk_cairo_set_source_color (cr, color);
1756 G_GNUC_END_IGNORE_DEPRECATIONS
1757 cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
1758
1759 cairo_move_to (cr, x1 + 0.5, y1 + 0.5);
1760 cairo_line_to (cr, x2 + 0.5, y2 + 0.5);
1761 cairo_stroke (cr);
1762
1763 cairo_restore (cr);
1764 }
1765
1766
1767 void
glade_utils_cairo_draw_rectangle(cairo_t * cr,GdkColor * color,gboolean filled,gint x,gint y,gint width,gint height)1768 glade_utils_cairo_draw_rectangle (cairo_t *cr,
1769 GdkColor *color,
1770 gboolean filled,
1771 gint x, gint y,
1772 gint width, gint height)
1773 {
1774 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
1775 gdk_cairo_set_source_color (cr, color);
1776 G_GNUC_END_IGNORE_DEPRECATIONS
1777
1778 if (filled)
1779 {
1780 cairo_rectangle (cr, x, y, width, height);
1781 cairo_fill (cr);
1782 }
1783 else
1784 {
1785 cairo_rectangle (cr, x + 0.5, y + 0.5, width, height);
1786 cairo_stroke (cr);
1787 }
1788 }
1789
1790
1791 /* copied from gedit */
1792 gchar *
glade_utils_replace_home_dir_with_tilde(const gchar * path)1793 glade_utils_replace_home_dir_with_tilde (const gchar *path)
1794 {
1795 #ifdef G_OS_UNIX
1796 gchar *tmp;
1797 gchar *home;
1798
1799 g_return_val_if_fail (path != NULL, NULL);
1800
1801 /* Note that g_get_home_dir returns a const string */
1802 tmp = (gchar *) g_get_home_dir ();
1803
1804 if (tmp == NULL)
1805 return g_strdup (path);
1806
1807 home = g_filename_to_utf8 (tmp, -1, NULL, NULL, NULL);
1808 if (home == NULL)
1809 return g_strdup (path);
1810
1811 if (strcmp (path, home) == 0)
1812 {
1813 g_free (home);
1814
1815 return g_strdup ("~");
1816 }
1817
1818 tmp = home;
1819 home = g_strdup_printf ("%s/", tmp);
1820 g_free (tmp);
1821
1822 if (g_str_has_prefix (path, home))
1823 {
1824 gchar *res;
1825
1826 res = g_strdup_printf ("~/%s", path + strlen (home));
1827
1828 g_free (home);
1829
1830 return res;
1831 }
1832
1833 g_free (home);
1834
1835 return g_strdup (path);
1836 #else
1837 return g_strdup (path);
1838 #endif
1839 }
1840
1841 static void
draw_tip(cairo_t * cr)1842 draw_tip (cairo_t *cr)
1843 {
1844 cairo_line_to (cr, 2, 8);
1845 cairo_line_to (cr, 2, 4);
1846 cairo_line_to (cr, 0, 4);
1847 cairo_line_to (cr, 0, 3);
1848 cairo_line_to (cr, 3, 0);
1849 cairo_line_to (cr, 6, 3);
1850 cairo_line_to (cr, 6, 4);
1851 cairo_line_to (cr, 4, 4);
1852
1853 cairo_translate (cr, 12, 6);
1854 cairo_rotate (cr, G_PI_2);
1855 }
1856
1857 static void
draw_tips(cairo_t * cr)1858 draw_tips (cairo_t *cr)
1859 {
1860 cairo_move_to (cr, 2, 8);
1861 draw_tip (cr); draw_tip (cr); draw_tip (cr); draw_tip (cr);
1862 cairo_close_path (cr);
1863 }
1864
1865 static void
draw_pointer(cairo_t * cr)1866 draw_pointer (cairo_t *cr)
1867 {
1868 cairo_line_to (cr, 8, 3);
1869 cairo_line_to (cr, 19, 14);
1870 cairo_line_to (cr, 13.75, 14);
1871 cairo_line_to (cr, 16.5, 19);
1872 cairo_line_to (cr, 14, 21);
1873 cairo_line_to (cr, 11, 16);
1874 cairo_line_to (cr, 7, 19);
1875 cairo_line_to (cr, 7, 3);
1876 cairo_line_to (cr, 8, 3);
1877 }
1878
1879 /* Needed for private draw functions! */
1880 #include "glade-design-private.h"
1881
1882 /**
1883 * glade_utils_pointer_mode_render_icon:
1884 * @mode: the #GladePointerMode to render as icon
1885 * @size: icon size
1886 *
1887 * Render an icon representing the pointer mode.
1888 * Best view with sizes bigger than GTK_ICON_SIZE_LARGE_TOOLBAR.
1889 */
1890 GdkPixbuf *
glade_utils_pointer_mode_render_icon(GladePointerMode mode,GtkIconSize size)1891 glade_utils_pointer_mode_render_icon (GladePointerMode mode, GtkIconSize size)
1892 {
1893 GdkRGBA c1, c2, fg, bg;
1894 cairo_surface_t *surface;
1895 gint width, height;
1896 GdkPixbuf *pix;
1897 cairo_t *cr;
1898
1899 if (gtk_icon_size_lookup (size, &width, &height) == FALSE) return NULL;
1900
1901 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
1902 cr = cairo_create (surface);
1903 cairo_scale (cr, width/24.0, height/24.0);
1904
1905 /* Now get colors */
1906 _glade_design_layout_get_colors (&bg, &fg, &c1, &c2);
1907
1908 /* Clear surface */
1909 cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
1910 cairo_fill(cr);
1911 cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
1912
1913 switch (mode)
1914 {
1915 case GLADE_POINTER_SELECT:
1916 case GLADE_POINTER_ADD_WIDGET:
1917 cairo_set_line_width (cr, 1);
1918 cairo_translate (cr, 1.5, 1.5);
1919 draw_pointer (cr);
1920 fg.alpha = .16;
1921 gdk_cairo_set_source_rgba (cr, &fg);
1922 cairo_stroke (cr);
1923
1924 cairo_translate (cr, -1, -1);
1925 draw_pointer (cr);
1926 gdk_cairo_set_source_rgba (cr, &c2);
1927 cairo_fill_preserve (cr);
1928
1929 fg.alpha = .64;
1930 gdk_cairo_set_source_rgba (cr, &fg);
1931 cairo_stroke (cr);
1932 break;
1933 case GLADE_POINTER_DRAG_RESIZE:
1934 cairo_set_line_width (cr, 1);
1935 cairo_translate (cr, 10.5, 3.5);
1936
1937 draw_tips (cr);
1938
1939 fg.alpha = .16;
1940 gdk_cairo_set_source_rgba (cr, &fg);
1941 cairo_stroke (cr);
1942
1943 cairo_translate (cr, -1, -1);
1944 draw_tips (cr);
1945
1946 gdk_cairo_set_source_rgba (cr, &c2);
1947 cairo_fill_preserve (cr);
1948
1949 c1.red = MAX (0, c1.red - .1);
1950 c1.green = MAX (0, c1.green - .1);
1951 c1.blue = MAX (0, c1.blue - .1);
1952 gdk_cairo_set_source_rgba (cr, &c1);
1953 cairo_stroke (cr);
1954 break;
1955 case GLADE_POINTER_MARGIN_EDIT:
1956 {
1957 gdk_cairo_set_source_rgba (cr, &bg);
1958 cairo_rectangle (cr, 4, 4, 18, 18);
1959 cairo_fill (cr);
1960
1961 c1.alpha = .1;
1962 gdk_cairo_set_source_rgba (cr, &c1);
1963 cairo_rectangle (cr, 6, 6, 16, 16);
1964 cairo_fill (cr);
1965
1966 cairo_set_line_width (cr, 1);
1967 fg.alpha = .32;
1968 gdk_cairo_set_source_rgba (cr, &fg);
1969 cairo_move_to (cr, 16.5, 22);
1970 cairo_line_to (cr, 16.5, 16.5);
1971 cairo_line_to (cr, 22, 16.5);
1972 cairo_stroke (cr);
1973
1974 c1.alpha = .16;
1975 gdk_cairo_set_source_rgba (cr, &c1);
1976 cairo_rectangle (cr, 16, 16, 6, 6);
1977 cairo_fill (cr);
1978
1979 cairo_set_line_width (cr, 2);
1980 c1.alpha = .75;
1981 gdk_cairo_set_source_rgba (cr, &c1);
1982 cairo_move_to (cr, 6, 22);
1983 cairo_line_to (cr, 6, 6);
1984 cairo_line_to (cr, 22, 6);
1985 cairo_stroke (cr);
1986
1987 c1.alpha = 1;
1988 cairo_scale (cr, .75, .75);
1989 cairo_set_line_width (cr, 4);
1990 _glade_design_layout_draw_node (cr, 16*1.25, 6*1.25, &c1, &c2);
1991 _glade_design_layout_draw_node (cr, 6*1.25, 16*1.25, &c1, &c2);
1992 }
1993 break;
1994 case GLADE_POINTER_ALIGN_EDIT:
1995 cairo_scale (cr, 1.5, 1.5);
1996 cairo_rotate (cr, 45*(G_PI/180));
1997 cairo_translate (cr, 11, 2);
1998 _glade_design_layout_draw_pushpin (cr, 2.5, &c1, &c2, &c2, &fg);
1999 break;
2000 default:
2001 break;
2002 }
2003
2004 pix = gdk_pixbuf_get_from_surface (surface, 0, 0,
2005 cairo_image_surface_get_width (surface),
2006 cairo_image_surface_get_height (surface));
2007
2008 cairo_surface_destroy (surface);
2009 cairo_destroy (cr);
2010
2011 return pix;
2012 }
2013
2014 /**
2015 * glade_utils_get_pointer:
2016 * @widget: The widget to get the mouse position relative for
2017 * @window: The window of the current event, or %NULL
2018 * @device: The device, if not specified, the current event will be expected to have a @device.
2019 * @x: The location to store the mouse pointer X position
2020 * @y: The location to store the mouse pointer Y position
2021 *
2022 * Get's the pointer position relative to @widget, while @window and @device
2023 * are not absolutely needed, they should be passed wherever possible.
2024 *
2025 */
2026 void
glade_utils_get_pointer(GtkWidget * widget,GdkWindow * window,GdkDevice * device,gint * x,gint * y)2027 glade_utils_get_pointer (GtkWidget *widget,
2028 GdkWindow *window,
2029 GdkDevice *device,
2030 gint *x,
2031 gint *y)
2032 {
2033 gint device_x = 0, device_y = 0;
2034 gint final_x = 0, final_y = 0;
2035 GtkWidget *event_widget = NULL;
2036
2037 g_return_if_fail (GTK_IS_WIDGET (widget));
2038
2039 if (!device)
2040 {
2041 GdkEvent *event = gtk_get_current_event ();
2042
2043 device = gdk_event_get_device (event);
2044 gdk_event_free (event);
2045 }
2046
2047 g_return_if_fail (GDK_IS_DEVICE (device));
2048
2049 if (!window)
2050 window = gtk_widget_get_window (widget);
2051
2052 g_return_if_fail (GDK_IS_WINDOW (window));
2053
2054 gdk_window_get_device_position (window, device, &device_x, &device_y, NULL);
2055 gdk_window_get_user_data (window, (gpointer)&event_widget);
2056
2057 if (event_widget != widget)
2058 {
2059 gtk_widget_translate_coordinates (event_widget,
2060 widget,
2061 device_x, device_y,
2062 &final_x, &final_y);
2063 }
2064 else
2065 {
2066 final_x = device_x;
2067 final_y = device_y;
2068 }
2069
2070 if (x)
2071 *x = final_x;
2072 if (y)
2073 *y = final_y;
2074 }
2075
2076 /* Use this to disable scroll events on property editors,
2077 * we dont want them handling scroll because they are inside
2078 * a scrolled window and interrupt workflow causing unexpected
2079 * results when scrolled.
2080 */
2081 static gint
abort_scroll_events(GtkWidget * widget,GdkEvent * event,gpointer user_data)2082 abort_scroll_events (GtkWidget *widget,
2083 GdkEvent *event,
2084 gpointer user_data)
2085 {
2086 GtkWidget *parent = gtk_widget_get_parent (widget);
2087
2088 /* Removing the events from the mask doesnt work for
2089 * stubborn combo boxes which call gtk_widget_add_events()
2090 * in it's gtk_combo_box_init() - so handle the event and propagate
2091 * it up the tree so the scrollwindow still handles the scroll event.
2092 */
2093 gtk_propagate_event (parent, event);
2094
2095 return TRUE;
2096 }
2097
2098 void
glade_util_remove_scroll_events(GtkWidget * widget)2099 glade_util_remove_scroll_events (GtkWidget *widget)
2100 {
2101 gint events = gtk_widget_get_events (widget);
2102
2103 events &= ~(GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK);
2104 gtk_widget_set_events (widget, events);
2105
2106 g_signal_connect (G_OBJECT (widget), "scroll-event",
2107 G_CALLBACK (abort_scroll_events), NULL);
2108 }
2109
2110 /**
2111 * _glade_util_file_get_relative_path:
2112 * @target: input GFile
2113 * @source: input GFile
2114 *
2115 * Gets the path for @source relative to @target even if @source is not a
2116 * descendant of @target.
2117 *
2118 */
2119 gchar *
_glade_util_file_get_relative_path(GFile * target,GFile * source)2120 _glade_util_file_get_relative_path (GFile *target, GFile *source)
2121 {
2122 gchar *relative_path;
2123
2124 if ((relative_path = g_file_get_relative_path (target, source)) == NULL)
2125 {
2126 GString *relpath = g_string_new ("");
2127
2128 g_object_ref (target);
2129
2130 while (relative_path == NULL)
2131 {
2132 GFile *old_target = target;
2133 target = g_file_get_parent (target);
2134
2135 relative_path = g_file_get_relative_path (target, source);
2136
2137 g_string_append (relpath, "..");
2138 g_string_append_c (relpath, G_DIR_SEPARATOR);
2139
2140 g_object_unref (old_target);
2141 }
2142
2143 g_string_append (relpath, relative_path);
2144 g_free (relative_path);
2145 relative_path = g_string_free (relpath, FALSE);
2146 }
2147
2148 return relative_path;
2149 }
2150