
/*
    MiscNibManager.m
*/

// RCS identification information
static char *rcsID = "$Id:$";
static void __AvoidCompilerWarning(void) {if(!rcsID)__AvoidCompilerWarning();}

// NeXT Headers
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>

// Other Headers
#import "MiscWindowManager.h"

// Class Headers
#import <MiscControllerKit/MiscNibManager.h>

// Global Constants
NSString* MiscNibManagerWillReleaseNotification = @"MiscNibManagerWillReleaseNotification";


@implementation MiscNibManager

/*"
   MiscNibManager is a lightweight class that manages the resources
   in a single nib. It is equipped to handle and give access to the window
   managers in the nib. Subclasses, of course, can always add outlets
   to manage other objects inside the nib.
   
   The nib filename should be within the bundle that the MiscNibManager 
   exists in otherwise you should use the #initWithBundle: and pass in the
   bundle that contains the nib. You can also manually get the
   pathname to the nib yourself and use our #initWithNibFilename: method.

   If the nib you are loading just has MiscWindowManager subclasses and
   other objects that are taken care of by a window manager or one of
   it's subclasses then you probably don't even need to create a subclass
   of MiscNibManager. You can load the nib with #initWithNibFilename: and
   get the window managers via #windowManagersOfClass or
   #windowManagersWithName:.

   Recently I have become enlightened that loading a nib is just an
   implementation detail. Really you are usually just interested in
   the window and controllers inside the nib. So why expose nib
   managers all over your code? When you ask the window manager class
   for a new instance it can make use of the nib manager to load
   itself from the nib. For example of this see MiscInfoWindowManager
   or MiscInspectorManager. You'll notice that no other object knows
   about MiscInfoNibManager except for the MiscInfoWindowManager.
   That's probably the way it should be. I'll document this a whole
   lot better for the next release.
"*/


//-------------------------------------------------------------------
// 	Initialization / deallocation
//-------------------------------------------------------------------

- (id) init
/*"
    Invokes #initWithNibFilename with [self nibFilename] as the argument.
    Returns nil upon failure.
"*/
{
    return [self initWithNibFilename:[self nibFilename]];
}


- (id) initWithNibFilename:(NSString*)nibFilename
/*"
    Our designated initializer. If nibFilename is nil or
    loading the nib fails, then we autorelease ourselves and
    return nil since we have nothing to manage.
"*/
{
    BOOL error = ([super init] == nil);
	
	// Check arguments.
	if (!error) {
		error = (nibFilename == nil || [nibFilename isEqualToString:@""]);
	}
	
    if (!error) {
        // This has to be done before the nib is loaded because the array
        // will keep track of the window managers that are found in the nib.
        _windowManagers = [[NSMutableArray alloc] init];

        [self setReleasedWhenAllWindowManagersReleased:YES];
    }

    if (!error) {
        // If nibFilename is an absolute path then loadNibNamed: doesn't seem
        // to work so we have to use it's alternative. On the other hand loadNibFile:
        // doesn't seem to work as well as loadNibNamed: when searching for nibs
        // with relative pathnames (since it searches the owner's bundle and the
        // main bundle) so we use a combination.
        if ([nibFilename isAbsolutePath]) {
            NSDictionary* externalNameTable;

            externalNameTable = [NSDictionary dictionaryWithObjects:
                [NSArray arrayWithObjects:self, nil]
                forKeys:[NSArray arrayWithObjects:@"NSOwner", nil]];

            error = ![NSBundle loadNibFile:nibFilename externalNameTable:externalNameTable
                                  withZone:[self zone]];
        }

        // This will find the nib if it is in the owner's bundle too.		
        else {
            error = ![NSBundle loadNibNamed:nibFilename owner:self];
        }

        if (error) {
            NSLog(@"MiscNibManager initWithNibFilename: could not load "
                  @"nib named %@.", nibFilename);

            // It is our responsiblity to free ourselves if there is a problem.
            [self autorelease];
        }
    }

    return error ? nil :self;
}

- (id) initWithBundle:(NSBundle*)aBundle
/*"
   The initializer to use if the nib filename (returned by
   [self nibFilename]) is in a different bundle than the main one.
   For example, ...
   We may also need to create a initWithNibFilename: inBundle: method
   to be complete.
"*/
{
    NSString* alternateNibFilename;
    
    // Try to find [self nibFilename] in the given bundle.
    alternateNibFilename = [aBundle pathForResource:[self nibFilename]
                                             ofType:nil];

    return [self initWithNibFilename:alternateNibFilename];
}


- (void) dealloc
/*"
    Standard deallocator. We release our window managers.
"*/
{
    NSLog(@"Instance of MiscNibManager is deallocing.");

	// Let anyone who cares know we are going away.
	[[NSNotificationCenter defaultCenter] 
		postNotificationName:MiscNibManagerWillReleaseNotification
		object:self];
		
    [_windowManagers release];
    [super dealloc];
}


- (NSString*) nibFilename
/*"
   Returns an empty string. Subclasses should override this method to
   return a nibFilename. It can be an absolute path though for
   portability reasons it shouldn't be. The main app wrapper
   and the nib owner's bundle will be searched when looking for
   the nib. Alternatively you can use #initWithNibFilename: and
   not override this method at all. This method is preferrable
   though if you want to hide the actual nib filename from clients.
"*/
{
    return @"";
}


//-------------------------------------------------------------------
// 	Window manager manipulation
//-------------------------------------------------------------------
 
- (void) addWindowManager:(MiscWindowManager*)newManager
/*"
   Adds newManager to our list of window managers as long as it is
   really an instance or subclass of MiscWindowManager. In
   Interface Builder if you connect an instance of MiscWindowManager
   to a nib manager then this method is called when the
   connection is made (via MiscWindowManager's #setNibManager:).
"*/
{
    BOOL error = NO;

    // Check our arguments.
    if (newManager == nil) {
        error = YES;
        NSLog (@"MiscNibManager: addWindowManager: argument given was nil.");
    }

    if (!error) {
        if ([newManager isKindOfClass:[MiscWindowManager class]]) {
            [[self mutableWindowManagers] addObject:newManager];
        }
        else {
            error = YES;
            NSLog (@"MiscNibManager: addWindowManager: argument was not an "
                   @"instance of MiscNibManager or one of it's subclasses.");
        }
    }
    
}


- (void) removeWindowManager:(MiscWindowManager*)oldManager
/*"
    Removes oldManager from our list of window managers.
"*/
{
    BOOL error = NO;

    // Check our arguments.
    if (oldManager == nil) {
        error = YES;
        NSLog (@"MiscNibManager: removeWindowManager: argument given was "
               @"nil.");
    }

    if (!error) {
        NSMutableArray*	windowManagers = [self mutableWindowManagers];

        // We only bother to check if oldManager is one of ours
        // so we can print an informative error message.
        if ([windowManagers containsObject:oldManager]) {
            [windowManagers removeObject:oldManager];
        }
        else {
            NSLog (@"MiscNibManager removeWindowManager: argument given "
                   @"was not one of our window managers.");
        }
    }
}


- (NSArray*) windowManagers
/*"
   Returns an immutable array containing our window managers. Subclasses
   should check out our protected #mutableWindowManagers.
"*/
{
	return [[[NSArray alloc] initWithArray:[self mutableWindowManagers]]
				autorelease];
}


- (NSEnumerator*) windowManagerEnumerator
/*"
    Returns an enumerator that can be used to access our window managers.
"*/
{
    // Getting the mutable window manager will save creating an
    // array just to create an enumerator for it.
    return [[self mutableWindowManagers] objectEnumerator];
}


//-------------------------------------------------------------------
// Access to our window manager(s).
//-------------------------------------------------------------------

- (NSArray*) windowManagersWithName:(NSString*)name
/*"
   Returns an array of our window managers with the given name. If
   none exist nil is returned. If you know there'll only be a single
   instance of a given window manager it's probably more
   convenient to use #windowManagerWithName:.
"*/
{
    NSMutableArray* windowManagersWithName = nil;
    NSEnumerator* windowManagerEnumerator = [self windowManagerEnumerator];
    MiscWindowManager* ourWindowManager;

    while ((ourWindowManager = [windowManagerEnumerator nextObject]) != nil) {
        if ([[ourWindowManager name] isEqualToString:name]) {
            if (windowManagersWithName == nil) {
                windowManagersWithName = [NSMutableArray arrayWithCapacity:1];
            }
            [windowManagersWithName addObject:ourWindowManager];
        }
    }

    // This should make an immutable copy before returning but
    // since MiscWindowManager does not respond to copyWithZone:
    // (yet) we can't. I'll fix it in a later release.
    return windowManagersWithName;
}


- (MiscWindowManager*) windowManagerWithName:(NSString*)name
/*"
   Returns the first of our window managers that have the given name.
   If none exist then nil is returned.
"*/
{
    NSArray* windowManagersWithName;
    MiscWindowManager* firstWindowManagerWithName = nil;

    windowManagersWithName = [self windowManagersWithName:name];
    if ([windowManagersWithName count] > 0) {
        firstWindowManagerWithName = [windowManagersWithName objectAtIndex:0];
    }

    return firstWindowManagerWithName;
}


- (NSArray*) windowManagersOfClass:(Class)aClass
/*"
   Returns an array of our window managers of the given class
   (or subclass). If none exist nil is returned. If you know
   there'll only be a single instance of a given window manager
   it's probably more convenient to use #windowManagerOfClass:.
"*/
{
    NSMutableArray* windowManagersOfClass = nil;
    NSEnumerator* windowManagerEnumerator = [self windowManagerEnumerator];
    MiscWindowManager* ourWindowManager;

    while ((ourWindowManager = [windowManagerEnumerator nextObject]) != nil) {
        if ([ourWindowManager isKindOfClass:aClass]) {
            if (windowManagersOfClass == nil) {
                windowManagersOfClass = [NSMutableArray arrayWithCapacity:1];
            }
            [windowManagersOfClass addObject:ourWindowManager];
        }
    }

    // This should make an immutable copy before returning but
    // since MiscWindowManager does not respond to copyWithZone:
    // (yet) we can't. I'll fix it in a later release.
    return windowManagersOfClass;
}

- (MiscWindowManager*) windowManagerOfClass:(Class)aClass
/*"
   Returns the first of our window managers of the given
   aClass (or subclass). If none exist then nil is returned.
"*/

{
    NSArray* windowManagersOfClass;
    MiscWindowManager* firstWindowManagerOfClass = nil;

    windowManagersOfClass = [self windowManagersOfClass:aClass];
    if ([windowManagersOfClass count] > 0) {
        firstWindowManagerOfClass = [windowManagersOfClass objectAtIndex:0];
    }

    return firstWindowManagerOfClass;
}

//-------------------------------------------------------------------
//	Accessor methods
//-------------------------------------------------------------------

- (BOOL) isReleasedWhenAllWindowManagersReleased
/*"
   Returns YES if, when the we have no more window managers (they have
   all been released) we release ourselves. The default is YES.
   Note, that if we start out with no window managers then we will
   not release ourselves automatically. 
"*/
{
    return _releasedWhenAllWindowManagersReleased;
}

- (void) setReleasedWhenAllWindowManagersReleased:(BOOL)freeUs
/*" 
    Sets whether, when we have no more window managers left (they have
    all been released either explicitly or when the user has closed the window)
    we also release ourselves. The default is YES. Note, that if we
   start out with no window managers then we will not release
   ourselves automatically.
"*/
{
    _releasedWhenAllWindowManagersReleased = freeUs;
}

@end


@implementation MiscNibManager (MiscWindowManagerNotifications)

- (void) windowManagerWillClose:(MiscWindowManager*)closingWindowManager
/*"
   This method is called from MiscWindowManager when the window manager
   is planning on releasing itself. You should not call this method
   directly. If closingWindowManager is one of our window managers it
   is removed from our list of window managers. If closingWindowManager
   was our last window manager and we should release ourselves when
   we have no more window managers then we also release ourselves.
"*/
{
    if ([[self windowManagers] containsObject:closingWindowManager]) {
        [self removeWindowManager:closingWindowManager];

        // Release ourselves when we have no more window
        // managers if we should.
        if ([[self windowManagers] count] == 0 &&
            [self isReleasedWhenAllWindowManagersReleased]) {
            [self autorelease];
        }
            
    }
    else {
        NSLog (@"windowManagerWillClose: I was given notification of "
               @"a window manager closing that I don't own.");
    }
}

@end

