diff options
author | Alexey Yakovenko <waker@users.sourceforge.net> | 2013-09-29 19:32:03 +0200 |
---|---|---|
committer | Alexey Yakovenko <waker@users.sourceforge.net> | 2013-09-29 19:32:03 +0200 |
commit | 624bab4dd36e1bea09c27a67f1ff0a3b983f1ac1 (patch) | |
tree | 2fc93406fb732d9e15d17ab9d6b00616eb282266 /tools/glade/glade/utils.c | |
parent | e041da40b7fe9bebd76dc04f25b0c345986d9ad0 (diff) |
added our glade fork
Diffstat (limited to 'tools/glade/glade/utils.c')
-rw-r--r-- | tools/glade/glade/utils.c | 2327 |
1 files changed, 2327 insertions, 0 deletions
diff --git a/tools/glade/glade/utils.c b/tools/glade/glade/utils.c new file mode 100644 index 00000000..56b5beb2 --- /dev/null +++ b/tools/glade/glade/utils.c @@ -0,0 +1,2327 @@ +/* Gtk+ User Interface Builder + * Copyright (C) 1998 Damon Chaplin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "gladeconfig.h" + +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#ifndef _WIN32 +#include <unistd.h> +#endif +#include <dirent.h> +#include <errno.h> + +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> + +#ifdef USE_GNOME +#include <gnome.h> +#include <bonobo.h> +#endif + +#include "gb.h" +#include "gbwidget.h" +#include "glade_project_window.h" +#include "utils.h" + + +gchar *GladeDayNames[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; +gchar *GladeMonthNames[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; + +typedef void (*GladeDialogCallback) (GtkDialog *dialog, gpointer data); + +#define READ_BUFFER_SIZE 4096 + +static void on_entry_dialog_ok (GtkWidget * widget, + gpointer data); +static void on_entry_dialog_destroy (GtkWidget * widget, + gpointer data); +static gboolean check_components_match (const gchar *base, + const gchar *path, + gint root_pos, + gint len); + +static GtkWidget* glade_util_find_table_accelerator_target (GtkWidget *parent, + GtkWidget *child); +static GtkWidget* glade_util_find_box_accelerator_target (GtkWidget *parent, + GtkWidget *child); +static GtkWidget* glade_util_find_fixed_accelerator_target (GtkWidget *parent, + GtkWidget *child); +static GtkWidget* glade_util_find_layout_accelerator_target (GtkWidget *parent, + GtkWidget *child); + +typedef struct _GladeLayoutCallbackData GladeLayoutCallbackData; +struct _GladeLayoutCallbackData +{ + gint x, y, best_distance; + GtkWidget *child, *best_target; +}; + +typedef struct _GladeFindWidgetData GladeFindWidgetData; +struct _GladeFindWidgetData +{ + gchar *name; + GtkWidget *found_widget; +}; + +static void glade_util_find_layout_acclererator_target_cb (GtkWidget *widget, + GladeLayoutCallbackData *data); + +static GtkWidget* glade_util_find_focus_child (GtkWidget *widget); + +static void glade_util_find_widget_recursive (GtkWidget *widget, + GladeFindWidgetData *data); + + +/* This shows a simple dialog box with a label and an 'OK' button. + Example usage: + glade_util_show_message_box ("Error saving file", NULL); +*/ +void +glade_util_show_message_box (const gchar *message, + GtkWidget *transient_widget) +{ + GtkWidget *dialog; + GtkWindow *transient_parent; + + transient_parent = (GtkWindow *)glade_util_get_toplevel (transient_widget); + dialog = gtk_message_dialog_new (transient_parent, + GTK_DIALOG_MODAL, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + "%s", message); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); + gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER); + + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); +} + + +/* This shows a dialog box with a message and an Entry for entering a value. + * When the OK button is pressed the handler of the specified widget will + * be called with the value and the given data. + * NOTE: The dialog is automatically destroyed when any button is clicked. + + Example usage: + glade_util_show_entry_dialog ("Name:", "default", widget, on_dialog_ok, + "NewName", NULL); + + void + on_dialog_ok(GtkWidget *widget, gchar *value, gpointer data) + { + ... + +*/ +void +glade_util_show_entry_dialog (const gchar *message, + const gchar *initial_value, + GtkWidget *widget, + GbEntryDialogFunc signal_handler, + gpointer data, + GtkWidget *transient_widget) +{ + GtkWidget *dialog, *hbox, *label, *entry, *button, *toplevel; + + dialog = gtk_dialog_new (); + gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE); + gtk_container_set_border_width (GTK_CONTAINER (dialog), 5); + + /* FIXME: Passing a function as a gpointer isn't compatable with ANSI. */ + gtk_object_set_data (GTK_OBJECT (dialog), "handler", signal_handler); + gtk_object_set_data (GTK_OBJECT (dialog), "widget", widget); + gtk_widget_ref (widget); + + hbox = gtk_hbox_new (FALSE, 10); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 20); + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), hbox, + TRUE, TRUE, 0); + gtk_widget_show (hbox); + + label = gtk_label_new (message); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, TRUE, 0); + gtk_widget_show (label); + + entry = glade_util_entry_new (GTK_OBJECT (dialog)); + if (initial_value) + gtk_entry_set_text (GTK_ENTRY (entry), initial_value); + gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0); + gtk_widget_show (entry); + gtk_widget_grab_focus (entry); + gtk_object_set_data (GTK_OBJECT (dialog), "entry", entry); + + button = gtk_button_new_from_stock (GTK_STOCK_CANCEL); + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area), button, + FALSE, TRUE, 20); + GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); + gtk_widget_show (button); + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (gtk_widget_destroy), + GTK_OBJECT (dialog)); + + button = gtk_button_new_from_stock (GTK_STOCK_OK); + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area), button, + FALSE, TRUE, 20); + GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); + gtk_widget_grab_default (button); + gtk_widget_show (button); + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (on_entry_dialog_ok), + data); + + gtk_signal_connect (GTK_OBJECT (dialog), "key_press_event", + GTK_SIGNAL_FUNC (glade_util_check_key_is_esc), + GINT_TO_POINTER (GladeEscDestroys)); + gtk_signal_connect (GTK_OBJECT (dialog), "destroy", + GTK_SIGNAL_FUNC (on_entry_dialog_destroy), widget); + + if (transient_widget) + { + toplevel = glade_util_get_toplevel (transient_widget); + if (toplevel && GTK_IS_WINDOW (toplevel) && GTK_WIDGET_MAPPED (toplevel)) + gtk_window_set_transient_for (GTK_WINDOW (dialog), + GTK_WINDOW (toplevel)); + } + + gtk_widget_show (dialog); +} + + +static void +on_entry_dialog_destroy (GtkWidget * widget, gpointer data) +{ + gtk_widget_unref (GTK_WIDGET (data)); +} + + +static void +on_entry_dialog_ok (GtkWidget * widget, gpointer data) +{ + GtkWidget *dialog = gtk_widget_get_toplevel (widget); + GbEntryDialogFunc handler; + GtkWidget *entry; + gchar *text; + gboolean close = TRUE; + + /* FIXME: ANSI forbids casting function pointer to data pointer. */ + handler = (GbEntryDialogFunc) (gtk_object_get_data (GTK_OBJECT (dialog), + "handler")); + widget = GTK_WIDGET (gtk_object_get_data (GTK_OBJECT (dialog), "widget")); + entry = GTK_WIDGET (gtk_object_get_data (GTK_OBJECT (dialog), "entry")); + g_return_if_fail (entry != NULL); + text = (gchar*) gtk_entry_get_text (GTK_ENTRY (entry)); + + if (handler) + close = (*handler) (widget, text, data); + if (close) + gtk_widget_destroy (dialog); +} + +static void +on_dialog_response (GtkDialog *dialog, gint response, gpointer ok_handler_data) +{ + GladeDialogCallback ok_handler; + + if (response == GTK_RESPONSE_OK) + { + ok_handler = (GladeDialogCallback) g_object_get_data (G_OBJECT (dialog), "ok_handler"); + ok_handler (dialog, ok_handler_data); + } + else + { + gtk_widget_destroy (GTK_WIDGET (dialog)); + } +} + +/* This creates a dialog with OK & Cancel buttons, usable in plain GTK or + Gnome, and returns a vbox which the caller can place widgets in. + If transient_for is non-NULL, the window it is in is used to set the + transient for relationship, so the dialog will always be above the widget. + (This only works with GTK 1.1.6+). + The callback will be called when the OK button is pressed. */ +GtkWidget* +glade_util_create_dialog (const gchar *title, + GtkWidget *transient_for, + GtkSignalFunc ok_handler, + gpointer ok_handler_data, + GtkWidget **vbox) +{ + GtkWidget *dialog; + GtkWindow *transient_parent; + + transient_parent = (GtkWindow *)glade_util_get_toplevel (transient_for); + dialog = gtk_dialog_new_with_buttons (title, transient_parent, + 0, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OK, GTK_RESPONSE_OK, + NULL); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); + gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE); + g_object_set_data (G_OBJECT (dialog), "ok_handler", ok_handler); + + g_signal_connect (dialog, "response", + G_CALLBACK (on_dialog_response), + ok_handler_data); + + *vbox = GTK_DIALOG (dialog)->vbox; + + return dialog; +} + + +gint glade_util_check_key_is_esc (GtkWidget *widget, + GdkEventKey *event, + gpointer data) +{ + g_return_val_if_fail (GTK_IS_WINDOW (widget), FALSE); + + if (event->keyval == GDK_Escape) + { + GladeEscAction action = GPOINTER_TO_INT (data); + + if (action == GladeEscCloses) + { + glade_util_close_window (widget); + return TRUE; + } + else if (action == GladeEscDestroys) + { + gtk_widget_destroy (widget); + return TRUE; + } + else + return FALSE; + } + else + return FALSE; +} + +/* This returns a new entry ready to insert in a dialog. + The entry is set up so that <Return> will invoke the default action. The + returned widget must be added to a container in the dialog. */ +extern GtkWidget * +glade_util_entry_new (GtkObject *dialog) +{ + GtkWidget *entry; + + g_return_val_if_fail (GTK_IS_WINDOW (dialog), NULL); + + entry = gtk_entry_new (); + g_return_val_if_fail (entry != NULL, NULL); + + /* Make <Return> in entry field invoke dialog default */ + gtk_signal_connect_object (GTK_OBJECT (entry), "activate", + GTK_SIGNAL_FUNC (gtk_window_activate_default), + GTK_OBJECT (dialog)); + + return entry; +} + + +/* This returns a new spinbutton ready to insert in a dialog. + A pointer to the spin button is added as object data to the dialog. The + spinbutton is set up so that <Return> will invoke the default action. The + returned widget must be added to a container in the dialog. */ +GtkWidget * +glade_util_spin_button_new (GtkObject *dialog, + const gchar *key, + GtkAdjustment *adjustment, + gfloat climb_rate, + guint digits) +{ + GtkWidget *spinbutton; + + g_return_val_if_fail (GTK_IS_WINDOW (dialog), NULL); + g_return_val_if_fail (GTK_IS_ADJUSTMENT (adjustment), NULL); + + spinbutton = gtk_spin_button_new (GTK_ADJUSTMENT (adjustment), climb_rate, + digits); + g_return_val_if_fail (spinbutton != NULL, NULL); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE); + + /* save pointer to entry so we can find it easily in the OK handler */ + gtk_object_set_data (GTK_OBJECT (dialog), key, spinbutton); + /* Make <Return> in entry field invoke dialog default */ + gtk_signal_connect_object (GTK_OBJECT (spinbutton), "activate", + GTK_SIGNAL_FUNC (gtk_window_activate_default), + GTK_OBJECT (dialog)); + + return spinbutton; +} + +/* This returns the index of the given gint with the given array of gints. + If the value is not found it outputs a warning and returns -1. + This function is intended to be used for finding the index when using + properties which are choices. */ +gint +glade_util_int_array_index (const gint array[], + gint array_size, + gint value) +{ + gint i; + + for (i = 0; i < array_size; i++) + { + if (array[i] == value) + return i; + } + return -1; +} + + +/* This returns the index of the given string with the given array of strings. + If the value is not found it outputs a warning and returns -1. + This function is intended to be used for finding the index when using + properties which are choices. */ +gint +glade_util_string_array_index (const gchar *array[], + gint array_size, + const gchar *value) +{ + gint i; + + for (i = 0; i < array_size; i++) + { + if (array[i] && !strcmp (array[i], value)) + return i; + } + return -1; +} + + +/* This returns TRUE if the two strings are equivalent. Note that NULL is + considered equivalent to the empty string. */ +gboolean +glade_util_strings_equivalent (const gchar *string1, + const gchar *string2) +{ + if (string1 && string1[0] == '\0') + string1 = NULL; + if (string2 && string2[0] == '\0') + string2 = NULL; + + if (string1 == NULL) + { + if (string2 == NULL) + return TRUE; + else + return FALSE; + } + else if (string2 == NULL) + return FALSE; + else if (!strcmp (string1, string2)) + return TRUE; + return FALSE; +} + + +/* This returns a copy of the given string, or NULL if the string is NULL + or empty, i.e. "". */ +gchar* +glade_util_copy_string (const gchar *string) +{ + if (string == NULL || string[0] == '\0') + return NULL; + return g_strdup (string); +} + + +/* Returns TRUE if string contains substring. Returns FALSE if substring is + not found or is NULL. */ +gboolean +glade_util_strstr (const gchar * string, + const gchar * substring) +{ + gchar first_char; + gint string_len, substring_len, i, j; + gboolean found; + + if (!string || !substring) + return FALSE; + + first_char = substring[0]; + string_len = strlen (string); + substring_len = strlen (substring); + + if (string_len < substring_len) + return FALSE; + for (i = 0; i <= string_len - substring_len; i++) + { + if (string[i] == first_char) + { + found = TRUE; + for (j = 1; j < substring_len; j++) + { + if (string[i + j] != substring[j]) + { + found = FALSE; + break; + } + } + if (found == TRUE) + return TRUE; + } + } + return FALSE; +} + + +/* This looks for '::' in a property name and returns a pointer to the next + character. If '::' is not found it returns the argument. */ +gchar * +glade_util_find_start_of_tag_name (const gchar * tag_name) +{ + register const gchar *pos = tag_name; + + while (*pos && (*pos != ':' || *(pos + 1) != ':')) + pos++; + return (gchar*) (*pos ? pos + 2 : tag_name); +} + + +/* This creates a string representing the modifiers flags, e.g. + "GDK_CONTROL_MASK | GDK_SHIFT_MASK". It is used for saving the XML, and + also for writing the source code. It uses a static buffer which is + overwritten for each call. */ +gchar* +glade_util_create_modifiers_string (guint8 modifier_flags) +{ + static gchar modifiers[128]; + + modifiers[0] = '\0'; + if (modifier_flags == 0) + { + strcat (modifiers, "0"); + return modifiers; + } + + if (modifier_flags & GDK_CONTROL_MASK) + { + strcat (modifiers, "GDK_CONTROL_MASK"); + } + if (modifier_flags & GDK_SHIFT_MASK) + { + if (modifiers[0] != '\0') + strcat (modifiers, " | "); + strcat (modifiers, "GDK_SHIFT_MASK"); + } + if (modifier_flags & GDK_MOD1_MASK) + { + if (modifiers[0] != '\0') + strcat (modifiers, " | "); + strcat (modifiers, "GDK_MOD1_MASK"); + } + return modifiers; +} + + +/* This creates a GtkPixmap widget, using a colormap and xpm data from an + '#include'd pixmap file. */ +GtkWidget* +glade_util_create_pixmap_using_colormap (GdkColormap *colormap, + gchar **xpm_data) +{ + GtkWidget *pixmap; + GdkPixmap *gdkpixmap; + GdkBitmap *mask; + + gdkpixmap = gdk_pixmap_colormap_create_from_xpm_d (NULL, colormap, &mask, + NULL, (gchar**) xpm_data); + pixmap = gtk_pixmap_new (gdkpixmap, mask); + + /* The GtkPixmap assumes the reference count, so we can unref them. */ + gdk_pixmap_unref (gdkpixmap); + gdk_bitmap_unref (mask); + + return pixmap; +} + + +/* This returns TRUE if the widget is a toplevel project component, + i.e. a window, dialog or popup menu. */ +gboolean +glade_util_is_component (GtkWidget *widget) +{ + if (widget->parent == NULL) + return TRUE; + + /* Note that menus may have submenus, so we make sure that this is the + toplevel menu. */ + if (GTK_IS_MENU (widget) + && gtk_menu_get_attach_widget (GTK_MENU (widget)) == NULL) + return TRUE; + + return FALSE; +} + + +/* This returns the toplevel window/dialog/menu that contains the given + widget. It even walks up menus, which gtk_widget_get_toplevel() does not. */ +GtkWidget* +glade_util_get_toplevel (GtkWidget *widget) +{ + GtkWidget *parent; + + if (widget == NULL) + return NULL; + + for (;;) + { + if (GTK_IS_MENU (widget)) + parent = gtk_menu_get_attach_widget (GTK_MENU (widget)); + else + parent = widget->parent; + if (parent == NULL) + break; + widget = parent; + } + + return widget; +} + + +/* This returns the closes ancestor of the given widget which is a GbWidget. */ +GtkWidget* +glade_util_get_parent (GtkWidget *widget) +{ + GtkWidget *parent; + + for (;;) + { + if (GTK_IS_MENU (widget)) + parent = gtk_menu_get_attach_widget (GTK_MENU (widget)); + else + parent = widget->parent; + + /* There is no way to move up from the combo popup list window to the + combo, so we added a special pointer. */ + if (!parent) + parent = gtk_object_get_data (GTK_OBJECT (widget), GladeParentKey); + + if (parent == NULL) + break; + + if (GB_IS_GB_WIDGET (parent)) + return parent; + + widget = parent; + } + + return widget; +} + + +/* This is used when setting up keyboard accelerators resulting from underlined + keys in labels. It returns the default mnemonic widget to use. */ +GtkWidget* +glade_util_find_default_accelerator_target (GtkWidget *label) +{ + GtkWidget *parent, *child, *target = NULL; + + g_return_val_if_fail (GTK_IS_LABEL (label), NULL); + + /* Now try to find the widget to the right of the label. We can do this + fairly easily for tables, boxes, and fixed containers. But the packer + container may be more difficult. */ + parent = label->parent; + child = label; + while (parent) + { + if (GTK_IS_TABLE (parent)) + target = glade_util_find_table_accelerator_target (parent, child); + else if (GTK_IS_HBOX (parent)) + target = glade_util_find_box_accelerator_target (parent, child); + else if (GTK_IS_FIXED (parent)) + target = glade_util_find_fixed_accelerator_target (parent, child); + else if (GTK_IS_LAYOUT (parent)) + target = glade_util_find_layout_accelerator_target (parent, child); + + if (target) + { + return target; + } + + child = parent; + parent = parent->parent; + } + + return NULL; +} + + +/* This tries to find a named widget in a component. */ +GtkWidget* +glade_util_find_widget (GtkWidget *widget, gchar *name) +{ + GladeFindWidgetData data; + + data.name = name; + data.found_widget = NULL; + + glade_util_find_widget_recursive (widget, &data); + + return data.found_widget; +} + + +static void +glade_util_find_widget_recursive (GtkWidget *widget, + GladeFindWidgetData *data) +{ + if (!data->found_widget && widget->name + && !strcmp (widget->name, data->name)) + { + data->found_widget = widget; + } + + if (GTK_IS_CONTAINER (widget)) + { + gtk_container_forall (GTK_CONTAINER (widget), + (GtkCallback) glade_util_find_widget_recursive, + data); + } +} + + +/* This tries to find the widget on the right of the given child. */ +static GtkWidget* +glade_util_find_table_accelerator_target (GtkWidget *parent, GtkWidget *child) +{ + GtkTableChild *tchild, *tchild2; + GList *children; + + tchild = glade_util_find_table_child (GTK_TABLE (parent), child); + g_return_val_if_fail (tchild != NULL, NULL); + + children = GTK_TABLE (parent)->children; + while (children) + { + tchild2 = (GtkTableChild*) children->data; + + if (tchild2->widget != tchild->widget + && tchild2->left_attach == tchild->right_attach + && tchild2->top_attach < tchild->bottom_attach + && tchild2->bottom_attach > tchild->top_attach) + { + return glade_util_find_focus_child (tchild2->widget); + } + children = children->next; + } + return NULL; +} + + +/* This tries to find the widget on the right of the given child. */ +static GtkWidget* +glade_util_find_box_accelerator_target (GtkWidget *parent, GtkWidget *child) +{ + GtkBoxChild *bchild, *bchild2; + GList *children, *elem; + gint pos; + + pos = glade_util_get_box_pos (GTK_BOX (parent), child); + g_return_val_if_fail (pos != -1, NULL); + + children = GTK_BOX (parent)->children; + elem = g_list_nth (children, pos); + bchild = (GtkBoxChild*) elem->data; + + /* If the child has PACK_START set, try to find the next child also with + PACK_START set. If that can't be found, try to find the last child with + PACK_END set, since that will be nearest the middle. + If the child has PACK_END set, find the previous child with PACK_END. */ + if (bchild->pack == GTK_PACK_START) + { + while (elem->next) + { + elem = elem->next; + bchild2 = (GtkBoxChild*) elem->data; + if (bchild2->pack == GTK_PACK_START) + return glade_util_find_focus_child (bchild2->widget); + } + /* We couldn't find another child with PACK_START, so we drop through + to the second loop with elem containing the last list element. */ + } + else + { + elem = elem->prev; + } + + /* Step back through the list trying to find a child with PACK_END set. */ + while (elem) + { + bchild2 = (GtkBoxChild*) elem->data; + if (bchild2->pack != GTK_PACK_END) + return glade_util_find_focus_child (bchild2->widget); + elem = elem->prev; + } + return NULL; +} + + +/* This tries to find the widget on the right of the given child. */ +static GtkWidget* +glade_util_find_fixed_accelerator_target (GtkWidget *parent, GtkWidget *child) +{ + GtkFixedChild *fchild, *fchild2; + GtkWidget *best_target = NULL; + gint distance, best_distance = -1; + GList *children; + + fchild = glade_util_find_fixed_child (GTK_FIXED (parent), child); + g_return_val_if_fail (fchild != NULL, NULL); + + children = GTK_BOX (parent)->children; + while (children) + { + fchild2 = (GtkFixedChild*) children->data; + + /* We allow a few pixels difference in the y coordinate. + The x coordinate must be greater than the given child label. */ + if (fchild2->widget != fchild->widget + && abs (fchild2->y - fchild->y) < 5 + && fchild2->x > fchild->x) + { + distance = fchild2->x - fchild->x; + + if (best_distance == -1 || distance < best_distance) + { + best_distance = distance; + best_target = fchild2->widget; + } + } + + children = children->next; + } + + return glade_util_find_focus_child (best_target); +} + + +/* This tries to find the widget on the right of the given child. */ +static GtkWidget* +glade_util_find_layout_accelerator_target (GtkWidget *parent, GtkWidget *child) +{ + GladeLayoutCallbackData data; + + data.best_distance = -1; + data.best_target = NULL; + data.child = child; + data.x = child->allocation.x; + data.y = child->allocation.y; + gtk_container_forall (GTK_CONTAINER (parent), + (GtkCallback)glade_util_find_layout_acclererator_target_cb, + &data); + return glade_util_find_focus_child (data.best_target); +} + +static void +glade_util_find_layout_acclererator_target_cb (GtkWidget *widget, + GladeLayoutCallbackData *data) +{ + gint distance; + + /* We allow a few pixels difference in the y coordinate. + The x coordinate must be greater than the given child label. */ + if (widget != data->child + && abs (widget->allocation.y - data->y) < 5 + && widget->allocation.x > data->x) + { + distance = widget->allocation.x - data->x; + + if (data->best_distance == -1 || distance < data->best_distance) + { + data->best_distance = distance; + data->best_target = widget; + } + } +} + + +/* This tries to find a widget which can accept the input focus, within the + given widget and its descendants. Note that this will only work if the + child is a GbWidget, since gb_label_write_source() assumes there is a + variable declared corresponding to the widget. */ +static GtkWidget* +glade_util_find_focus_child (GtkWidget *widget) +{ + if (widget == NULL) + return NULL; + + if (GTK_WIDGET_CAN_FOCUS (widget)) + return widget; + + if (GTK_IS_COMBO (widget)) + return GTK_COMBO (widget)->entry; + +#ifdef USE_GNOME + if (GNOME_IS_FILE_ENTRY (widget)) + return gnome_file_entry_gtk_entry (GNOME_FILE_ENTRY (widget)); +#endif + + return NULL; +} + + +gint +glade_util_get_box_pos (GtkBox * box, + GtkWidget * widget) +{ + GList *children; + GtkBoxChild *child; + guint pos = 0; + + children = box->children; + while (children) + { + child = children->data; + if (widget == child->widget) + return pos; + pos++; + children = children->next; + } + g_warning (_("Widget not found in box")); + return -1; +} + + +GtkTableChild * +glade_util_find_table_child (GtkTable * table, + GtkWidget * widget) +{ + GList *children; + GtkTableChild *child; + + children = table->children; + while (children) + { + child = children->data; + if (widget == child->widget) + return child; + children = children->next; + } + g_warning (_("Widget not found in table")); + return NULL; +} + + +GtkBoxChild * +glade_util_find_box_child (GtkBox * box, + GtkWidget * widget) +{ + GList *children; + GtkBoxChild *child; + + children = box->children; + while (children) + { + child = children->data; + if (widget == child->widget) + return child; + children = children->next; + } + g_warning (_("Widget not found in box")); + return NULL; +} + + +GtkFixedChild* +glade_util_find_fixed_child (GtkFixed *fixed, + GtkWidget *widget) +{ + GList *children; + GtkFixedChild *child; + + children = fixed->children; + while (children) + { + child = children->data; + if (widget == child->widget) + return child; + children = children->next; + } + g_warning (_("Widget not found in fixed container")); + return NULL; +} + + +#if GLADE_SUPPORTS_GTK_PACKER +GtkPackerChild * +glade_util_find_packer_child (GtkPacker * packer, + GtkWidget * widget) +{ + GList *children; + GtkPackerChild *child; + + children = packer->children; + while (children) + { + child = children->data; + if (widget == child->widget) + return child; + children = children->next; + } + g_warning (_("Widget not found in packer")); + return NULL; +} +#endif + + +/* This returns the GtkLabel's text, including the underline characters. + We don't really need it now for GTK+ 2.0. Free the returned string. */ +gchar* +glade_util_get_label_text (GtkWidget *label) +{ + char *result; + + /* Hopefully this will work in GTK+ 2.0. */ + result = (gchar*) gtk_label_get_label (GTK_LABEL (label)); + +#if 0 + g_print ("Label text: %s\n", result); +#endif + + /* We strdup it just to remain compatable with the old version. */ + return g_strdup (result); + +#if 0 + /* This is the old GTK+ 1.2 version, for reference. */ + GdkWChar *label_wc, *label_with_underscores; + gchar *label_text, *label_pattern, *result; + gint len, i, j; + gboolean in_pattern; + + g_return_val_if_fail (GTK_IS_LABEL (label), NULL); + + /* We assume that the multi-byte and wide char versions of the text match. */ + label_text = GTK_LABEL (label)->label; + label_wc = GTK_LABEL (label)->label_wc; + label_pattern = GTK_LABEL (label)->pattern; + /* The maximum space we need is 2 * the string length, i.e. when all letters + are underlined. */ + len = strlen (label_text); + label_with_underscores = g_new (GdkWChar, len * 2 + 1); + + /* We are careful in case the pattern is shorter than the label text. */ + in_pattern = label_pattern ? TRUE : FALSE; + for (i = 0, j = 0; i < len; i++) + { + if (in_pattern && label_pattern[i]) + { + if (label_pattern[i] == '_') + label_with_underscores[j++] = '_'; + } + else + in_pattern = FALSE; + label_with_underscores[j++] = label_wc[i]; + + if (label_wc[i] == '_') + label_with_underscores[j++] = '_'; + } + label_with_underscores[j] = '\0'; + + /* Now convert it to multi-byte. */ + result = gdk_wcstombs (label_with_underscores); + g_free (label_with_underscores); + + return result; +#endif +} + + +/* This should be hooked up to the delete_event of windows which you want + to hide, so that if they are shown again they appear in the same place. + This stops the window manager asking the user to position the window each + time it is shown, which is quite annoying. */ +gint +glade_util_close_window_on_delete (GtkWidget * widget, + GdkEvent * event, + gpointer data) +{ + glade_util_close_window (widget); + return TRUE; +} + + +gint +glade_util_close_window (GtkWidget * widget) +{ + gint x, y; + gboolean set_position = FALSE; + + /* remember position of window for when it is used again */ + if (widget->window) + { + gdk_window_get_root_origin (widget->window, &x, &y); + set_position = TRUE; + } + + gtk_widget_hide (widget); + + if (set_position) + gtk_widget_set_uposition (widget, x, y); + + return TRUE; +} + + +/* Returns TRUE if the given file exists. filename must be UTF-8. */ +gboolean +glade_util_file_exists (const gchar *filename) +{ + int status; + struct stat filestat; + gchar *on_disk_filename; + + on_disk_filename = g_filename_from_utf8 (filename, -1, NULL, NULL, NULL); + status = stat (on_disk_filename, &filestat); + g_free (on_disk_filename); + + if (status == -1 && errno == ENOENT) + return FALSE; + return TRUE; +} + + +/* Returns the last modification time of the given file, or 0 if it doesn't + exist, or -1 on error. filename must be UTF-8. */ +GladeError* +glade_util_file_last_mod_time (const gchar *filename, time_t *last_mod_time) +{ + int status; + struct stat filestat; + gchar *on_disk_filename; + + on_disk_filename = g_filename_from_utf8 (filename, -1, NULL, NULL, NULL); + status = stat (on_disk_filename, &filestat); + g_free (on_disk_filename); + + if (status == -1) + { + return glade_error_new_system (_("Couldn't access file:\n %s\n"), + filename); + } + + *last_mod_time = filestat.st_mtime; + return NULL; +} + + +/* This copies a file from src to dest, and returns a GladeError if an error + occurs. src & dest must be UTF-8. */ +GladeError* +glade_util_copy_file (const gchar *src, + const gchar *dest) +{ + FILE *input_fp, *output_fp; + gchar buffer[READ_BUFFER_SIZE]; + gint bytes_read, bytes_written; + GladeError *error = NULL; + + input_fp = glade_util_fopen (src, "r"); + if (input_fp == NULL) + { + return glade_error_new_system (_("Couldn't open file:\n %s\n"), src); + } + + output_fp = glade_util_fopen (dest, "w"); + if (output_fp == NULL) + { + error = glade_error_new_system (_("Couldn't create file:\n %s\n"), + dest); + fclose (input_fp); + return error; + } + + for (;;) + { + bytes_read = fread (buffer, 1, READ_BUFFER_SIZE, input_fp); + if (bytes_read != READ_BUFFER_SIZE && ferror (input_fp)) + { + error = glade_error_new_system (_("Error reading from file:\n %s\n"), + src); + break; + } + + if (bytes_read) + { + bytes_written = fwrite (buffer, 1, bytes_read, output_fp); + if (bytes_read != bytes_written) + { + error = glade_error_new_system (_("Error writing to file:\n %s\n"), + dest); + break; + } + } + + if (bytes_read != READ_BUFFER_SIZE && feof (input_fp)) + { + break; + } + } + + fclose (input_fp); + fclose (output_fp); + + return error; +} + + +/* Creates a directory if it doesn't already exist. directory must be an + absolute path, in UTF-8. */ +GladeError* +glade_util_ensure_directory_exists (const gchar *directory) +{ + GladeError *error = NULL; + struct stat filestat; + gchar *on_disk_directory; + + g_return_val_if_fail (g_path_is_absolute (directory), NULL); + + on_disk_directory = g_filename_from_utf8 (directory, -1, NULL, NULL, NULL); + if (stat (on_disk_directory, &filestat) != 0) + { + /* If the directory doesn't exist, try to create it. */ + if (errno == ENOENT) + { + gchar *parent_dir; + + /* First make sure the parent directory exists. */ + parent_dir = glade_util_parent_directory (directory); + if (parent_dir) + { + error = glade_util_ensure_directory_exists (parent_dir); + g_free (parent_dir); + } + + if (!error) + { +#ifdef _WIN32 + if (mkdir (on_disk_directory) != 0) +#else + if (mkdir (on_disk_directory, 0777) != 0) +#endif + { +#ifndef _WIN32 +/* This happens under WIN32 when stat is confused by the filename, but this is + harmless, since we know that the directory exists after all. */ + error = glade_error_new_system (_("Couldn't create directory:\n %s\n"), directory); +#endif + } + } + } + else + { + error = glade_error_new_system (_("Couldn't access directory:\n %s\n"), directory); + } + } +#ifndef _WIN32 + /* If the directory does exist, check it is a directory. */ + else if (!S_ISDIR (filestat.st_mode)) + { + error = glade_error_new_general (GLADE_STATUS_INVALID_DIRECTORY, + _("Invalid directory:\n %s\n"), + directory); + } +#endif + + g_free (on_disk_directory); + + return error; +} + + +/* Adds a filename onto a directory to make a complete pathname. + The directory may or may not end in '/'. file must be a simple filename. + Free the returned string when no longer needed. */ +gchar* +glade_util_make_path (const gchar *dir, + const gchar *file) +{ + gint dir_len; + + g_return_val_if_fail (dir != NULL, NULL); + g_return_val_if_fail (file != NULL, NULL); + + dir_len = strlen (dir); + g_return_val_if_fail (dir_len > 0, NULL); + + if (dir[dir_len - 1] == G_DIR_SEPARATOR) + return g_strdup_printf ("%s%s", dir, file); + else + return g_strdup_printf ("%s%c%s", dir, G_DIR_SEPARATOR, file); +} + + +/* This turns a relative pathname into an absolute one based on the given + base directory (which MUST be absolute). + e.g. "/home/damon" + "../dave/test" -> "/home/dave/test" + The returned path should be freed when no longer needed. */ +gchar* +glade_util_make_absolute_path (const gchar *dir, const gchar *file) +{ + gint dir_pos, file_pos, len, root_pos = 0; + gchar *path; + + g_return_val_if_fail (dir != NULL, NULL); + + if (file == NULL || file[0] == '\0') + return g_strdup (dir); + + if (g_path_is_absolute (file)) + return g_strdup (file); + + /* For windows if dir has a drive set, e.g. "C:\", we never delete that. */ +#ifdef NATIVE_WIN32 + if (isalpha (dir[0]) && dir[1] == ':' && dir[2] == G_DIR_SEPARATOR) + root_pos = 2; +#endif + + /* Start at last character in dir. */ + dir_pos = strlen (dir) - 1; + + /* First we make sure we skip any '/' at the end of dir. */ + if (dir_pos > root_pos && dir[dir_pos] == G_DIR_SEPARATOR) + dir_pos--; + + /* Now for each '..' in file, we step back one component in dir, and + forward one component in file. */ + file_pos = 0; + for (;;) + { + /* Skip './' */ + if (file[file_pos] == '.' && file[file_pos + 1] == G_DIR_SEPARATOR) + file_pos += 2; + + else if (file[file_pos] == '.' && file[file_pos + 1] == '.' + && (file[file_pos + 2] == G_DIR_SEPARATOR + || file[file_pos + 2] == '\0')) + { + while (dir_pos > root_pos && dir[dir_pos] != G_DIR_SEPARATOR) + dir_pos--; + if (dir_pos > root_pos) + dir_pos--; + + if (file[file_pos + 2] == G_DIR_SEPARATOR) + file_pos += 3; + else + file_pos += 2; + } + + else + break; + } + + /* Now concatenate the parts of dir and file together. */ + if (dir_pos > root_pos) + dir_pos++; + len = dir_pos + 1 + (strlen (file) - file_pos) + 1; + path = g_malloc (len); + strncpy (path, dir, dir_pos); + path[dir_pos] = G_DIR_SEPARATOR; + strcpy (path + dir_pos + 1, file + file_pos); + return path; +} + + +/* This turns an absolute pathname into an relative one based on the given + base directory. Both arguments must be absolute paths, and should be in + canonical form, i.e. not containing '..', '.' or multiple '/'s together. + The returned value may or may not end with a '/', depending on the + arguments. + e.g. "/home/damon" + "/home/damon/pixmaps" -> "pixmaps" + "/home/damon" + "/home/damon/pixmaps/" -> "pixmaps/" + "/home/damon/project" + "/home/damon/test/pic.xpm" -> "../test/pic.xpm" + "/home/damon/project" + "/home/damon/project" -> "" + "/home/damon/project" + "/home/damon" -> "../" + The returned path should be freed when no longer needed. */ +gchar* +glade_util_make_relative_path (const gchar *base, const gchar *path) +{ + gchar *relative_path; + gint pos, num_parents = 0, len, path_len, i, root_pos = 0; + gboolean match = FALSE; + + if (path == NULL) + return g_strdup (""); + + /* For windows if base has a drive set, e.g. "C:\", then path must start with + the same drive or we just return path. */ +#ifdef NATIVE_WIN32 + if (isalpha (base[0]) && base[1] == ':' && base[2] == G_DIR_SEPARATOR) + { + root_pos = 2; + if (!isalpha (path[0]) || path[1] != ':' || path[2] != G_DIR_SEPARATOR + || base[0] != path[0]) + return g_strdup (path); + } +#endif + + /* We step up each component of base_dir until we find a match with the + start of file. */ + pos = strlen (base) - 1; + for (;;) + { + /* Skip trailing '/'s. */ + if (pos > root_pos && base[pos] == G_DIR_SEPARATOR) + pos--; + + match = check_components_match (base, path, root_pos, pos + 1); + if (match) + break; + + /* They didn't match, so we move up one component and try again. */ + num_parents++; + while (pos && base[pos] != G_DIR_SEPARATOR) + pos--; + } + + /* If we match some components, build the relative path, else just return + path. */ + if (match) + { + path_len = strlen (path); + /* Skip over the '/', but special case for root directory. */ + if (pos == root_pos) + pos++; + else + pos += 2; + len = (num_parents * 3) + 1; + if (path_len > pos) + len += path_len - pos; + relative_path = g_malloc (len); + /* Add a '../' for each parent directory needed. */ + for (i = 0; i < num_parents; i++) + { + relative_path[i * 3] = '.'; + relative_path[i * 3 + 1] = '.'; + relative_path[i * 3 + 2] = G_DIR_SEPARATOR; + } + /* Add on the end of path, skipping the '/' after the component. */ + if (path_len > pos) + strcpy (relative_path + num_parents * 3, path + pos); + relative_path[len - 1] = '\0'; + + return relative_path; + } + else + return g_strdup (path); +} + + +/* This checks that the leading directory components of base match those in + path up to the given length. */ +static gboolean +check_components_match (const gchar *base, + const gchar *path, + gint root_pos, + gint len) +{ + if (strncmp (base, path, len)) + return FALSE; + + /* We also need to check that it is a complete component in path, i.e. + "/home/damon" should NOT match "/home/damon2". But root dir is a + special case. "/" matches "/home". */ + if (len == root_pos + 1 || path[len] == '\0' || path[len] == G_DIR_SEPARATOR) + return TRUE; + return FALSE; +} + + +/* Returns TRUE if file is in dir. Both paths must be absolute. file can + be a directory, and both can end in '/' or not. Note that we assume + that both are in proper form, i.e. there are no instances of '//', '.', + or '..' in either. */ +gboolean +glade_util_directory_contains_file (const gchar *dir, + const gchar *file) +{ + gint dir_len, file_len; + gchar *next_dir_separator; + + g_return_val_if_fail (g_path_is_absolute (dir), FALSE); + g_return_val_if_fail (g_path_is_absolute (file), FALSE); + + dir_len = strlen (dir); + file_len = strlen (file); + + /* First check that file matches dir up until dir finishes. */ + if (strncmp (dir, file, dir_len)) + return FALSE; + + /* If dir doesn't end in a '/', then we also need to check that the next + character in file is a '/'. */ + if (dir[dir_len - 1] != G_DIR_SEPARATOR) + { + if (file[dir_len] != G_DIR_SEPARATOR) + return FALSE; + dir_len++; + } + + /* If file is equivalent to dir we return FALSE. */ + if (dir_len == file_len) + return FALSE; + + /* Now we need to check that file is not in a subdirectory, i.e. there are + no more '/'s in file except possibly for the last character. */ + next_dir_separator = strchr (file + dir_len, G_DIR_SEPARATOR); + if (next_dir_separator && next_dir_separator < file + file_len - 1) + return FALSE; + + return TRUE; +} + + +/* Returns TRUE if the 2 directories are equivalent. Both must be absolute + paths, and may or may not end in '/'. */ +gboolean +glade_util_directories_equivalent (const gchar *dir1, + const gchar *dir2) +{ + gint dir1_len, dir2_len; + + g_return_val_if_fail (g_path_is_absolute (dir1), FALSE); + g_return_val_if_fail (g_path_is_absolute (dir2), FALSE); + + /* Find the length of both directories and decrement it if they end in a + '/'. */ + dir1_len = strlen (dir1); + dir2_len = strlen (dir2); + + if (dir1[dir1_len - 1] == G_DIR_SEPARATOR) + dir1_len--; + if (dir2[dir2_len - 1] == G_DIR_SEPARATOR) + dir2_len--; + + /* Now both lengths must be equal and the directories must match up to + that point. */ + if (dir1_len != dir2_len) + return FALSE; + + if (strncmp (dir1, dir2, dir1_len)) + return FALSE; + + return TRUE; +} + + +/* This is similar to GLib's dirname, but it makes sure the dirname ends with + a G_DIR_SEPARATOR. The returned string should be freed later. */ +gchar* +glade_util_dirname (const gchar *file_name) +{ + register gchar *base; + register guint len; + + g_return_val_if_fail (file_name != NULL, NULL); + + base = strrchr (file_name, G_DIR_SEPARATOR); + if (!base) + return g_strdup ("." G_DIR_SEPARATOR_S); + while (base > file_name && *base == G_DIR_SEPARATOR) + base--; + len = (guint) 1 + base - file_name; + + base = g_new (gchar, len + 2); + g_memmove (base, file_name, len); + if (len > 1) + base[len++] = G_DIR_SEPARATOR; + base[len] = 0; + + return base; +} + + +/* This returns the parent directory of the given directory, which may or may + not end in a G_DIR_SEPARATOR. If there is no parent directory, NULL is + returned. The returned string should be freed. dir MUST be absolute. */ +gchar* +glade_util_parent_directory (const gchar *dir) +{ + gchar *skipped_root, *parent_dir; + gint pos; + + g_return_val_if_fail (g_path_is_absolute (dir), NULL); + + /* We handle the root dir specially here, and return NULL. */ + skipped_root = (gchar*) g_path_skip_root ((gchar*) dir); + if (*skipped_root == '\0') + return NULL; + + /* Ignore any G_DIR_SEPARATOR at the end of dir (just by skipping the last + char), and step back to the previous G_DIR_SEPARATOR. */ + pos = strlen (dir) - 2; + while (pos >= 0 && dir[pos] != G_DIR_SEPARATOR) + pos--; + + /* This shouldn't really happen, since we dealt with the root dir above, + but just in case. */ + if (pos < 0) + return NULL; + + /* Check if the parent directory is the root directory. If it is, we just + want to return the root directory, i.e. "/" or "C:\". */ + if (pos <= skipped_root - dir) + pos = skipped_root - dir; + + parent_dir = g_malloc (pos + 1); + strncpy (parent_dir, dir, pos); + parent_dir[pos] = '\0'; + + return parent_dir; +} + + +/* This searches the $HOME/Projects directory to find the default directory to + use for the next project, e.g. $HOME/Projects/project1. The returned + directory should be freed when no longer needed. */ +GladeError* +glade_util_get_next_free_project_directory (gchar **project_directory_return, + gint *project_num_return) +{ + GladeError *error = NULL; + gchar *projects_dir, *project_string, *subdir; + const gchar *home_dir; + DIR *directory; + struct dirent *entry; + gint project_num, max_project_num, project_string_len; + gint num_matched, chars_matched; + + home_dir = g_get_home_dir (); + if (home_dir) + { + projects_dir = glade_util_make_absolute_path (home_dir, _("Projects")); + } + else + { +#ifdef _WIN32 + projects_dir = g_strdup ("C:\\Projects"); +#else + /* Just use the root directory. Though it probably won't be writable. */ + projects_dir = g_strdup (G_DIR_SEPARATOR_S); +#endif + } + + /* Step through the 'Projects' directory, if it exists, to find + subdirectories named 'projectXX', and get the highest number used so + far. */ + max_project_num = 0; + directory = opendir (projects_dir); + project_string = _("project"); + project_string_len = strlen (project_string); + if (directory == NULL) + { + if (errno != ENOENT) + { + error = glade_error_new_system (_("Couldn't open directory:\n %s\n"), + projects_dir); + g_free (projects_dir); + return error; + } + } + else + { + for (;;) + { + entry = readdir (directory); + + if (entry == NULL) + break; + + if (!strncmp (entry->d_name, project_string, project_string_len)) + { + /* Now see if it has a number on the end. */ + num_matched = sscanf (entry->d_name + project_string_len, + "%i%n", &project_num, &chars_matched); + if (num_matched >= 1 + && chars_matched == strlen (entry->d_name) - project_string_len) + { + max_project_num = MAX (max_project_num, project_num); + } + } + } + + closedir (directory); + } + + max_project_num++; + *project_num_return = max_project_num; + subdir = g_strdup_printf ("%s%i", project_string, max_project_num); + *project_directory_return = glade_util_make_absolute_path (projects_dir, + subdir); + g_free (subdir); + g_free (projects_dir); + + return NULL; +} + + +/* This will hold the last "TZ=XXX" string we used with putenv(). After we + call putenv() again to set a new TZ string, we can free the previous one. + As far as I know, no libc implementations actually free the memory used in + the environment variables (how could they know if it is a static string or + a malloc'ed string?), so we have to free it ourselves. */ +static char* saved_tz = NULL; + + +/* Sets the TZ environment variable to the given value, e.g. "UTC", returning + the old setting. + NOTE: You must call glade_util_reset_timezone() some time later to restore + the original TZ. Pass glade_util_reset_timezone() the string that + glade_util_set_timezone() returns. */ +gchar* +glade_util_set_timezone (const gchar *tz) +{ + char *old_tz, *old_tz_copy = NULL, *new_tz; + + /* Get the old TZ setting and save a copy of it to return. */ + old_tz = getenv ("TZ"); + if (old_tz) + { + old_tz_copy = g_malloc (strlen (old_tz) + 4); + strcpy (old_tz_copy, "TZ="); + strcpy (old_tz_copy + 3, old_tz); + } + + /* Create the new TZ string. */ + new_tz = g_malloc (strlen (tz) + 4); + strcpy (new_tz, "TZ="); + strcpy (new_tz + 3, tz); + + /* Add the new TZ to the environment. */ + putenv (new_tz); + tzset (); + + /* Free any previous TZ environment string we have used. */ + if (saved_tz) + g_free (saved_tz); + + /* Save a pointer to the TZ string we just set, so we can free it later. */ + saved_tz = new_tz; + + return old_tz_copy; /* This will be zero if the TZ env var was not set */ +} + + +void +glade_util_reset_timezone (gchar *tz) +{ + /* restore the original TZ setting. */ + if (tz) + putenv (tz); + else + putenv ("TZ"); /* Delete from environment */ + + tzset (); + + /* Free any previous TZ environment string we have used. */ + if (saved_tz) + g_free (saved_tz); + + /* Save a pointer to the TZ string we just set, so we can free it later. + (This can possibly be NULL if there was no TZ to restore.) */ + saved_tz = tz; +} + + +gboolean +glade_util_check_is_stock_id (const gchar *stock_id) +{ + static GHashTable *stock_hash = NULL; + + gboolean retval = FALSE; + + /* Create a hash of stock ids, so future calls will be fast. */ + if (!stock_hash) + { + GSList *stock_items, *elem; + + stock_hash = g_hash_table_new (g_str_hash, g_str_equal); + + stock_items = gtk_stock_list_ids (); + for (elem = stock_items; elem; elem = elem->next) + { + /* We just use the stock id for the key and the data, as we don't + need to store anything. We should free the keys at some point, + but we need them until Glade exits. */ + g_hash_table_insert (stock_hash, elem->data, elem->data); + } + + g_slist_free (stock_items); + } + + if (stock_id && g_hash_table_lookup (stock_hash, stock_id)) + retval = TRUE; + +#if 0 + g_print ("glade_util_check_is_stock_id: %s -> %i\n", + stock_id ? stock_id : "", retval); +#endif + + return retval; +} + + +static gint +compare_uline_labels (const gchar *labela, const gchar *labelb) +{ + for (;;) { + gunichar c1, c2; + + if (*labela == '\0') + return (*labelb == '\0') ? 0 : -1; + if (*labelb == '\0') + return 1; + + c1 = g_utf8_get_char (labela); + if (c1 == '_') + { + labela = g_utf8_next_char (labela); + c1 = g_utf8_get_char (labela); + } + + c2 = g_utf8_get_char (labelb); + if (c2 == '_') + { + labelb = g_utf8_next_char (labelb); + c2 = g_utf8_get_char (labelb); + } + + if (c1 < c2) + return -1; + if (c1 > c2) + return 1; + + labela = g_utf8_next_char (labela); + labelb = g_utf8_next_char (labelb); + } + + /* Shouldn't be reached. */ + return 0; +} + + +/* This is a GCompareFunc for comparing the labels of 2 stock items, ignoring + any '_' characters. It isn't particularly efficient. */ +gint +glade_util_compare_stock_labels (gconstpointer a, gconstpointer b) +{ + const gchar *stock_ida = a, *stock_idb = b; + GtkStockItem itema, itemb; + gboolean founda, foundb; + gint retval; + + founda = gtk_stock_lookup (stock_ida, &itema); + foundb = gtk_stock_lookup (stock_idb, &itemb); + + if (founda) + { + if (!foundb) + retval = -1; + else + /* FIXME: Not ideal for UTF-8. */ + retval = compare_uline_labels (itema.label, itemb.label); + } + else + { + if (!foundb) + retval = 0; + else + retval = 1; + } + + return retval; +} + + +/* These are pinched from gtkcombo.c */ +gpointer /* GtkListItem * */ +glade_util_gtk_combo_find (GtkCombo * combo) +{ + gchar *text; + gchar *ltext; + GList *clist; + int (*string_compare) (const char *, const char *); + + if (combo->case_sensitive) + string_compare = strcmp; + else + string_compare = g_strcasecmp; + + text = (gchar*) gtk_entry_get_text (GTK_ENTRY (combo->entry)); + clist = GTK_LIST (combo->list)->children; + + while (clist && clist->data) + { + ltext = glade_util_gtk_combo_func (GTK_LIST_ITEM (clist->data)); + if (!ltext) + continue; + if (!(*string_compare) (ltext, text)) + return (GtkListItem *) clist->data; + clist = clist->next; + } + + return NULL; +} + +gchar * +glade_util_gtk_combo_func (gpointer data) +{ + GtkListItem * listitem = data; + + /* I needed to pinch this as well - Damon. */ + static const gchar *gtk_combo_string_key = "gtk-combo-string-value"; + + GtkWidget *label; + gchar *ltext = NULL; + + ltext = (gchar *) gtk_object_get_data (GTK_OBJECT (listitem), + gtk_combo_string_key); + if (!ltext) + { + label = GTK_BIN (listitem)->child; + if (!label || !GTK_IS_LABEL (label)) + return NULL; + ltext = (gchar*) gtk_label_get_text (GTK_LABEL (label)); + } + return ltext; +} + +/** + * glade_enum_from_string + * @type: the GType for this enum type. + * @string: the string representation of the enum value. + * + * This helper routine is designed to be used by widget build routines to + * convert the string representations of enumeration values found in the + * XML descriptions to the integer values that can be used to configure + * the widget. + * + * Returns: the integer value for this enumeration, or 0 if it couldn't be + * found. + */ +gint +glade_enum_from_string (GType type, const char *string) +{ + GEnumClass *eclass; + GEnumValue *ev; + gchar *endptr; + gint ret = 0; + + ret = strtoul(string, &endptr, 0); + if (endptr != string) /* parsed a number */ + return ret; + + eclass = g_type_class_ref(type); + ev = g_enum_get_value_by_name(eclass, string); + if (!ev) ev = g_enum_get_value_by_nick(eclass, string); + if (ev) ret = ev->value; + + g_type_class_unref(eclass); + + return ret; +} + +/** + * glade_string_from_enum + * @type: the GType for this enum type. + * @value: the value of the enum + * + * This helper routine is designed to be used by widget build routines to + * convert the string representations of enumeration values found in the + * XML descriptions to the integer values that can be used to configure + * the widget. + * + * Returns: the string value for this enumeration + */ +const char * +glade_string_from_enum (GType type, gint value) +{ + GEnumClass *eclass; + GEnumValue *ev; + + eclass = g_type_class_ref(type); + + ev = g_enum_get_value (eclass, value); + + g_type_class_unref(eclass); + + return ev ? ev->value_name : "0"; +} + + +/** + * glade_util_flags_from_string + * @type: the GType for this flags type. + * @string: the string representation of the flags value. + * + * This helper routine is designed to be used by widget build routines + * to convert the string representations of flags values found in the + * XML descriptions to the integer values that can be used to + * configure the widget. The string is composed of string names or + * nicknames for various flags separated by '|'. + * + * Returns: the integer value for this flags string + */ +guint +glade_util_flags_from_string (GType type, const char *string) +{ + GFlagsClass *fclass; + gchar *endptr, *prevptr; + guint i, j, ret = 0; + char *flagstr; + + ret = strtoul(string, &endptr, 0); + if (endptr != string) /* parsed a number */ + return ret; + + fclass = g_type_class_ref(type); + + + flagstr = g_strdup (string); + for (ret = i = j = 0; ; i++) { + gboolean eos; + + eos = flagstr [i] == '\0'; + + if (eos || flagstr [i] == '|') { + GFlagsValue *fv; + const char *flag; + gunichar ch; + + flag = &flagstr [j]; + endptr = &flagstr [i]; + + if (!eos) { + flagstr [i++] = '\0'; + j = i; + } + + /* trim spaces */ + for (;;) + { + ch = g_utf8_get_char (flag); + if (!g_unichar_isspace (ch)) + break; + flag = g_utf8_next_char (flag); + } + + while (endptr > flag) + { + prevptr = g_utf8_prev_char (endptr); + ch = g_utf8_get_char (prevptr); + if (!g_unichar_isspace (ch)) + break; + endptr = prevptr; + } + + if (endptr > flag) + { + *endptr = '\0'; + fv = g_flags_get_value_by_name (fclass, flag); + + if (!fv) + fv = g_flags_get_value_by_nick (fclass, flag); + + if (fv) + ret |= fv->value; + else + g_warning ("Unknown flag: '%s'", flag); + } + + if (eos) + break; + } + } + + g_free (flagstr); + + g_type_class_unref(fclass); + + return ret; +} + +/** + * glade_util_string_from_flags + * @type: the GType for this flags type. + * @flags: the value of the flags + * + * This helper routine is designed to be used by widget build routines + * to convert the flags values into the string representations in the + * XML descriptions. + * The string is composed of string names or + * nicknames for various flags separated by '|'. + * + * Returns: the string representation of these flags. Must be freed. + */ +gchar * +glade_util_string_from_flags (GType type, guint flags) +{ + GFlagsClass *flags_class; + GString *string; + char *ret; + + flags_class = g_type_class_ref (type); + + string = g_string_new (""); + + if (flags_class->n_values) + { + GFlagsValue *fval; + + for (fval = flags_class->values; fval->value_name; fval++) + { + /* We have to be careful as some flags include 0 values, e.g. + BonoboDockItemBehavior uses 0 for BONOBO_DOCK_ITEM_BEH_NORMAL. + If a 0 value is available, we only output it if the entire + flags value is 0, otherwise we check if the bit-flag is set. */ + if ((fval->value == 0 && flags == 0) + || (fval->value && (fval->value & flags) == fval->value)) + { + if (string->len) + g_string_append_c (string, '|'); + g_string_append (string, fval->value_name); + } + } + } + + ret = string->str; + g_string_free (string, FALSE); + + g_type_class_unref (flags_class); + + return ret; +} + + +/* Returns TRUE if the widget should have a border width property. + Containers normally do, but Bonobo controls don't. + Also, GtkDialog widgets and their action area hbuttonboxes don't, as they + use style properties. */ +gboolean +glade_util_uses_border_width (GtkWidget *widget) +{ + char *child_name = gb_widget_get_child_name (widget); + +#ifdef USE_GNOME + if (BONOBO_IS_WIDGET (widget)) + return FALSE; +#endif + + /* The GtkDialog vbox has its border width set from a style property, so + we shouldn't show the property in Glade. */ + if (GTK_IS_VBOX (widget) && widget->parent && GTK_IS_DIALOG (widget->parent)) + return FALSE; + + if (child_name && !strcmp (child_name, GladeChildDialogActionArea)) + return FALSE; + + if (GTK_IS_CONTAINER (widget)) + return TRUE; + + return FALSE; +} + + +/* Converts a filename from UTF-8 to on-disk encoding, and sets it in a + GtkFileSelection. */ +void +glade_util_set_file_selection_filename (GtkWidget *filesel, + const gchar *filename_utf8) +{ + gchar *on_disk_filename; + + on_disk_filename = g_filename_from_utf8 (filename_utf8, -1, NULL, NULL, + NULL); + + gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (filesel), + on_disk_filename); + g_free (on_disk_filename); +} + + +/* Gets the selected file in a GtkFileSelection, converts it to UTF-8 and + returns it. Note that the returned string must be freed. */ +gchar* +glade_util_get_file_selection_filename (GtkWidget *filesel) +{ + const gchar *on_disk_filename; + gchar *filename; + + on_disk_filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (filesel)); + filename = g_filename_to_utf8 (on_disk_filename, -1, NULL, NULL, NULL); + return filename; +} + + +/* Like fopen but takes a UTF-8 filename and converts to on-disk encoding. */ +FILE* +glade_util_fopen (const gchar *filename_utf8, const gchar *mode) +{ + gchar *on_disk_filename; + FILE *fp; + + on_disk_filename = g_filename_from_utf8 (filename_utf8, -1, NULL, NULL, + NULL); + fp = fopen (on_disk_filename, mode); + g_free (on_disk_filename); + return fp; +} + + +/** + * glade_util_uri_list_parse: + * @uri_list: the text/urilist, must be NULL terminated. + * + * Extracts a list of file names from a standard text/uri-list, + * such as one you would get on a drop operation. + * This is mostly stolen from gnome-vfs-uri.c. + * + * Returns a GList of gchars. + **/ +GList * +glade_util_uri_list_parse (const gchar *uri_list) +{ + const gchar *p, *q; + GList *result = NULL; + + g_return_val_if_fail (uri_list != NULL, NULL); + + p = uri_list; + + /* We don't actually try to validate the URI according to RFC + * 2396, or even check for allowed characters - we just ignore + * comments and trim whitespace off the ends. We also + * allow LF delimination as well as the specified CRLF. + */ + while (p) + { + if (*p != '#') + { + gchar *retval; + gchar *path; + + while (g_ascii_isspace (*p)) + p++; + + q = p; + while ((*q != '\0') && (*q != '\n') && (*q != '\r')) + q++; + + if (q > p) + { + q--; + while (q > p && g_ascii_isspace (*q)) + q--; + + retval = g_new (gchar, q - p + 2); + memcpy (retval, p, q - p + 1); + retval[q - p + 1] = '\0'; + + path = g_filename_from_uri (retval, NULL, NULL); + if (!path) + { + g_free (retval); + continue; + } + + result = g_list_prepend (result, path); + + g_free (retval); + } + } + p = strchr (p, '\n'); + if (p) + p++; + } + + return g_list_reverse (result); +} + + +void +glade_util_get_translation_properties (GtkWidget *widget, + const gchar *property_name, + gboolean *translatable, + gchar **comments, + gboolean *context) +{ + gchar buffer[1024]; + + g_return_if_fail (GTK_IS_WIDGET (widget)); + + sprintf (buffer, "%s:::comments", property_name); + *comments = g_object_get_data (G_OBJECT (widget), buffer); + + /* This defaults to TRUE if no object data is set. */ + sprintf (buffer, "%s:::not_translatable", property_name); + *translatable = g_object_get_data (G_OBJECT (widget), buffer) ? FALSE : TRUE; + + /* This defaults to FALSE if no object data is set. */ + sprintf (buffer, "%s:::context", property_name); + *context = g_object_get_data (G_OBJECT (widget), buffer) ? TRUE : FALSE; +} + + +void +glade_util_set_translation_properties (GtkWidget *widget, + const gchar *property_name, + gboolean translatable, + const gchar *comments, + gboolean context) +{ + gchar buffer[1024]; + + g_return_if_fail (GTK_IS_WIDGET (widget)); + + sprintf (buffer, "%s:::comments", property_name); + g_object_set_data_full (G_OBJECT (widget), buffer, + g_strdup (comments), comments ? g_free : NULL); + + sprintf (buffer, "%s:::not_translatable", property_name); + g_object_set_data (G_OBJECT (widget), buffer, translatable ? NULL : "N"); + + sprintf (buffer, "%s:::context", property_name); + g_object_set_data (G_OBJECT (widget), buffer, context ? "Y" : NULL); +} + + +/* Copies the extra translation properties stored inside the widget data. */ +void +glade_util_copy_translation_properties (GtkWidget *from_widget, + const gchar *from_property_name, + GtkWidget *to_widget, + const gchar *to_property_name) +{ + gchar *comments; + gboolean translatable, context; + + g_return_if_fail (GTK_IS_WIDGET (from_widget)); + g_return_if_fail (GTK_IS_WIDGET (to_widget)); + + /* Copy the translator comments string, and translatable & context flags + from one widget to the other. */ + glade_util_get_translation_properties (from_widget, from_property_name, + &translatable, &comments, &context); + glade_util_set_translation_properties (to_widget, to_property_name, + translatable, comments, context); +} + |