/*
 * GToolKit - Objective-C interface to the GIMP Toolkit
 * Copyright (c) 1998  Elmar Ludwig - Universitaet Osnabrueck
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library 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.
 */
#include <stdarg.h>
#include <stdio.h>
#include <Foundation/NSAutoreleasePool.h>
#include <Foundation/NSArray.h>
#include <Foundation/NSException.h>
#include <Foundation/NSLock.h>
#include <Foundation/NSThread.h>
#include <Foundation/NSUtilities.h>
#include <GToolKit/GToolKit.h>

static void clicked (GtkWidget *widget)
{
    [GTKApp stopModalWithCode:[Gtk_to_Object(widget, 0) tag]];
}

static gint delete (GtkWidget *widget, GdkEvent *event)
{
    clicked(widget);
    return NO;
}

static GTKButton *insert (GTKBox *box, NSString *label, int tag)
{
    GTKButton *button = [GTKButton buttonWithLabel:label];
    GtkButton *gtk_button = [button gtk];

    [button setTag:tag];
    [box packStart:button expand:YES fill:NO padding:0];
    gtk_misc_set_padding((GtkMisc *) gtk_button->child, 4, 0);
    GTK_WIDGET_SET_FLAGS(gtk_button, GTK_CAN_DEFAULT);
    gtk_signal_connect((GtkObject *) gtk_button, "clicked",
		       (GtkSignalFunc) clicked, NULL);
    return button;
}

/*
 * Display a modal message box or alert panel.
 * Return value is n-1 if button number n is clicked (e.g. 0 for button1).
 */
int GTKRunMessageBox (NSString *title, NSString *format, NSString *button1,
		      NSString *button2, NSString *button3, ...)
{
    GTKDialog *window = [GTKDialog dialog];
    GTKBox *hbox = (GTKBox *) [window actionArea];
    GTKBox *vbox = (GTKBox *) [window vbox];
    GTKButton *focus = nil;
    GTKLabel *label;
    NSString *text;
    int result, tag = 0;
    va_list ap;

    if (button1) focus = insert(hbox, button1, tag);
    if (button2) insert(hbox, button2, ++tag);
    if (button3) insert(hbox, button3, ++tag);
    if (focus) [window setDefault:focus], [window setFocus:focus];
    if (title) [window setTitle:title];

    va_start(ap, button3);
    text = [[NSString alloc] initWithFormat:format arguments:ap];
    label = [GTKLabel labelWithStr:text];
    [text release];
    va_end(ap);

    [label setPadding:10 ypad:10];
    [label setJustify:GTK_JUSTIFY_LEFT];
    [vbox packStartDefaults:label];
    [window setTag:tag];

    gtk_signal_connect([window gtk], "delete_event", (GtkSignalFunc) delete, 0);
    result = [GTKApp runModalForWindow:window];
    [window destroy];
    return result;
}

static NSLock *gtkapp_lock;		// lock for GTKApp instance variables

@implementation GTKApplication

NSString *GTKApplicationDidBecomeActiveNotification =
	@"GTKApplicationDidBecomeActive";
NSString *GTKApplicationDidFinishLaunchingNotification =
	@"GTKApplicationDidFinishLaunching";
NSString *GTKApplicationDidResignActiveNotification =
	@"GTKApplicationDidResignActive";
NSString *GTKApplicationWillFinishLaunchingNotification =
	@"GTKApplicationWillFinishLaunching";
NSString *GTKApplicationWillTerminateNotification =
	@"GTKApplicationWillTerminate";

GTKApplication *GTKApp;		// the application object

/*
 * Return the shared application object (as stored in the variable GTKApp).
 * This method will generate an error message if the application object
 * has not yet been created.
 * @see -initWithArgc:argv:
 */
+ (GTKApplication *) sharedApplication
{
    if (GTKApp == nil)
	g_error("[GTKApplication sharedApplication]: no application object");
    return GTKApp;
}

 + (void) taskNowMultiThreaded:(NSNotification *) event;
{
#ifdef DEBUG
    fprintf(stderr, "[GTKApplication taskNowMultiThreaded]\n");
#endif
    if (gtkapp_lock == nil) gtkapp_lock = [NSRecursiveLock new];
    [GTK taskNowMultiThreaded:event];
}

 - init
{
    g_error("[GTKApplication init]: need argc and argv");
    return nil;
}

 - (void) windowDidBecomeMain:(NSNotification *) event
{
    GTKWindow *window = [event object];

#ifdef DEBUG
    fprintf(stderr, "[GTKApp windowDidBecomeMain] window = %p\n", window);
#endif
    if (window == nil) return;
    if (mainWindow == nil)
	[center postNotificationName:GTKApplicationDidBecomeActiveNotification
		object:self];
    [gtkapp_lock lock];
    mainWindow = window;
    [gtkapp_lock unlock];
}

 - (void) windowDidResignMain:(NSNotification *) event
{
    GTKWindow *window = [event object];

#ifdef DEBUG
    fprintf(stderr, "[GTKApp windowDidResignMain] window = %p\n", window);
#endif
    if (window == nil) return;
    if (mainWindow == window)
    {
	[center postNotificationName:GTKApplicationDidResignActiveNotification
		object:self];
	[gtkapp_lock lock];
	mainWindow = nil;
	[gtkapp_lock unlock];
    }
}

 - (void) windowWillClose:(NSNotification *) event
{
    GTKWindow *window = [event object];
    unsigned int count;

#ifdef DEBUG
    fprintf(stderr, "[GTKApp windowWillClose] window = %p\n", window);
#endif
    if (window == nil) return;
    if (window == mainWindow) [self windowDidResignMain:event];
    [gtkapp_lock lock];
    [windows removeObject:window];
    count = [windows count];
    [gtkapp_lock unlock];

    if (count == 0 && [delegate respondsToSelector:
	@selector(applicationShouldTerminateAfterLastWindowClosed:)] &&
	[delegate applicationShouldTerminateAfterLastWindowClosed:self])
	[self terminate:self];
}

 - (void) windowWillOpen:(NSNotification *) event
{
    GTKWindow *window = [event object];

#ifdef DEBUG
    fprintf(stderr, "[GTKApp windowWillOpen] window = %p\n", window);
#endif
    [gtkapp_lock lock];
    if (window) [windows addObject:window];
    [gtkapp_lock unlock];
}

/*
 * Initialize a new GTKApplication object and store it in the global
 * variable GTKApp. Note that you cannot create more than one application
 * object in your program. This method will modify the program's command
 * line arguments as given by /argc/ and /argv/ and remove any options
 * related to GTK (such as |--display| or |--class|) from the argument list.
 */
- initWithArgc:(int *) argc argv:(char ***) argv
{
#ifdef DEBUG
    fprintf(stderr, "[GTKApp initWithArgc:argv:]\n");
#endif
    if (GTKApp == nil)
    {
	GTKApp = [super init];
	center = [NSNotificationCenter defaultCenter];
	windows = [NSMutableArray new];

	gtk_set_locale();
	gtk_init(argc, argv);

	if ([NSThread isMultiThreaded])
	    [GTKApplication taskNowMultiThreaded:nil];
	else
	    [center addObserver:[GTKApplication class]
		    selector:@selector(taskNowMultiThreaded:)
		    name:NSWillBecomeMultiThreadedNotification object:nil];

	[center addObserver:self selector:@selector(windowDidBecomeMain:)
		name:GTKWindowDidBecomeMainNotification object:nil];
	[center addObserver:self selector:@selector(windowDidResignMain:)
		name:GTKWindowDidResignMainNotification object:nil];
	[center addObserver:self selector:@selector(windowWillClose:)
		name:GTKWindowWillCloseNotification object:nil];
	[center addObserver:self selector:@selector(windowWillOpen:)
		name:GTKWindowWillOpenNotification object:nil];
    }

    return GTKApp;
}

 - (void) dealloc
{
#ifdef DEBUG
    fprintf(stderr, "[GTKApp dealloc] (ignored)\n");
#endif
}

/*
 * This method is invoked automatically by the @-run method (see below)
 * just before the application's main event loop will be started. It
 * currently does nothing except for posting (in this order) the
 * GTKApplicationWillFinishLaunchingNotification and
 * GTKApplicationDidFinishLaunchingNotification to the default notification
 * center.
 */
- (void) finishLaunching
{
    [center postNotificationName:GTKApplicationWillFinishLaunchingNotification
	    object:self];

    // do something useful here...

    [center postNotificationName:GTKApplicationDidFinishLaunchingNotification
	    object:self];
}

/*
 * Set the application object's delegate or unset it (if /delegate/ is
 * |nil|). If the delegate implements some of the methods described in
 * the protocol @proto(GTKApplicationDelegate), it will be notified by
 * the application object on the corresponding events.
 */
- (void) setDelegate:_delegate
{
    [gtkapp_lock lock];
    if (delegate) [center removeObserver:delegate name:nil object:self];
    delegate = _delegate;

    if ([delegate respondsToSelector:@selector(applicationDidBecomeActive:)])
	[center addObserver:delegate
		selector:@selector(applicationDidBecomeActive:)
		name:GTKApplicationDidBecomeActiveNotification object:self];
    if ([delegate respondsToSelector:@selector(applicationDidFinishLaunching:)])
	[center addObserver:delegate
		selector:@selector(applicationDidFinishLaunching:)
		name:GTKApplicationDidFinishLaunchingNotification object:self];
    if ([delegate respondsToSelector:@selector(applicationDidResignActive:)])
	[center addObserver:delegate
		selector:@selector(applicationDidResignActive:)
		name:GTKApplicationDidResignActiveNotification object:self];
    if ([delegate respondsToSelector:@selector(applicationWillFinishLaunching:)])
	[center addObserver:delegate
		selector:@selector(applicationWillFinishLaunching:)
		name:GTKApplicationWillFinishLaunchingNotification object:self];
    if ([delegate respondsToSelector:@selector(applicationWillTerminate:)])
	[center addObserver:delegate
		selector:@selector(applicationWillTerminate:)
		name:GTKApplicationWillTerminateNotification object:self];
    [gtkapp_lock unlock];
}

/*
 * Return the application object's delegate.
 */
- delegate
{
    return delegate;
}

/*
 * Start the application's main event loop.
 */
- (void) run
{
    [self finishLaunching];
    [self main];
}

/*
 * Check whether the main event loop is running.
 */
- (BOOL) isRunning
{
    return [self mainLevel] > 0;
}

/*
 * Start a modal event loop for the given /window/, i.e. restrict all events
 * for this application to the specified window. This input mechanism should
 * only be used for panels or windows that require immediate user interaction.
 * Generally, it is best to avoid modal windows if possible.<p>
 * -runModalForWindow: does not return until the modal event loop is
 * terminated by a @-stopModalWithCode: message, and it returns
 * the status code passed as an argument to this method.
 * @see -stopModal, -stopModalWithCode:
 */
- (int) runModalForWindow:(GTKWindow *) window
{
#ifdef DEBUG
    fprintf(stderr, "[GTKApp runModalForWindow] window = %p\n", window);
#endif
    [window show];
    [self grabAdd:window];
    [self main];
    [self grabRemove:window];
#ifdef DEBUG
    fprintf(stderr, "[GTKApp runModalForWindow] (exit) code = %d\n", modalCode);
#endif
    return modalCode;
}

/*
 * Stop a running modal event loop with the code GTKRunStoppedResponse.
 */
- (void) stopModal
{
    [self stopModalWithCode:GTKRunStoppedResponse];
}

/*
 * Stop a running modal event loop with the given /code/.
 * @see -runModalForWindow:
 */
- (void) stopModalWithCode:(int) code
{
    [gtkapp_lock lock];
    modalCode = code;
    [gtkapp_lock unlock];
    [self stop:self];
}

/*
 * Stop the application's main event loop (if it is running). 
 * This will cause the @-run method to return (similar to @-terminate:),
 * but will not send any notifications. If a modal even loop is running,
 * it will be stopped instead of the main event loop.
 * The /sender/ parameter is ignored.
 */
- (void) stop:sender
{
    if ([self isRunning]) [self mainQuit];
}

/*
 * Tell the application's main event loop to finish. This will cause
 * the @-run method to return, which will normally end the program.
 * The /sender/ parameter is ignored.<p>
 * If the delegate implements the method:
 * <pre>
 * - (BOOL) applicationShouldTerminate:(GTKApplication *) sender</pre>
 *
 * this message is sent to the delegate to determine whether the application
 * should actually terminate. If it returns |YES|, this method will post
 * the GTKApplicationWillTerminateNotification to the default notification
 * center and terminate the event loop.
 */
- (void) terminate:sender
{
#ifdef DEBUG
    fprintf(stderr, "[GTKApp terminate] sender = %p\n", sender);
#endif
    if ([delegate respondsToSelector:@selector(applicationShouldTerminate:)] &&
	[delegate applicationShouldTerminate:self] == NO) return;

    [center postNotificationName:GTKApplicationWillTerminateNotification
	    object:self];

    if ([self mainLevel] == 1) [self mainQuit];
    else [self exit:0];
}

/*
 * Report an exception to |stderr|. This method simply calls NSLog()
 * to print the /exception/'s name and reason.
 */
- (void) reportException:(NSException *) exception
{
    NSString *reason = [exception reason];

    if (reason) NSLog(@"%@: %@", [exception name], reason);
    else NSLog([exception name]);
}

/*
 * Check whether the application is active.
 */
- (BOOL) isActive
{
    return mainWindow != nil;
}

/*
 * Return the application's current main window (the /active/ window)
 * or |nil| if the application is not active (or has no windows).
 * @see -isActive
 */
- (GTKWindow *) mainWindow
{
    return mainWindow;
}

/*
 * Return the application's window list.
 */
- (NSArray *) windows
{
    NSArray *result;

    [gtkapp_lock lock];
    result = [NSArray arrayWithArray:windows];
    [gtkapp_lock unlock];
    return result;
}
@end
