/*
 * util.c - GUI utility functions
 *
 * Swami
 * Copyright (C) 1999-2014 Element Green <element@elementsofsound.org>
 *
 * 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; version 2
 * of the License only.
 *
 * 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 or point your web browser to http://www.gnu.org.
 */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>

#include <gtk/gtk.h>
#include <glib-object.h>

#include <libinstpatch/libinstpatch.h>

#include "util.h"
#include "SwamiguiRoot.h"
#include "i18n.h"

#ifdef __APPLE__
#include <CoreFoundation.h>
#endif

#if 0

/* time interval (in milliseconds) to check if log view should be popped */
#define LOG_POPUP_CHECK_INTERVAL 200

static gboolean log_viewactive = FALSE;	/* log window active? */
static gint log_poplevel = LogBad;
static gboolean log_popview = FALSE;	/* log view popup scheduled? */

static GtkWidget *log_view_widg = NULL;	/* currently active error view widg */

#endif

/* unique dialog system data */
typedef struct
{
    GtkWidget *dialog;
    const gchar *strkey;
    int key2;
} UniqueDialogKey;

gboolean unique_dialog_inited = FALSE;
GArray *unique_dialog_array;

static void swamigui_util_cb_waitfor_widget_destroyed(GtkWidget *widg, gpointer data);
static void
ui_dep_xml_start_element(GMarkupParseContext *context, const gchar *element_name,
                         const gchar **attribute_names, const gchar **attribute_values,
                         gpointer user_data, GError **error);
static void
ui_dep_xml_end_element(GMarkupParseContext *context, const gchar *element_name,
                       gpointer user_data, GError **error);
static void
ui_dep_xml_text(GMarkupParseContext *context, const gchar *text,
                gsize text_len, gpointer user_data, GError **error);


// static gboolean log_check_popup (gpointer data);
// static void log_view_cb_destroy (void);


/* initialize various utility services (unique dialog, log view timer, etc) */
void
swamigui_util_init(void)
{
    unique_dialog_array = g_array_new(FALSE, FALSE, sizeof(UniqueDialogKey));
    unique_dialog_inited = TRUE;

    //  g_timeout_add (LOG_POPUP_CHECK_INTERVAL, (GSourceFunc)log_check_popup, NULL);
}

void
swamigui_util_deinit(void)
{
    g_array_free(unique_dialog_array, TRUE);
}

guint
swamigui_util_unit_rgba_color_get_type(void)
{
    static guint unit_type = 0;

    if(!unit_type)
    {
        IpatchUnitInfo *info;

        info = ipatch_unit_info_new();
        info->value_type = G_TYPE_UINT;
        info->name = "rgba-color";
        info->label = _("Color");
        info->descr = _("RGBA color value (in the form 0xRRGGBBAA)");

        unit_type = ipatch_unit_register(info);

        ipatch_unit_info_free(info);
    }

    return (unit_type);
}

/**
 * swamigui_util_canvas_line_set:
 * @line: #GnomeCanvasLine object or an item with a "points" property
 * @x1: First X coordinate of line
 * @y1: First Y coordinate of line
 * @x2: Second X coordinate of line
 * @y2: Second Y coordinate of line
 *
 * A convenience function to set a #GnomeCanvasLine to a single line segment.
 * Can also be used on other #GnomeCanvasItem types which have a "points"
 * property.
 */
void
swamigui_util_canvas_line_set(GnomeCanvasItem *item, double x1, double y1,
                              double x2, double y2)
{
    GnomeCanvasPoints *points;

    points = gnome_canvas_points_new(2);

    points->coords[0] = x1;
    points->coords[1] = y1;
    points->coords[2] = x2;
    points->coords[3] = y2;

    g_object_set(item, "points", points, NULL);
    gnome_canvas_points_free(points);
}

/* Unique dialog system is for allowing unique non-modal dialogs for
   resources identified by a string key and an optional additional
   integer key, attempting to open up a second dialog for the same
   resource will cause the first dialog to be brought to the front of
   view and no additional dialog will be created */

/* looks up a unique dialog widget by its keys, returns the widget or NULL */
GtkWidget *
swamigui_util_lookup_unique_dialog(const gchar *strkey, gint key2)
{
    UniqueDialogKey *udkeyp;
    gint i;

    for(i = unique_dialog_array->len - 1; i >= 0; i--)
    {
        udkeyp = &g_array_index(unique_dialog_array, UniqueDialogKey, i);

        if((udkeyp->strkey == strkey || strcmp(udkeyp->strkey, strkey) == 0)
                && udkeyp->key2 == key2)
        {
            return (udkeyp->dialog);
        }
    }

    return (NULL);
}

/* register a unique dialog, if a dialog already exists with the same keys,
   then activate the existing dialog and return FALSE, otherwise register the
   new dialog and return TRUE */
gboolean
swamigui_util_register_unique_dialog(GtkWidget *dialog, const gchar *strkey,
                                     gint key2)
{
    UniqueDialogKey udkey;
    GtkWidget *widg;

    if((widg = swamigui_util_lookup_unique_dialog(strkey, key2)))
    {
        gtk_widget_activate(widg);
        return (FALSE);
    }

    /* We want the dialog set centered on top of the main window. To get
       this result we need that the dialog be not visible beforehand, otherwise
       gtk_window_set_transient_for() will be ignored. This is done in swami.ui
       by setting visible property to false: <property name="visible">False</property>
    */
    gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(swamigui_root->main_window));

    /* ensure the dialog will be destroy when the parent will be destroyed */
    gtk_window_set_destroy_with_parent((GtkWindow*)dialog, TRUE);

    udkey.dialog = dialog;
    udkey.strkey = strkey;
    udkey.key2 = key2;

    g_array_append_val(unique_dialog_array, udkey);

    gtk_signal_connect(GTK_OBJECT(dialog), "destroy",
                       (GtkSignalFunc)swamigui_util_unregister_unique_dialog, NULL);

    return (TRUE);
}

void
swamigui_util_unregister_unique_dialog(GtkWidget *dialog)
{
    UniqueDialogKey *udkeyp;
    gint i;

    for(i = unique_dialog_array->len - 1; i >= 0; i--)
    {
        udkeyp = &g_array_index(unique_dialog_array, UniqueDialogKey, i);

        if(udkeyp->dialog == dialog)
        {
            break;
        }
    }

    if(i >= 0)
    {
        g_array_remove_index(unique_dialog_array, i);
    }
}

/* activate (or raise) a unique dialog into view */
gboolean
swamigui_util_activate_unique_dialog(const gchar *strkey, gint key2)
{
    GtkWidget *dialog;

    if((dialog = swamigui_util_lookup_unique_dialog(strkey, key2)))
    {
        gdk_window_raise(GTK_WIDGET(dialog)->window);
        return (TRUE);
    }

    return (FALSE);
}

/* Destroy any dialog actually open

   This is usually called when the application quit.

   Note: This ensures that all SwamiControl object associated to child widgets
   inside these dialog are properly disconnected and freed when these child widget
   are destroyed.
*/
void swamigui_util_destroy_unique_dialog()
{
    UniqueDialogKey *udkeyp;
    gint i;

    for (i = unique_dialog_array->len - 1; i >= 0; i--)
    {
        udkeyp = &g_array_index(unique_dialog_array, UniqueDialogKey, i);
        gtk_widget_destroy(GTK_WIDGET(udkeyp->dialog));
    }
}

/* run gtk_main loop until the GtkObject data property "action" is !=
   NULL, mates nicely with swamigui_util_quick_popup, returns value of
   "action".  Useful for complex routines that require a lot of user
   dialog interaction.  Rather than having to save state info and exit
   and return to a routine, a call to this routine can be made which
   will wait for the user's choice and return the index of the button
   (or other user specified value), -1 if the widget was destroyed or
   -2 if gtk_main_quit was called */
gpointer
swamigui_util_waitfor_widget_action(GtkWidget *widg)
{
    GQuark quark;
    gpointer val = NULL;
    gboolean destroyed = FALSE;
    guint sigid;

    /* initialize the action variable to NULL */
    quark = gtk_object_data_force_id("action");
    gtk_object_set_data_by_id(GTK_OBJECT(widg), quark, NULL);

    /* already passing one variable to destroy signal handler, so bind this one
       as a GtkObject data item, will notify us if widget was destroyed */
    gtk_object_set_data(GTK_OBJECT(widg), "_destroyed", &destroyed);

    /* val is set to "action" by swamigui_util_cb_waitfor_widget_destroyed if the
       widget we are waiting for gets killed */
    sigid =
        gtk_signal_connect(GTK_OBJECT(widg), "destroy",
                           GTK_SIGNAL_FUNC(swamigui_util_cb_waitfor_widget_destroyed),
                           &val);

    do
    {
        if(gtk_main_iteration())   /* run the gtk main loop, wait if no events */
        {
            val = GINT_TO_POINTER(-2);    /* gtk_main_quit was called, return -2 */
        }
        else if(val == NULL)	/* check the "action" data property */
        {
            val = gtk_object_get_data_by_id(GTK_OBJECT(widg), quark);
        }
    }
    while(val == NULL);		/* loop until "action" is set */

    if(!destroyed)
    {
        gtk_signal_disconnect(GTK_OBJECT(widg), sigid);
    }

    return (val);
}

static void
swamigui_util_cb_waitfor_widget_destroyed(GtkWidget *widg, gpointer data)
{
    gpointer *val = data;
    gpointer action;
    gboolean *destroyed;

    action = gtk_object_get_data(GTK_OBJECT(widg), "action");
    destroyed = gtk_object_get_data(GTK_OBJECT(widg), "_destroyed");

    *destroyed = TRUE;

    if(action)
    {
        *val = action;
    }
    else
    {
        *val = GINT_TO_POINTER(-1);
    }
}

/* a callback for widgets (buttons, etc) within a "parent" widget used by
  swamigui_util_waitfor_widget_action, sets "action" to the specified "value" */
void
swamigui_util_widget_action(GtkWidget *cbwidg, gpointer value)
{
    GtkWidget *parent;

    parent = gtk_object_get_data(GTK_OBJECT(cbwidg), "parent");
    gtk_object_set_data(GTK_OBJECT(parent), "action", value);
}

/* Structure used by XML parser when finding UI dependencies */
typedef struct
{
    GHashTable *dephash;
    char *object_id;              // Current toplevel object ID (if tracking for deps)
    gboolean in_prop;             // TRUE if in dependent property element (model or adjustment)
    GPtrArray *deparray;          // Array of strings of the object ID dependencies
} UiDepBag;

/**
 * swamigui_util_glade_create:
 * @name: Name of the GtkBuilder widget to create
 *
 * Creates a GtkBuilder widget, by @name, from the main Swami UI XML file.
 * Prints a warning if the named widget does not exist.
 *
 * Returns: Newly created GtkBuilder widget (which the caller owns a ref to)
 *    or %NULL on error
 */
GtkWidget *
swamigui_util_glade_create(const char *name)
{
    static GHashTable *dephash = NULL;    // Object dependency hash
    char **object_ids, **dep_ids;
    GtkBuilder *builder;
    GError *err = NULL;
    GtkWidget *widg;
    gchar *resdir, *filename;
    int count;

    resdir = swamigui_util_get_resource_path(SWAMIGUI_RESOURCE_PATH_UIXML);  /* ++ alloc */
    filename = g_build_filename(resdir, "swami-2.ui", NULL);   /* ++ alloc */
    g_free(resdir);  /* -- free resdir */

    /* One time creation of hash of object dependencies - Wish GtkBuilder did this */
    if(!dephash)
    {
        GMarkupParser parser = { 0 };
        GMarkupParseContext *context;
        UiDepBag depbag = { 0 };
        char *uixml = NULL;
        gsize len;

        dephash = g_hash_table_new(g_str_hash, g_str_equal);

        depbag.dephash = dephash;
        depbag.deparray = g_ptr_array_new();
        parser.start_element = ui_dep_xml_start_element;
        parser.end_element = ui_dep_xml_end_element;
        parser.text = ui_dep_xml_text;
        context = g_markup_parse_context_new(&parser, 0, &depbag, NULL);

        if(g_file_get_contents(filename, &uixml, &len, &err))
        {
            if(!g_markup_parse_context_parse(context, uixml, len, &err)
                    || !g_markup_parse_context_end_parse(context, &err))
            {
                g_critical("Failed to parse UI XML file '%s': %s", filename,
                           err->message);
                g_clear_error(&err);
            }

            g_free(uixml);    /* -- free XML content */
        }
        else
        {
            g_critical("Failed to load UI XML file '%s': %s", filename,
                       err->message);
            g_clear_error(&err);
        }

        g_ptr_array_free(depbag.deparray, TRUE);
        g_markup_parse_context_free(context);
    }

    dep_ids = g_hash_table_lookup(dephash, name);

    if(dep_ids)
        for(count = 0; dep_ids[count]; count++);
    else
    {
        count = 0;
    }

    object_ids = g_new(char *, count + 2);        /* ++ object_ids */

    if(dep_ids)
    {
        memcpy(object_ids, dep_ids, count * sizeof(char *));
    }

    object_ids[count] = (char *)name;
    object_ids[count + 1] = NULL;

    builder = gtk_builder_new();          /* ++ ref new builder */

    if(gtk_builder_add_objects_from_file(builder, filename, object_ids, &err) == 0)
    {
        g_critical("Failed to load UI interface '%s': %s", name,
                   err->message);
        g_clear_error(&err);

        g_free(filename);           /* -- free allocated filename */
        g_object_unref(builder);    /* -- unref builder */
        g_free(object_ids);         /* -- free object IDs */

        return (NULL);
    }

    g_free(filename);          /* -- free allocated filename */
    g_free(object_ids);         /* -- free object IDs */

    gtk_builder_connect_signals(builder, NULL);
    widg = (GtkWidget *)g_object_ref(gtk_builder_get_object(builder, name));    /* ++ ref for caller */
    g_object_unref(builder);    /* -- unref builder */

    return (widg);
}

/* UI object dependencies XML start element callback */
static void
ui_dep_xml_start_element(GMarkupParseContext *context, const gchar *element_name,
                         const gchar **attribute_names, const gchar **attribute_values,
                         gpointer user_data, GError **error)
{
    UiDepBag *bag = user_data;
    const GSList *stack;
    int i;

    if(!bag->object_id)
    {
        stack = g_markup_parse_context_get_element_stack(context);

        if(stack && stack->next && !stack->next->next
                && strcmp(element_name, "object") == 0)
        {
            for(i = 0; attribute_names[i]; i++)
                if(strcmp(attribute_names[i], "id") == 0)
                {
                    bag->object_id = g_strdup(attribute_values[i]);
                    break;
                }
        }
    }
    else if(strcmp(element_name, "property") == 0)
    {
        for(i = 0; attribute_names[i]; i++)
            if(strcmp(attribute_names[i], "name") == 0)
            {
                if(strcmp(attribute_values[i], "model") == 0
                        || strcmp(attribute_values[i], "adjustment") == 0)
                {
                    bag->in_prop = TRUE;
                }

                break;
            }
    }
}

/* Callback for XML end of element for UI dependency search */
static void
ui_dep_xml_end_element(GMarkupParseContext *context, const gchar *element_name,
                       gpointer user_data, GError **error)
{
    UiDepBag *bag = user_data;
    const GSList *stack;
    char **depids;

    if(bag->in_prop)
    {
        bag->in_prop = FALSE;
        return;
    }
    else if(bag->object_id)
    {
        stack = g_markup_parse_context_get_element_stack(context);

        if(stack && stack->next && !stack->next->next)
        {
            if(bag->deparray->len > 0)
            {
                depids = g_new(char *, bag->deparray->len + 1);
                memcpy(depids, bag->deparray->pdata, bag->deparray->len * sizeof(gpointer));
                depids[bag->deparray->len] = NULL;    // NULL terminated

                /* Hash takes over allocation of object_id and dependency object IDs (forever) */
                g_hash_table_insert(bag->dephash, bag->object_id, depids);
                g_ptr_array_set_size(bag->deparray, 0);
            }
            else
            {
                g_free(bag->object_id);
            }

            bag->object_id = NULL;
        }
    }
}

/* Called for text values in GtkBuilder UI XML dependency search. */
static void
ui_dep_xml_text(GMarkupParseContext *context, const gchar *text,
                gsize text_len, gpointer user_data, GError **error)
{
    UiDepBag *bag = user_data;

    if(!bag->in_prop)
    {
        return;
    }

    g_ptr_array_add(bag->deparray, g_strdup(text));
}

/**
 * swamigui_util_glade_lookup:
 * @widget: A GtkBuilder generated widget or a child there of.
 * @name: Name of widget in same GtkBuilder interface as @widget to get.
 *
 * Find a GtkBuilder generated widget, by @name, via any other widget in
 * the same widget interface.  A warning is printed if the widget is not found
 * to help with debugging, when a widget is expected.  Use
 * swamigui_util_glade_lookup_nowarn() to check if the named
 * widget does not exist, and not display a warning.
 *
 * Returns: The widget or %NULL if not found.
 */
GtkWidget *
swamigui_util_glade_lookup(GtkWidget *widget, const char *name)
{
    GtkWidget *w;

    w = swamigui_util_glade_lookup_nowarn(widget, name);

    if(!w)
    {
        g_warning("libglade widget not found: %s", name);
    }

    return (w);
}

typedef struct
{
    GtkWidget *found;
    const char *name;
    GtkWidget *skip;
} FindBag;

/* Recursive function to walk a GtkContainer looking for a GtkBuilder widget by name */
static void
swamigui_util_glade_lookup_container_foreach(GtkWidget *widget, gpointer data)
{
    FindBag *findbag = data;
    const char *name;

    if(findbag->found || widget == findbag->skip)
    {
        return;
    }

    name = gtk_buildable_get_name(GTK_BUILDABLE(widget));

    if(name && strcmp(name, findbag->name) == 0)
    {
        findbag->found = widget;
        return;
    }

    if(GTK_IS_CONTAINER(widget))
        gtk_container_foreach(GTK_CONTAINER(widget),
                              swamigui_util_glade_lookup_container_foreach, findbag);
}

/**
 * swamigui_util_glade_lookup_nowarn:
 * @widget: A GtkBuilder generated widget or a child there of.
 * @name: Name of widget in same GtkBuilder interface as @widget to get.
 *
 * Like swamigui_util_glade_lookup() but does not print a warning if named
 * widget is not found.
 *
 * Returns: The widget or %NULL if not found.
 */
GtkWidget *
swamigui_util_glade_lookup_nowarn(GtkWidget *widget, const char *name)
{
    FindBag findbag = { 0 };

    g_return_val_if_fail(GTK_IS_WIDGET(widget), NULL);

    findbag.name = name;

    for(; widget != NULL; widget = gtk_widget_get_parent(widget))
    {
        gtk_container_foreach(GTK_CONTAINER(widget),
                              swamigui_util_glade_lookup_container_foreach, &findbag);
        findbag.skip = widget;
    }

    return (findbag.found);
}

int
swamigui_util_option_menu_index(GtkWidget *opmenu)
{
    GtkWidget *menu, *actv;

    g_return_val_if_fail(GTK_IS_OPTION_MENU(opmenu), 0);

    menu = gtk_option_menu_get_menu(GTK_OPTION_MENU(opmenu));
    actv = gtk_menu_get_active(GTK_MENU(menu));

    return (g_list_index(GTK_MENU_SHELL(menu)->children, actv));
}

#if 0


/* a callback for a glib timeout that periodically checks if the log view
   should be popped by the GTK thread (see swamigui_util_init) */
static gboolean
log_check_popup(gpointer data)
{
    if(log_popview)
    {
        log_popview = FALSE;
        log_view(NULL);
    }

    return (TRUE);
}

/* log_view - Display log view window */
void
log_view(gchar *title)
{
    GtkWidget *dialog;
    GtkWidget *hbox;
    GtkWidget *msgarea;
    GtkAdjustment *adj;
    GtkWidget *vscrollbar;
    GtkWidget *btn;
    static GStaticMutex mutex = G_STATIC_MUTEX_INIT;

    /* need to lock test and set of log_viewactive */
    g_static_mutex_lock(&mutex);

    if(log_viewactive)
    {
        g_static_mutex_unlock(&mutex);
        return;
    }

    log_viewactive = TRUE;
    g_static_mutex_unlock(&mutex);

    if(title)
    {
        dialog = gtk_dialog_new(title);
    }
    else
    {
        dialog = gtk_dialog_new(_("Swami log"));
    }

    hbox = gtk_hbox_new(FALSE, 0);
    gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox, TRUE, TRUE, 0);
    gtk_widget_show(hbox);

    msgarea = gtk_text_new(NULL, NULL);
    gtk_widget_set_default_size(msgarea, 400, 100);
    gtk_text_set_editable(GTK_TEXT(msgarea), FALSE);
    gtk_text_set_word_wrap(GTK_TEXT(msgarea), FALSE);

    /* have to lock on log read, another thread might be writing to it */
    g_mutex_lock(log_mutex);
    gtk_text_insert(GTK_TEXT(msgarea), NULL, NULL, NULL, log_buf->str, -1);
    g_mutex_unlock(log_mutex);

    gtk_box_pack_start(GTK_BOX(hbox), msgarea, TRUE, TRUE, 0);
    gtk_widget_show(msgarea);

    adj = GTK_TEXT(msgarea)->vadj;	/* get the message area's vert adj */

    vscrollbar = gtk_vscrollbar_new(adj);
    gtk_box_pack_start(GTK_BOX(hbox), vscrollbar, FALSE, FALSE, 0);
    gtk_widget_show(vscrollbar);

    btn = gtk_button_new_with_label(_("OK"));
    gtk_signal_connect_object(GTK_OBJECT(btn), "clicked",
                              (GtkSignalFunc) gtk_widget_destroy, GTK_OBJECT(dialog));
    gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area), btn,
                       FALSE, FALSE, 0);
    gtk_widget_show(btn);

    gtk_signal_connect_object(GTK_OBJECT(dialog), "destroy",
                              GTK_SIGNAL_FUNC(log_view_cb_destroy), NULL);

    gtk_widget_show(dialog);

    log_view_widg = NULL;
}

/* reset dialog active variables */
static void
log_view_cb_destroy(void)
{
    log_viewactive = FALSE;
    log_view_widg = NULL;
}

#endif


/**
 * swamigui_util_str_crlf2lf:
 * @str: String to convert
 *
 * Convert all dos newlines ("\r\n") to unix newlines "\n" in a string
 *
 * Returns: New string with converted newlines, should be freed when done with
 */
char *
swamigui_util_str_crlf2lf(char *str)
{
    char *newstr, *s;

    newstr = g_new(char, strlen(str) + 1);
    s = newstr;

    while(*str != '\0')
    {
        if(*str != '\r' || *(str + 1) != '\n')
        {
            *(s++) = *str;
        }

        str++;
    }

    *s = '\0';

    return (newstr);
}

/**
 * swamigui_util_str_lf2crlf:
 * @str: String to convert
 *
 * Convert all unix newlines "\n" to dos newlines ("\r\n") in a string
 *
 * Returns: New string with converted newlines, should be freed when done with
 */
char *
swamigui_util_str_lf2crlf(char *str)
{
    GString *gs;
    char *s;

    gs = g_string_sized_new(sizeof(str));

    while(*str != '\0')
    {
        if(*str != '\n')
        {
            gs = g_string_append_c(gs, *str);
        }
        else
        {
            gs = g_string_append(gs, "\r\n");
        }

        str++;
    }

    s = gs->str;
    g_string_free(gs, FALSE);	/* character segment is not free'd */

    return (s);
}

/**
 * swamigui_util_substrcmp:
 * @sub: Partial string to search for in str
 * @str: String to search for sub string in
 *
 * Search for a sub string in a string
 *
 * Returns: %TRUE if "sub" is found in "str", %FALSE otherwise
 */
int
swamigui_util_substrcmp(char *sub, char *str)
{
    char *s, *s2;

    if(!*sub)
    {
        return (TRUE);    /* null string, matches */
    }

    while(*str)
    {
        if(tolower(*str) == tolower(*sub))
        {
            s = sub + 1;
            s2 = str + 1;

            while(*s && *s2)
            {
                if(tolower(*s) != tolower(*s2))
                {
                    break;
                }

                s++;
                s2++;
            }

            if(!*s)
            {
                return (TRUE);
            }
        }

        str++;
    }

    return (FALSE);
}

gchar *
swamigui_util_get_resource_path(SwamiResourcePath kind)
{
    static gchar *res_root;

    /* Init res_root if not done yet */
    if(!res_root)
    {
#if defined (G_OS_WIN32)
        res_root = g_win32_get_package_installation_directory_of_module(NULL);
#elif defined (__APPLE__)
        CFURLRef ResDirURL;
        gchar buf[PATH_MAX];

        /* Use bundled resources if we are in an app bundle */
        ResDirURL = CFBundleCopyResourcesDirectoryURL(CFBundleGetMainBundle());

        if(CFURLGetFileSystemRepresentation(ResDirURL, TRUE, (UInt8 *)buf, PATH_MAX)
                && g_str_has_suffix(buf, ".app/Contents/Resources"))
        {
            res_root = g_strdup(buf);
        }

        CFRelease(ResDirURL);
#endif
#ifdef SOURCE_BUILD
        g_free(res_root);  /* drop previous value if any and replace with src dir */
        res_root = g_build_filename(SOURCE_DIR, "/src/swamigui", NULL);
#endif
    }

    /* default/fallback if all else fails: use default paths */
    if(!res_root)
    {
        res_root = g_strdup("");
    }

    switch(kind)
    {
    case SWAMIGUI_RESOURCE_PATH_ROOT:
        return (*res_root ? g_strdup(res_root) : NULL);

    case SWAMIGUI_RESOURCE_PATH_UIXML:
        return (*res_root ? g_strdup(res_root) : g_strdup(UIXML_DIR));

    case SWAMIGUI_RESOURCE_PATH_IMAGES:
        return (*res_root ?
                g_build_filename(res_root, "images", NULL) :
                g_strdup(IMAGES_DIR));

    default:
        return NULL;
    }
}
