/*
 * GToolKit - Objective-C interface to the GIMP Toolkit
 * Copyright (c) 1998, 1999, 2000  Elmar Ludwig
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307, USA.
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <Foundation/NSAutoreleasePool.h>
#include <Foundation/NSDictionary.h>
#include <Foundation/NSException.h>
#include <Foundation/NSObjCRuntime.h>
#include <Foundation/NSScanner.h>
#include <GToolKit/GTKAction.h>
#include <GToolKit/GTKGladeXML.h>
#ifdef HAVE_LIBGLADE
#include <glade/glade.h>
#endif /* HAVE_LIBGLADE */

@implementation GTKGladeXML

#ifdef HAVE_LIBGLADE
static NSString *volatile gettext_domain;	// default translation domain

 + (void) initialize
{
#ifdef DEBUG
    fprintf(stderr, "[GTKGladeXML initialize]\n");
#endif
    glade_init();
}
#endif /* HAVE_LIBGLADE */

/*
 * Return the unique type id of this class.
 */
+ (GtkType) getType
{
#ifdef HAVE_LIBGLADE
    return glade_xml_get_type();
#else
    return GTK_TYPE_INVALID;
#endif /* HAVE_LIBGLADE */
}

/*
 * Set the default translation domain for all translatable strings in the
 * XML files to /domain/ (this requires GNU gettext). The initial translation
 * domain is |nil|, i.e. the current text domain will be used.
 */
+ (void) setDefaultTranslationDomain:(NSString *) domain
{
#ifdef HAVE_LIBGLADE
    NSString *old_domain = gettext_domain;

    gettext_domain = [domain copy];
    [old_domain release];
#endif /* HAVE_LIBGLADE */
}

/*
 * Return the current default translation domain used by GladeXML
 * (or |nil| if no translation domain has been set).
 * @see +setDefaultTranslationDomain:
 */
+ (NSString *) defaultTranslationDomain
{
#ifdef HAVE_LIBGLADE
    return [[gettext_domain retain] autorelease];
#else
    return nil;
#endif /* HAVE_LIBGLADE */
}

/*
 * Create an autoreleased GTKGladeXML instance. It will read the given /file/
 * and construct all objects stored inside the file (if /root/ is |nil|) or
 * just a subtree starting from the widget named /root/. This method is
 * equivalent to the call:
 * <pre>
 * [... gladeXMLFromFile:/file/ rootObject:/root/ nameTable:
 *    [NSDictionary dictionaryWithObject:/owner/ forKey:@"owner"]];</pre>
 * @see -initFromFile:rootObject:nameTable:
 */
+ (id) gladeXMLFromFile:(NSString *) file rootObject:(NSString *) root
       owner:(id) owner
{
    return [[[self alloc] initFromFile:file rootObject:root owner:owner]
	    autorelease];
}

/*
 * Create an autoreleased GTKGladeXML instance. It will read the given /file/
 * and construct all objects stored inside the file (if /root/ is |nil|) or
 * just a subtree starting from the widget named /root/. See below for a
 * description of the /context/ parameter and how it is used.
 * @see -initFromFile:rootObject:nameTable:
 */
+ (id) gladeXMLFromFile:(NSString *) file rootObject:(NSString *) root
       nameTable:(NSDictionary *) context
{
    return [[[self alloc] initFromFile:file rootObject:root nameTable:context]
	    autorelease];
}

/*
 * Initialize a new GTKGladeXML instance. It will read the given /file/
 * and construct all objects stored inside the file (if /root/ is |nil|)
 * or just a subtree starting from the widget named /root/. This method
 * is equivalent to the call:
 * <pre>
 * [... initFromFile:/file/ rootObject:/root/ nameTable:
 *    [NSDictionary dictionaryWithObject:/owner/ forKey:@"owner"]];</pre>
 * @see -initFromFile:rootObject:nameTable:
 */
- (id) initFromFile:(NSString *) file rootObject:(NSString *) root
       owner:(id) owner
{
    return [self initFromFile:file rootObject:root nameTable:
		 [NSDictionary dictionaryWithObject:owner forKey:@"owner"]];
}

#ifdef HAVE_LIBGLADE
struct ConnectFuncInfo		// user data for xml_connect()
{
    GTKGladeXML *self;		// current widget tree
    NSDictionary *dict;		// context dictionary
};

/*
 * This is the GladeXML connect function. It will establish signal handler
 * connections as specified in the XML file (with some extended features).
 * Used by glade_xml_signal_autoconnect_full() below.
 */
static void xml_connect (const gchar *handler_name, GtkObject *object,
			 const gchar *signal_name, const gchar *signal_data,
			 GtkObject *_target, gboolean after, gpointer _info)
{
    NSString *handler, *signal, *data;
    GTKAction *action;
    SEL selector;
    id target;

    if (signal_name == NULL) signal_name = "(nil)";
#ifdef DEBUG
    if (handler_name == NULL)		// should never happen
    {
	g_warning("missing handler for %s::%s (ignored)",
		  glade_get_widget_name((GtkWidget *) object), signal_name);
	return;
    }

    fprintf(stderr, "(xml_connect) %s::%s, handler [%s %s %s]\n",
	    glade_get_widget_name((GtkWidget *) object), signal_name,
	    _target ? glade_get_widget_name((GtkWidget *) _target) : "",
	    handler_name, signal_data ? signal_data : "");
#endif /* DEBUG */
    handler = gtoolkit_objc_string(handler_name);

    if (strchr(handler_name, ' '))	// "target selector data"
    {
	struct ConnectFuncInfo *info = _info;
	NSScanner *scanner = [NSScanner scannerWithString:handler];
	NSString *name, *space = @" ";

	[scanner scanUpToString:space intoString:&name];
	[scanner scanUpToString:space intoString:&handler];
	if (![scanner scanUpToString:space intoString:&data]) data = nil;
	if ((target = [info->dict objectForKey:name]) == nil)
	    target = [info->self getWidget:name];
    }
    else				// separate fields
    {
	data = gtoolkit_objc_string(signal_data);
	target = GTOOLKIT_OBJECT(_target);
    }

    if (target == nil)			// oops: no target
    {
	g_warning("invalid target for %s::%s (ignored)",
		  glade_get_widget_name((GtkWidget *) object), signal_name);
	return;
    }

    signal = gtoolkit_objc_string(signal_name);
    selector = NSSelectorFromString(handler);

    if (selector == 0)			// oops: no selector
    {
	g_warning("unknown selector for %s::%s (ignored)",
		  glade_get_widget_name((GtkWidget *) object), signal_name);
	return;
    }

    action = [GTKAction actionWithTarget:target selector:selector data:data];
    if (data) [action retainData];

    if (after)
	[GTOOLKIT_OBJECT(object) connectSignal:signal withActionAfter:action];
    else
	[GTOOLKIT_OBJECT(object) connectSignal:signal withAction:action];
}
#endif /* HAVE_LIBGLADE */

/*
 * Initialize a new GTKGladeXML instance. It will read the given /file/
 * and construct all objects stored inside the file (if /root/ is |nil|)
 * or just a subtree starting from the widget named /root/. The /context/
 * parameter contains an external name table of objects in your program
 * that the loaded interface description may refer to in its signal
 * connections.<p>
 * The Objective-C interface to libglade allows some extended features:<p>
 * <ul>
 * <li>	The signal handler is an Objective-C method selector, not a C
 *	function name. This is why you have to specify an additional
 *	parameter, the name of a target object.
 * <li>	The name of the target object can be entered in the "Object:" field
 *	(see the Properties window in Glade) if it refers to an object in the
 *	same XML file (e.g. another widget).
 * <li>	Alternatively, the signal handler itself can take the special form:<p>
 *	"/target_name/ /method_selector/ /user_data/" (without the quotes)<p>
 *	In this case, the /target_name/ may refer to either an object in the
 *	XML file or an object from the external name table (the /context/
 *	argument described above). /user_data/ is optional and can be missing.
 * <li>	A widget name of /class_name/::/widget_name/ indicates that the
 *	given Objective-C class should be used when restoring the object from
 *	the XML file. This class must exist in your program and it must be a
 *	subclass of the GTK+ class used in Glade.<br>
 *	<em>This feature is still experimental.</em><p>
 *	Note:!! The current implementation always restores such objects by
 *	calling the @GTK#-initWithGtk: initializer method, regardless of any
 *	other initializer methods you may have defined in the subclass.
 * </ul><p>
 * This method automatically retains all widgets at the root of the created
 * widget hierarchy, typically windows, dialogs or popup-menus (take a look
 * at the widget tree in Glade), or the object specified by /root/.
 * @return If the file was successfully loaded, this method returns self, \
 * otherwise the receiver is released and an NSGenericException is raised.
 */
- (id) initFromFile:(NSString *) file rootObject:(NSString *) root
       nameTable:(NSDictionary *) context
{
#ifdef HAVE_LIBGLADE
    NSAutoreleasePool *pool = [NSAutoreleasePool new];
    GladeXML *xml = glade_xml_new_with_domain(gtoolkit_utf8_string(file),
	    gtoolkit_utf8_string(root), gtoolkit_utf8_string(gettext_domain));
    struct ConnectFuncInfo info;

    if (xml)
    {
	[super initWithGtk:xml];
	info.self = self, info.dict = context;
	// there should be a way to enumerate toplevel widgets
	glade_xml_signal_autoconnect_full(xml, xml_connect, &info);
    }
    else
    {
	[self release];
	[NSException raise:NSGenericException
	    format:@"%@: loading user interface failed", file];
    }

    [pool release];
    return self;
#else
    [self release];
    [NSException raise:NSGenericException
	format:@"Glade support not available - install libglade"];
    return nil;
#endif /* HAVE_LIBGLADE */
}

/*
 * This method can resolve several widget references with a single method
 * call, so it can be used to avoid repeated invocations of @-getWidget:
 * (see below). It may be called with any number of /name///ref_pointer/
 * pairs, followed by a single |nil| argument to terminate the variable
 * argument list. A /ref_pointer/ is the address of a variable of type |id|,
 * |GTKObject *|, etc. (in fact, any pointer will do) where the result can
 * be stored.<p>
 * The method tries to look up (by name) each widget in turn and stores the
 * corresponding reference (or |nil| if a widget with this name could not
 * be found) in the address that the following argument points to. If the
 * ref_pointer for a given name is |NULL|, no value will be assigned.
 * It is typically used like this:
 * <pre>
 * GTKWindow *my_window;
 * GTKButton *ok, *cancel;
 *
 * [... resolveReferences:@"window", &my_window, @"ok_button", &ok,
 *    @"cancel_button", &cancel, nil];</pre>
 * @return |YES| if all references could be resolved, |NO| otherwise
 * @see -getWidget:
 */
- (BOOL) resolveReferences:(NSString *) name, ...
{
#ifdef HAVE_LIBGLADE
    va_list ap;
    BOOL result = YES;

    for (va_start(ap, name); name; name = va_arg(ap, NSString *))
    {
	id *ref_ptr = va_arg(ap, id *);

	if (ref_ptr && (*ref_ptr = [self getWidget:name]) == nil) result = NO;
    }

    va_end(ap);
    return result;
#else
    return NO;
#endif /* HAVE_LIBGLADE */
}

/*
 * Return the widget in the XML file with the name /name/, or |nil| if no
 * such object exists.
 */
- (GTKWidget *) getWidget:(NSString *) name
{
#ifdef HAVE_LIBGLADE
    return GTOOLKIT_OBJECT(glade_xml_get_widget(gtk,
					gtoolkit_utf8_string(name)));
#else
    return nil;
#endif /* HAVE_LIBGLADE */
}

/*
 * Return the widget in the XML file with the long name (including all its
 * parent widget names, separated by periods) /name/, or |nil| if no such
 * object exists.
 */
- (GTKWidget *) getWidgetByLongName:(NSString *) name
{
#ifdef HAVE_LIBGLADE
    return GTOOLKIT_OBJECT(glade_xml_get_widget_by_long_name(gtk,
					gtoolkit_utf8_string(name)));
#else
    return nil;
#endif /* HAVE_LIBGLADE */
}
@end
