/* MiscSwapView_ByObject.m				
*
* This is a MiscSwapView category. It can handle swapping of different
* contentViews (controlled by MiscSwapViewItem's) into ourself by
* comparing identifier objects or tags (identifier tags and controller tags).
* More in depth information can be found here in the source-code.
*
* Written by: 		Thomas Engel
* Created:    		24.01.1994 (Copyleft)
* Last modified: 	27.07.1997
* Copyright (C) 1995 Thomas Engel
*/

#import "MiscSwapView.h"
#import "MiscSwapViewItem.h"


NSString * MiscSwapViewItemWillGetSelectedNotification = @"MiscSwapViewItemWillGetSelectedNotification";
NSString * MiscSwapViewItemDidGetSelectedNotification = @"MiscSwapViewItemDidGetSelectedNotification";
NSString * MiscSwapViewItemWillGetDeselectedNotification = @"MiscSwapViewItemWillGetDeselectedNotification";
NSString * MiscSwapViewItemDidGetDeselectedNotification = @"MiscSwapViewItemDidGetDeselectedNotification";



@implementation MiscSwapView (ByObject)


- (void)swapContentView:sender
/*"
    This is the method that has to be triggered by each UI object for a nice swap. We will here try to smartly identify the identifier object which is needed for the actual swap.
"*/
{	
    // Currently we support all buttons, cells, matrixes and popUpButtons.
    // This is hardwired and not perfect for subclassing...but if should
    // cover 99% of the regular UI setups anyway.
    
    id 	anObject = sender;

    if( [sender isKindOfClass:[NSMatrix class]] )
        anObject = [sender selectedCell];
    else if( [sender isKindOfClass:[NSPopUpButton class]] )
        anObject = [sender selectedItem];

    [self selectSwapViewItemWithIdentifier:anObject];
}

- (void)selectSwapViewItem:(MiscSwapViewItem *)anItem;
/*"
   Figures out which view actually to swap in based on based on the specified item and then performs the swap after sending out all related notifications. The delegate and the current item have a chance to intercept the swap and cancel the action if they don't agree with the swap. This allows for "item" validation so that a view has to stay up until a values are set properly.

   Passing nil as the new item will swap out the current one and leave the view empty. Reselecting the currently selected item has no effekt.
"*/
/*" @Note:
    Maybe we should retrun a BOOL here since there is a good chance of "swap" failure...this might enable us to "roll-back" the Ui elements after a failure.
"*/
/*" @Bugs:
    Letting the swapItem intercepting the swap is a nice idea...but won't currently work properly with all UI objects... since most UI widges change their state anyway and we will end up with a inconsistent UI !! This might require a different solution where we will rest the UI widget if the swap is refused...we need a "subclass-customaizable" method for that....got to think about it later.
"*/
{
    id		oldUserInfo = nil;
    id		newUserInfo = nil;

    // Really something to do ??

    if( currentSwapItem == anItem ) return;

    // Lets remember the info for notifications that we will post later on.

    if( currentSwapItem )
        oldUserInfo = [NSDictionary dictionaryWithObjectsAndKeys:currentSwapItem, @"Item", nil ];
    if( anItem )
        newUserInfo = [NSDictionary dictionaryWithObjectsAndKeys:anItem, @"Item", nil ];

    // <<HACK>> Need to check if there isn't a generic delegate dispatch method in the MiscKit...if not lets add one.
   // Doing a retain here is good since we can then release the current item even if they are the same.

    [anItem retain];

    if( !delegate ||
        ( [delegate respondsToSelector:@selector(swapView:shouldSelectSwapViewItem:)] &&
          [delegate swapView: self shouldSelectSwapViewItem:anItem] == YES ) )
    {
        // We propably should ask our current item if its accepting the fact that its time has come
        // to leave the place... if its not willing to...then the action is void.
        // <<HACK>>...<<BUG>>
        // This will not propage to the UI properly in most cases and currently will only work perfectly when using
        // buttons.

        if( ( [currentSwapItem respondsToSelector:@selector(swapView:shouldSelectSwapViewItem:)] &&
              [currentSwapItem swapView:self shouldSelectSwapViewItem:anItem] == NO ) )
        {
            [anItem release];
            return;
        }

        // Seems like we are in the swapping business....

        [[NSNotificationCenter defaultCenter] postNotificationName:MiscSwapViewItemWillGetDeselectedNotification
                                                            object:self
                                                          userInfo:oldUserInfo];

        [[NSNotificationCenter defaultCenter] postNotificationName:MiscSwapViewItemWillGetSelectedNotification
                                                            object:self
                                                          userInfo:newUserInfo];

        [self setContentView:[anItem view]];
        [currentSwapItem release];
        currentSwapItem = anItem;

        [[NSNotificationCenter defaultCenter] postNotificationName:MiscSwapViewItemDidGetDeselectedNotification
                                                            object:self
                                                          userInfo:oldUserInfo];

        [[NSNotificationCenter defaultCenter] postNotificationName:MiscSwapViewItemDidGetSelectedNotification
                                                            object:self
                                                          userInfo:newUserInfo];
    }
    else
    {
        [anItem release];
    }
}

- (void)selectSwapViewItemWithIdentifier:(id)anObject;
/*"
Figures out which view actually to swap in based on information return by swapViewItemWithIdentifier: and the given identifier.and then envoces selectSwapViewItem:
"*/
{
    [self selectSwapViewItem:[self swapViewItemWithIdentifier:anObject]];
}

- (MiscSwapViewItem *)selectedSwapViewItem
{
    return currentSwapItem;
}

- (void)addSwapViewItem:(id)anItem
/*"
    Adds a swapView item to the internal item list and registers it for all our notifications so that it can find out when it's time to prepare for displaying.
"*/
{
   // We do not want the same controller registering more than once. So lets remove it before we add it.

    [anItem retain];
    if( ![itemArray containsObject:anItem] ) [itemArray addObject:anItem];
    [anItem release];
    [self _registerNotificationObserver:anItem];
}

- (void)insertSwapViewItem:(id)anItem atIndex:(int)index
/*"
    Adds a swapView item to the internal item list and registers it for all our notifications so that it can find out when it's time to prepare for displaying.
"*/
{
    // This method is necessary since search order is important and therefore it makes sens to add items
    // at the beginning and not only at the end.
    // We do not want the same controller registering more than once. So lets remove it before we add it.

    [anItem retain];
    if( ![itemArray containsObject:anItem] ) [itemArray insertObject:anItem atIndex:index];
    [anItem release];
    [self _registerNotificationObserver:anItem];
}

- (void)removeSwapViewItem:(id)anItem
/*"
    This will remove anItem from the list of swapItems. However, it will not affect the currently visible content view. This means that you might have to remove the current view if it corresponds to the currently removed item. <<NOTE: We might change this behavior later, or you can take care of that in a subclass>>
 */
{
    [self _unregisterNotificationObserver:anItem];
    [itemArray removeObject:anItem];
}

- (void)removeAllSwapViewItems
/*"
    We will remove all swap view items at once by releaseing the entire array. However, it will not affect the currently visible content view and requires similar attention as the regular removeSwapViewItem: method.
"*/
{
    id		nextItem = nil;
    id		anEnumerator = [itemArray objectEnumerator];
    
    while( nextItem = [anEnumerator nextObject] )
        [self _unregisterNotificationObserver:nextItem];

    [itemArray release];
    itemArray = [NSMutableArray new];
}

- (NSArray *)swapViewItems
/*"
    Return the list of all registered swapViewItems. You have to use the related methods to actually modify this array since otherwise some settings (notifications) might become inconsistent.
"*/
{
    return itemArray;
}

- (void)_registerNotificationObserver:(id)anObject
/*"
    Registers an object for all our notifications sending all the default notification messages.
"*/
{
    if( !anObject ) return;

    // Only add if it knows about that method...

    if( [anObject respondsToSelector:@selector(swapViewItemWillGetSelected:)] )
        [[NSNotificationCenter defaultCenter] addObserver:anObject
                                                 selector:@selector(swapViewItemWillGetSelected:)
                                                     name:@"MiscSwapViewItemWillGetSelectedNotification"
                                                   object:self];
    if( [anObject respondsToSelector:@selector(swapViewItemDidGetSelected:)] )
        [[NSNotificationCenter defaultCenter] addObserver:anObject
                                                 selector:@selector(swapViewItemDidGetSelected:)
                                                     name:@"MiscSwapViewItemDidGetSelectedNotification"
                                                   object:self];
    if( [anObject respondsToSelector:@selector(swapViewItemWillGetDeselected:)] )
        [[NSNotificationCenter defaultCenter] addObserver:anObject
                                                 selector:@selector(swapViewItemWillGetDeselected:)
                                                     name:@"MiscSwapViewItemWillGetDeselectedNotification"
                                                   object:self];
    if( [anObject respondsToSelector:@selector(swapViewItemDidGetDeselected:)] )
        [[NSNotificationCenter defaultCenter] addObserver:anObject
                                                 selector:@selector(swapViewItemDidGetDeselected:)
                                                     name:@"MiscSwapViewItemDidGetDeselectedNotification"
                                                   object:self];
}

- (void)_unregisterNotificationObserver:(id)anObject
/*"
    Unregisters an object for all our notifications.
"*/
{
    if( !anObject ) return;

    // Only remove if we really added it = if it knows about that method...

    if( [anObject respondsToSelector:@selector(swapViewItemWillGetSelected:)] )
        [[NSNotificationCenter defaultCenter] removeObserver:anObject
                                                        name:MiscSwapViewItemWillGetSelectedNotification
                                                      object:self];
    if( [anObject respondsToSelector:@selector(swapViewItemDidGetSelected:)] )
        [[NSNotificationCenter defaultCenter] removeObserver:anObject
                                                        name:MiscSwapViewItemDidGetSelectedNotification
                                                      object:self];
    if( [anObject respondsToSelector:@selector(swapViewItemWillGetDeselected:)] )
        [[NSNotificationCenter defaultCenter] removeObserver:anObject
                                                        name:MiscSwapViewItemWillGetDeselectedNotification
                                                      object:self];
    if( [anObject respondsToSelector:@selector(swapViewItemDidGetDeselected:)] )
        [[NSNotificationCenter defaultCenter] removeObserver:anObject
                                                        name:MiscSwapViewItemDidGetDeselectedNotification
                                                      object:self];
}

- (void)setTagComparison:(BOOL)flag
{
    // Should we compare the tags first...and then the objects ?

    tagComparison = flag;
}

- (BOOL)doesTagComparison
{
    return tagComparison;
}

- (id)swapViewItemWithIdentifier:(id)anObject
{
    // This is the basic comparison center. Subclasses of this class should
    // implement only the new findControllerByTag(Object) methods and leave
    // this method untouched. Sometime there is a way of finding a more
    // 'useable' identifier even inside the swapAction method.
    // Using this method from addSwapViewItem: to see if there is already a
    // a controller for that identifier only works when using identifiers as the
    // comparison. It does not for tags, since we don't care if someone
    // registers all the itemArray for the same tag..

    id		newItem = nil;
    
    if( [self doesTagComparison] )
        newItem = [self swapViewItemWithIdentifier:anObject comparingTags:YES];

    if( !newItem ) newItem = [self swapViewItemWithIdentifier:anObject comparingTags:NO];

    return newItem;
}

- (id)swapViewItemWithIdentifier:(id)anObject comparingTags:(BOOL)flag
{
    // This is the basic comparison center. Subclasses of this class should
    // implement only the new findControllerByTag(Object) methods and leave
    // this method untouched. Sometime there is a way of finding a more
    // 'useable' identifier even inside the swapAction method.
    // Using this method from addSwapViewItem: to see if there is already a
    // a controller for that identifier only works when using identifiers as the
    // comparison. It does not for tags, since we don't care if someone
    // registers all the itemArray for the same tag..

    id		newItem = nil;
    id		anItem;
    int		i;

    // Used to also check that identifier responded to tag, but that did not
    // work when the identifier was nil and we wanted a comparison by tag only.


    for( i=0; i<[itemArray count]; i++ )
    {
        anItem = [itemArray objectAtIndex:i];

        if( [anItem matchesIdentifier:anObject comparingTags:flag] )
        {
            newItem = anItem;
            // NSLog( @"Matched: %@ forKey: %@",newItem, anObject );
            break;
        }
    }

    return newItem;
}

@end

/*
 * History: 24.02.94 Made it a MiscSwapView Category.
 *
 *			24.01.94 Made it a subclass of MiscSwapView
 *
 *			08.01.94 Switched to tagComparison for better reading.
 *					 choosesByTagFirst was not that nice.
 *
 *			21.12.93 Code transferred from the old swapPopManager and
 *					 some viewController methods added plus the identifier object
 *					 which now stores the object identifiering our swap.
 *
 *			20.12.93 Enlightened the controller to check the tags if they are
 *					 set. This helps to localize apps.
 *
 *			04.12.93 Added a delegate to this class the enable better
 *					 command-key handling.
 *
 *			04.11.93 First steps to a general-purpose swapPopManager.
 *
 *
 * Bugs: - I'm not sure about what to free....
 */