
/*
    MiscControllerManager.m
*/

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


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

// Other Headers
#import "MiscController.h"

// Superclass/Class Headers
#import "MiscControllerManager.h"


@implementation MiscControllerManager 

/*"
	The MiscControllerManager class is an extension of MiscWindowManager. 
	In addition to managing our window, we now also manage zero or more 	
	MiscControllers that'll display information in our window. Our job 
	is to reduce the number of outlets from controller to controller 
	within the same window. Since each controller should have the same 
	controller manager, they can just ask their controller manager for any 
	other controller (by name or class) it knows about. We also keep track of 
	each controller's edited state so we know when the window should be marked 
	as "dirty".
	
	Additionally, when the window is closed and releasedWhenWindowClosed
	(method from our superclass) is YES, we will free our controllers
	when we release ourselves.
"*/


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

- (id) init
/*"
    Our designated initializer. Right now we only create an array to keep
	track of our controllers. 
"*/
{
    BOOL error = ([super init] == nil) ? YES : NO;

    if (!error) {
        _controllers = [[NSMutableArray alloc] init];

		// If something goes wrong it is our responsibility to free ourselves
		// and return nil.
        if (error) {
            [self autorelease];
        }
    }

    return error ? nil : self;
}


- (void) dealloc
/*"
    Releases our array of controllers.
"*/
{
    [_controllers release];
    [super dealloc];
}


//-------------------------------------------------------------------
// 	Accessing the managed controllers
//-------------------------------------------------------------------

- (void) addController:(MiscController*)newController
/*"
   Adds the given controller to our list of controllers if it is a
   subclass of MiscController. This message is usually sent from
   our new controller's #setControllerManager: method (if the
   connection from a controller to it's controller manager
   is made in IB).
"*/
{
    if ([newController isKindOfClass:[MiscController class]]) {
        [[self mutableControllers] addObject:newController];
    }
    else {
        NSLog (@"MiscControllerManager: You attempted to add a controller "
               @"that was not an instance of MiscController.");
    }
}

- (void) removeController:(MiscController*)oldController
/*"
    Though this probably shouldn't be needed too often it is provided
    for those rare cases.
"*/
{
    if ([oldController isKindOfClass:[MiscController class]]) {
        [[self mutableControllers] removeObject:oldController];
    }
    else {
        NSLog (@"MiscControllerManager: You attempted to remove a controller "
               @"that was not an instance of MiscController.");
    }		
}

- (NSArray*) controllers
/*"
    Returns our controllers. If you are calling this method in a 
	subclass you can also make use of our protected #mutableControllers
	method if you plan on altering the array in any way.
"*/
{
	// Shouldn't return our mutable array I suppose.
	return [[[NSArray alloc] initWithArray:[self mutableControllers]]
		autorelease];
}


- (NSEnumerator*) controllerEnumerator
/*"
	This method will probably be going away since it's easy enough to 
	ask for our controllers and then create an enumerator from that.
"*/
{
	// Might as well use our mutable array of controllers
	// to save creating an array just to create it's enumerator
	// (that's what would happen if we used [self controllers]).
	return [[self mutableControllers] objectEnumerator];
}

//-------------------------------------------------------------------
// 	Controllers by class
//-------------------------------------------------------------------

- (NSArray*) controllersOfClass:(Class)aClass
/*"
    Returns all our controllers that belong to (or are subclasses of) aClass.
    If you know for sure that this method will return a single controller
    then you can probably use #controllerOfClass:. If there are no
    matches then nil is returned.
"*/
{
    NSEnumerator*	controllerEnumerator;
    MiscController*	controller;
    NSMutableArray*	controllersToReturn = nil;

    controllerEnumerator = [self controllerEnumerator];
    while ((controller = [controllerEnumerator nextObject]) != nil) {
        if ([controller isKindOfClass:aClass]) {
            if (controllersToReturn == nil) {
                controllersToReturn = [NSMutableArray arrayWithCapacity:1];
            }
            [controllersToReturn addObject:controller];
        }
    }

    return [[[NSArray alloc] initWithArray:controllersToReturn] autorelease];
}
	

- (MiscController*) controllerOfClass:(Class)aClass
/*"
    Returns the first controller in our list of controllers that belongs
    to (or is a subclass of) aClass. If there is no such controller, then nil
    is returned.
"*/
{
    NSArray*		controllers;
    MiscController*	controllerToReturn = nil;

    controllers = [self controllersOfClass:aClass];
    if ([controllers count] > 0) {
        controllerToReturn = [controllers objectAtIndex:0];
    }
    return controllerToReturn;
}


//-------------------------------------------------------------------
// 	Controllers by name
//-------------------------------------------------------------------

- (NSArray*) controllersWithName:(NSString*)aName
/*"
    Returns all our controllers that have the name aName (see 
	MiscController's #{setName:}/#{name} methods).
    If you know for sure that this method will return a single controller
    then you can probably use #controllerOfClass:. If there are no
    matches then nil is returned.
"*/
{
    NSEnumerator*	controllerEnumerator;
    MiscController*	controller;
    NSMutableArray*	controllersToReturn = nil;

    controllerEnumerator = [self controllerEnumerator];
    while ((controller = [controllerEnumerator nextObject]) != nil) {
        if ([[controller name] isEqualToString:aName]) {
            if (controllersToReturn == nil) {
                controllersToReturn = [NSMutableArray arrayWithCapacity:1];
            }
            [controllersToReturn addObject:controller];
        }
    }

    return [[[NSArray alloc] initWithArray:controllersToReturn] autorelease];
}


- (MiscController*) controllerWithName:(NSString*)aName
/*"
    Returns the first controller in our list of controlllers that has
    the name aName (see MiscController's #{setName:}/#{name} methods).
	If there is no such controller, then nil is returned.
"*/
{
    NSArray*		controllers;
    MiscController*	controllerToReturn = nil;

    controllers = [self controllersWithName:aName];
    if ([controllers count] > 0) {
        controllerToReturn = [controllers objectAtIndex:0];
    }
    return controllerToReturn;
}


//-------------------------------------------------------------------
// 	Updating the document status
//-------------------------------------------------------------------

- (void) updateDocumentEdited
/*"
    Scans the receiver's controllers and sets the documed edited flag if 
    any of them are dirty, or clears it if none of them are dirty. 
"*/
{
    NSEnumerator*  	controllerEnumerator;
    MiscController*	controller;
    BOOL			documentEdited = NO;

    controllerEnumerator = [self controllerEnumerator];
    while ((controller = [controllerEnumerator nextObject]) != nil) {
        
		// Stop on the first controller we find dirty.
        if ([controller isDirty]) {
            documentEdited = YES;
            break;
        }
    }

    [self setDocumentEdited:documentEdited];
}


//-------------------------------------------------------------------
// 	Save/Revert
//-------------------------------------------------------------------

- (BOOL) save
/*"
   Sends save to our controllers. If they all successfully save then
   we tell our window that our document isn't edited. If any of the
   controllers return NO when asked to save we also return NO. It is up to
   the controller that couldn't save to let the user know what went wrong.
   If you want this code to be called from a menu cell, just create a #save:
   method in your subclass that calls this method. The reason it's not done
   for you is that some controller managers will not want to save, and with
   the new menu cell validation the menu item will be enabled. Also take 
   note that MiscController does not implement the #save method by default,
   so if you use this method all your subclasses of MiscController will have 
   to respond to save.
"*/
{
    NSEnumerator*  	controllerEnumerator;
    MiscController*	controller;
    BOOL			saveSuccessful = YES;

    controllerEnumerator = [self controllerEnumerator];
    while ((controller = [controllerEnumerator nextObject]) != nil) {
        
		// Stop on the first controller that cannot save.
        if (![controller save]) {
            saveSuccessful = NO;
            break;
        }
    }

	// We probably could have just called setDocumentEdited:NO if the save was
	// successful or not, but this way the programmer can catch any bugs that
	// a controller might have when it successfully saves but doesn't say it's
	// not dirty anymore. 
	[self updateDocumentEdited];
	
	return saveSuccessful;
}


- (BOOL) revert
/*"
	Sends revert to our controllers. If they all successfully revert then
	we tell our window that our document isn't edited. If any of the 
	controllers return NO when asked to revert we also return NO. See the
	#save method for other helpful tips when using these methods.
"*/
{
    NSEnumerator*  	controllerEnumerator;
    MiscController*	controller;
    BOOL			revertSuccessful = YES;

    controllerEnumerator = [self controllerEnumerator];
    while ((controller = [controllerEnumerator nextObject]) != nil) {
        
		// Stop on the first controller that cannot revert.
        if (![controller revert]) {
            revertSuccessful = NO;
            break;
        }
    }

	// We probably could have just called setDocumentEdited: is the save was
	// successful or not, but this way the programmer can catch any bugs that
	// a controller might have when it successfully saves but doesn't say it's
	// not dirty anymore. 
	[self updateDocumentEdited];
	
	return revertSuccessful;

}

	
//-------------------------------------------------------------------
// 	Controlling a manager's editability
//-------------------------------------------------------------------

- (BOOL) isEditable
/*"
    Returns YES if any of our controllers are editable. If they are all 
    uneditable then we return NO. If we happen to have no controllers then
    the answer will always be NO.
"*/
{
    NSEnumerator*	controllerEnumerator;
    MiscController*	controller;
    BOOL 			editable = NO;
    
    // Go through our controllers until one says he is editable.
    controllerEnumerator = [self controllerEnumerator];
    while ((controller = [controllerEnumerator nextObject]) != nil) {
        
	// We check for this because not all MiscController
	// subclasses respond to isEditable.
        if ([controller respondsToSelector:@selector(isEditable)]) {
            if ([controller isEditable]) {
                editable = YES;
                break;
            }
        }
    }
    return editable;
}

- (void) setEditable:(BOOL)nowEditable
/*"
    Sets all of our controllers to be editable. Feel free to override this
    method if that's not the behavior you'd like (ie: some controllers 
    should become editable and some should not).
"*/
{
    NSEnumerator*	controllerEnumerator;
    MiscController*	controller;

    // Go through our controllers and set them all editable if they
    // respond to setEditable:
    controllerEnumerator = [self controllerEnumerator];
    while ((controller = [controllerEnumerator nextObject]) != nil) {

        if ([controller respondsToSelector:@selector(setEditable:)]) {
            [controller setEditable:nowEditable];
        }
    }
}

@end


