
/*
    MiscWindowManager.m
*/

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

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

// Other headers
#import <MiscControllerKit/MiscNibManager.h>

// Class headers
#import "MiscWindowManager.h"

// Private macros
#define MISC_CASCADE_XOFFSET 25.0
#define MISC_CASCADE_YOFFSET 25.0

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

// Private Variables
// When the OpenStep MiscKit is released I will use MiscClassVariable
// but for now...
static Class nibManagerClass = nil;


@implementation MiscWindowManager

/*"
    MiscWindowManager is an abstract class that manages the resources 
    of a single window. To work correctly it needs to be the window's
    delegate. 
	
	This class will probably change the most in the coming releases.
	Some of it's methods aren't very useful anymore (they were more
	so in 3.x development). I also have some other functionality
	that I'll be adding.
"*/


//-------------------------------------------------------------------
//	Class initialization
//-------------------------------------------------------------------

+ (void) initialize
/*"
	Does nothing right now.
"*/
{
}
		

//-------------------------------------------------------------------
//	Nib manager class that'll load us
//-------------------------------------------------------------------

+ (Class) nibManagerClass
/*"
	Returns the nib manager class used to load us. By default it is
	nil since the abstract MiscNibManager isn't capable to loading a nib.
	The class returned by this method can be used by subclasses to create
	new window managers via the class returned by this method.
"*/
{
	return nibManagerClass;
}


+ (void) setNibManagerClass:(Class)newNibManagerClass
/*"
	Sets our nib manager class used to load us. By default it is
	nil.
"*/
{
	nibManagerClass = newNibManagerClass;
}


//-------------------------------------------------------------------
//	All our instances
//-------------------------------------------------------------------

+ (NSArray*) allWindowManagers
/*"
    Returns an array of all known instances of MiscWindowManager and
    it's subclases. This method assumes that all the instances are
    are their window's delegate, since these instances are retrieved 
    via the global Window list. Returns nil if no instances exist.
"*/
{
    return [self windowManagersOfClass:[MiscWindowManager class]];
}


//-------------------------------------------------------------------
// 	Manager instances by name
//-------------------------------------------------------------------

+ (NSArray*) windowManagersOfClass:(Class)managerClass
/*"
    Returns all instances of class managerClass (or it's subclasses), or 
    nil if there aren't any.
"*/
{
    NSMutableArray*	managers = nil;
    NSArray*		windowList = [NSApp windows];	
    int			winCount;
    int 		i;
    id			winDelegate;

    winCount = [windowList count];
    for (i = 0; i < winCount; i++) {
        winDelegate = [[windowList objectAtIndex:i] delegate];

        if ([winDelegate isKindOfClass:managerClass]) {
            if (managers == nil) {
                managers = [NSMutableArray arrayWithCapacity:1];
            }
            [managers addObject:winDelegate];
        }
    }

    // Make managers immutable.
    return [[[NSArray alloc] initWithArray:managers] autorelease];
}	

+ (MiscWindowManager*) windowManagerOfClass:(Class)managerClass
/*"
    Returns the first manager instance that's a kind of managerClass, or 
    nil if one doesn't exist. If you know that you might have more than 
    one instance that has the name "aName" then you should probably use 
    #windowManagersOfClass:. 
"*/
{
    NSArray*		windowManagersOfClass;
    MiscWindowManager*	firstWindowManagerOfClass = nil;

    windowManagersOfClass = [self windowManagersOfClass:managerClass];

    // If we have at least one manager with that name return the
    // first one.
    if ([windowManagersOfClass count] > 0) {
        firstWindowManagerOfClass = [windowManagersOfClass objectAtIndex:0];
    }

    return firstWindowManagerOfClass;
}	


//-------------------------------------------------------------------
// 	Manager instances by name
//-------------------------------------------------------------------

+ (NSArray*) windowManagersWithName:(NSString*)aName
/*"
    Returns an array of managers that have the name "aName", or nil if
    none exist. If you know that you'll only have a single instance for
    aName then you could use #windowManagerWithName:.
"*/
{
    NSEnumerator*	allWindowManagersEnumerator;
    NSMutableArray*	windowManagersWithName = nil;
    MiscWindowManager*	windowManager;

    // Go through each window manager looking for matching names.
    allWindowManagersEnumerator = [[self allWindowManagers] objectEnumerator];
    while ((windowManager = [allWindowManagersEnumerator nextObject]) != nil) {
        if ([[windowManager name] isEqualToString:aName]) {

            if (windowManagersWithName == nil) {
                windowManagersWithName = [NSMutableArray arrayWithCapacity:1];
            }

            [windowManagersWithName addObject:windowManager];
        }
    }

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

+ (id) windowManagerWithName:(NSString*)aName
/*"
    Returns the first manager that has the name "aName", or nil if one doesn't
    exist. If you know that you might have more than one instance that
    has the name "aName" then you should probably use 
    #windowManagersWithName:. 
"*/
{
    NSArray*		windowManagersWithName;
    MiscWindowManager*	firstWindowManagerWithName = nil;

    windowManagersWithName = [self windowManagersWithName:aName];

    // If we have at least one manager with that name return the
    // first one.
    if ([windowManagersWithName count] > 0) {
        firstWindowManagerWithName = [windowManagersWithName objectAtIndex:0];
    }

    return firstWindowManagerWithName;
}	


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

- init
/*"
    Our designated initializer. If we or our superclass fails initialization
	we return nil. If we fail we autorelease ourselves. By default we
	autocascade our windows and don't release ourselves when our window closes.
"*/
{
    BOOL	error = ([super init] == nil);

    if (!error) {
		// Set our default cascade offset and window cascading to YES.
		[self setCascadeOffset:MISC_CASCADE_XOFFSET
								:MISC_CASCADE_YOFFSET];
		[self setAutoCascadesWindow:YES];

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

    return error ? nil :self;
}

- (void) dealloc
/*"
    Standard deallocator.
"*/
{
	// Let anyone that cares know we are going away.
	[[NSNotificationCenter defaultCenter] 
		postNotificationName:MiscWindowManagerWillReleaseNotification
		object:self];

    // Stops problems if the window hangs around in NXApp's window list.
    [_window setDelegate:nil];
    // Take it out off screen if it isn't already.
    [_window close];
    [_window autorelease];
    [_windowFrameName release];
    [_name release];
    [super dealloc];
}


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

- (NSString *) name
/*"
    Returns the name of our window manager. This is most useful if, instead
    of having outlets from your nib manager to window managers or from other
    window managers, you just ask for it via #windowManager(s)WithName:.
"*/
{
    return _name;
}

- (void) setName:(NSString*)newName
{
    [_name autorelease];
    _name = [newName copy];
}

- (MiscNibManager*) nibManager
/*"
    Returns our nib manager of nil if we don't have one. For instance if
    we were a window manager in the main nib we probably wouldn't have
    a nib manager.
"*/
{
    return _nibManager;
}

- (void) setNibManager:(MiscNibManager*)newNibManager
/*"
    Sets our nib manager to aManager. We also add ourself to 
    our new manager's window manager list via #addWindowManager:. 
    We don't retain our nib manager, since that would introduce
    a cyclic reference. Raises an NSInvalidArgumentException if 
    the argument isn't a MiscNibManager or a subclass.
"*/
{
    BOOL error = NO;

    // Check arguments.
    if (!error && newNibManager == nil) {
        error = YES;
    }

    // Raise an exception if the argument is not nil but is also not a
    // MiscNibManager (or subclass).
    if (!error && ![newNibManager isKindOfClass:[MiscNibManager class]]) {
        [NSException raise:NSInvalidArgumentException
                    format:@"The nib manager of a MiscWindowManager "
            @"subclass must be an instance of a MiscNibManager subclass. "
            @"The instance presented was of class %@",
            [[newNibManager class] description]];
        error = YES;
    }

    if (!error) {
        // If we have an old manager, let it know we have moved
        // on.
        [[self nibManager] removeWindowManager:self];

        // Set our own ivar, but don't retain it since that would create
        // a cyclic reference.
        _nibManager = newNibManager;

        // Let our new controller know about us.
        [[self nibManager] addWindowManager:self];
    }
}


//-------------------------------------------------------------------
// 	Accessing the main window
//-------------------------------------------------------------------

- (NSWindow*) window 
/*"
    Returns the main window.
"*/
{
    return _window; 
}

- (void) setWindow:(NSWindow*)newWindow
/*"
   This is usually just called when the nib is being unarchived if you connected the outlets
   in IB. We retain our window.
"*/
{
    [_window autorelease];
    _window = [newWindow retain];
}

- (NSString*) windowTitle
/*"
    Returns the main Window's title. 
"*/
{
    return [[self window] title];
}

- (void) setWindowTitle:(NSString*)title
/*"
    Sets the main Window's title to the argument. 
"*/
{
    [[self window] setTitle:title];
}



//-------------------------------------------------------------------
//	Window cascading 
//-------------------------------------------------------------------

- (BOOL) autoCascadesWindow
/*"
    Returns YES if we try to cascade our window before it is brought 
    on the screen. The default is YES.
"*/
{
    return _autoCascadesWindow;
}

- (void) setAutoCascadesWindow:(BOOL)cascadeIt
/*"
    Sets whether we try to cascade our window before it's brought to the
    screen. The default is YES.
"*/
{
    _autoCascadesWindow = cascadeIt;
}

- (void) setCascadeOffset:(float)x :(float)y
/*"
    Sets the amount we should cascade our window. The default for both
    the x and y offset is 25.0.
"*/
{
    _xOffset = x;
    _yOffset = y;
}


//-------------------------------------------------------------------
// 	Checking the document status
//-------------------------------------------------------------------

- (BOOL) isDocumentEdited
/*"
    Returns YES if the document has been edited, NO otherwise.
"*/
{
    return [[self window] isDocumentEdited];
}

- (void) setDocumentEdited:(BOOL)docNowEdited
/*"
    Sets the document edited flag.  This method is invoked automatically 
    if anything in the interface is edited by the user.  Subclass should 
    invoke this method if they modify any of their controlled objects 
    outside the normal framwork. 
"*/
{
    if ([self isDocumentEdited] != docNowEdited) {
        NSWindow*	myWindow = [self window];

        [myWindow setDocumentEdited:docNowEdited];
        // Not sure if this is needed...
        [myWindow displayIfNeeded];
    }
}


//-------------------------------------------------------------------
// 	Display management
//-------------------------------------------------------------------

- (void) makeKeyConditionallyAndOrderFront:sender
/*"
    Like NSWindow's #makeKeyAndOrderFront: except the window isn't made key 
    unless the application is active to prevent the app from continually 
    asserting itself. This should be used instead of sending our window
    a #makeKeyAndOrderFront: message. 
"*/
{
    NSWindow*	window = [self window];

	// Autocascade the window if we haven't already, we should,
	// and it isn't visible yet.
	if ([self autoCascadesWindow]) {
		[self _tryToCascadeWindow];
	}
	
    // Make my main Window key and/or just order it to the front.
    if ([NSApp isActive]) {
        [window makeKeyAndOrderFront:sender];
    } else {
        [window orderFront:sender];
    }
}

- (BOOL) isVisible 
/*"
    Returns YES if the main Window is visible to the user, NO otherwise. 
"*/
{
    return [[self window] isVisible];
}

- (void) performClose:(id)sender
/*"
    This message can be sent when we want the manager to close. It just 
    sends a #performClose: message to our main Window which initiates the 
    closing and releasing of everything else. You could extend this method 
    to stop the #performClose: message from being sent under certain 
    circumstances.  Currently, the #performClose: message is sent only 
    if our main Window is visible. 
"*/
{
    if ([self isVisible]) {
        [[self window] performClose:self];
    }
}

- (void) close
/*"
    Sends our main window a close message. See the Window documentation
    for the difference between sending #performClose: and a #close: message
    to a window. The message is only sent if the window is visible.
"*/
{
    if ([self isVisible]) {
        [[self window] close];
    }
}

//-------------------------------------------------------------------
// 	Window frame management
//-------------------------------------------------------------------

- (BOOL) autosavesWindowFrame
{
    return _autosavesWindowFrame;
}
        
- (void) setAutosavesWindowFrame:(BOOL)saveFrame
{
    _autosavesWindowFrame = saveFrame;
}

- (NSString*) windowFrameName
{
    return _windowFrameName;
}

- (void) setWindowFrameName:(NSString*)frameName
{
    [_windowFrameName autorelease];
    _windowFrameName = [frameName copy];
}

- (void) setWindowFrameFromDefaults
/*"
    Sets our main window's window frame stored in our defaults. This 
    method only works if #windowFrameName returns something other than
    nil and the name returned is actually in the defaults database.
"*/
{
    NSString*	frameName = [self windowFrameName];

    if (frameName != nil) {
        [[self window] setFrameUsingName:[self windowFrameName]];
    }
}

- (void) saveWindowFrameToDefaults
/*"
    Saves our current window frame in the defaults database so it can later
    be retrieved with #setWindowFrameFromDefaults. This method only works
    if #windowFramName returns something other than nil.
"*/
{
    NSString*	frameName = [self windowFrameName];

    if (frameName != nil) {
        [[self window] saveFrameUsingName:[self windowFrameName]];
    }
}

- (BOOL) isReleasedWhenWindowClosed
/*"
    Returns YES if, when our window is closed we release ourself (and
    the window too). The default is NO.
"*/
{
    return _releasedWhenWindowClosed;
}

- (void) setReleasedWhenWindowClosed:(BOOL)freeUs
/*" 
    Sets whether, when our window is closed whether we release ourselves
    and our window.	The default is NO.
"*/
{
    _releasedWhenWindowClosed = freeUs;
}

@end


@implementation MiscWindowManager (WindowDelegate)

- (void) windowWillMiniaturize:(NSNotification*)sender
/*"
    Installs the miniwindowIcon.  Returns self. 
"*/
{
    if ([self _miniwindowIcon] != nil) {
        [[self window] setMiniwindowImage:
            [NSImage imageNamed:[self _miniwindowIcon]]];
    }
}
		
- (BOOL) windowShouldClose:(id)ourWindow
/*"
    If the main Window is dirty *and visible*, ask the user to save before 
    proceeding with the close.  If the user closed the Window previously 
    (which they must have if it wasn't visible), they've already been asked 
    this question, so don't ask them again. You shouldn't override or extend
    this method. Custom code should either be added to #_windowShouldClose:,
    #_queryWindowShouldClose: or #_windowClosed.
"*/
{
    BOOL shouldClose = YES;	// We'll close unless stopped.
    
    if (shouldClose) {
        // Give our class the opportunity to stop the close. If stopped
        // some sort of alert panel should probably be shown to explain
        // why we didn't follow their wishes.
        shouldClose = [self _windowShouldClose:ourWindow];
    }

    if (shouldClose) {
        // Only runs the alert Panel if the Window is dirty and visible.
        shouldClose = [self _alertUserOfWindowClose:ourWindow];
    }

    // This code must be the last executed before the window closes. If
    // you want to add more window will close hooks make sure they are
    // before this.
    if (shouldClose) {
        // We will be freeing the window in our release method so don't
        // let it free itself prematurely (if it happened to be set to free
        // early).
        [ourWindow setReleasedWhenClosed:NO];

        // It seems inevitable that we will close now.
        [self _windowClosed];
    }

    if (shouldClose && [self isReleasedWhenWindowClosed]) {
        // We'll free the window itself in our release message.
        [self autorelease];
    }

    // Return value of NO aborts the close operation.
    return shouldClose;
}

@end

