// SuperimposedObject.m
// Project: Rulers
//
// Stephan Wacker
// 93-02-13


#if 0	// Purpose

The idea of a SuperimposedObject is to replace a given object's class with a subclass so that some methods can be handled differently (by the regular overriding mechanism) while the references to the object are not changed.  This is useful if you want to modify the behaviour of User Interface objects that you set up with the InterfaceBuilder.

  A short example may show how this should work.

  Suppose you need a button that sends its action when the mouse button is pressed (instead of waiting until it is released).  You would create a subclass of Button and override its mouseDown: method.  But how do you tell InterfaceBuilder how to initialize and connect your Button?  The main problem is that you cannot simply drag a Button from the palette and change its class as you can change the class of the File's Owner or most other objects in the Objects File Window.

  My solution is to use a regular Button and change its class (the isa instance variable).  Thus, InterfaceBuilder can create and initialize the Button as usual, giving it a title or icon and connecting it to a target.  But when you change the Button's isa variable it will use all methods you have overridden in the Button subclass.

  NB: I realized only after this class was completed that in the example above you could simply send a  [[myButton cell] sendActionOn: NX_MOUSEDOWNMASK]  message to the Button and achieve the same effect.


What's new?

  The experienced developer may know two similar ways how to modify existing classes: posing and categories.  Both methods are less useful because they affect all instances of a class.  My method, however, can be applied very selectively.


Restrictions

  There are, however, some limitations to what you may do.

  -- The class you superimpose on the object must be a subclass of its original class.  That ensures that the object's instance variables are valid in its new life and that all its normal methods are still valid. 

  -- You must not define new instance variables in the subclass because the original object has not reserved any space for them.

  The method superimposeClass: defined below will make sure that these conditions are observed.  Otherwise an error message is printed (to the Workspace console) and the object's class is not changed.

#endif	// Purpose




#import "SuperimposedObject.h"


#define THIS_METHOD	sel_getName(_cmd)
#define THIS_CLASS	[[self class] name]



@implementation Object( SuperimposedObject )


+ (BOOL) isSubclassOf: aClass
// Answer whether the Class aClass is a superclass of the receiving Class.
// PRIVATE
{
    id		theClass;
    
    for( theClass  = [self class];
	 theClass != [Object class];
	 theClass  = [theClass superclass] )
    {
	if( theClass == aClass ) return YES;
    }
    
    return NO;
}


+ (size_t) instanceSize
// Answer the size (in bytes) of an instance of the receiving class.
// PRIVATE
{
    struct objc_class *selfP = (struct objc_class *) self;
    
    return selfP->instance_size;
}


- superimposeClass: aSubclass
// Change the receiving object's class to aSubclass.
{
    // Check that aSubclass is a class object.
    if( [aSubclass class] != aSubclass ) {
	NXLogError( "[%s %s]: argument is not a class",
	  THIS_CLASS, THIS_METHOD );
	return nil;
    }
    
    // Check that aSubclass is truly a subclass of the object's class. 
    if( ![aSubclass isSubclassOf: [self class]] ) {
	NXLogError( "[%s] %s is not a subclass of %s",
	  THIS_METHOD, [aSubclass name], THIS_CLASS );
	return nil;
    }
    
    // Check that the subclass has no additional instance variables.
    if( [aSubclass instanceSize] > [[self class] instanceSize] ) {
	NXLogError( "[%s] %s is larger than %s",
	  THIS_METHOD, [aSubclass name], THIS_CLASS );
	return nil;
    }
    
    // Magic...
    isa = aSubclass;
    
    [self awakeFromMetamorphosis];
    
    return self;
}


- awakeFromMetamorphosis
// Subclasses may implement this method and their objects will be notified
// when their class has been changed.
{
    return self;
}


@end	// Object( SuperimposedObject )



@implementation View( SuperimposedObject )


#if 0
- superimposeClass: aSubclass
// Views should be redisplayed when they change their class.
{
    if( [super superimposeClass: aSubclass] ) {
	[self update];
	return self;
    }
    
    return nil;
}
#endif

@end	// View( SuperimposedObject )

