//
// Time-stamp: <95/12/08 22:57:29 stephan>
//
// RulerControl.m
// Project: Rulers
//
// Stephan Wacker
// 93-01-31

#import "RulerControl.h"
#import "RulerView.h"
#import "RulerPreferencesControl.h"
#import "RulerManager.h"

#import "RulerPanel.h"
#import "SuperimposedObject.h"


@implementation RulerControl


- (int) unitsMode
// Answer the current units mode.
{
    return unitsSystem;
}

- setUnitsMode: (int) system
// Set new units mode.
// PRIVATE
{
    if( unitsSystem != system )
    {
	unitsSystem = system;
	[view setCacheInvalid];
    }
    
    return self;
}



#if 0
#
- (int) screenMode
// Answer the current screen mode.
{
    id		preferences = [[NXApp delegate] preferencesControl];
    
    if( preferences ) {
	return [preferences screenMode];
    } else {
	return [RulerPreferencesControl defaultScreenMode];
    }
}
#else
#
- (float) screenDpi
// Answer the current screen dpi setting.
{
    id		preferences = [[NXApp delegate] preferencesControl];
    
    if( preferences ) {
	return [preferences screenDpi];
    } else {
	return [RulerPreferencesControl defaultScreenDpi];
    }
}
#
#endif



- (int) inchMode
// Answer the current inch mode.
{
    return inchMode;
}

- setInchMode: (int) mode
// Set new inch mode.
// PRIVATE
{
    if( inchMode != mode )
    {
	inchMode = mode;
	[view setCacheInvalid];
    }
    
    return self;
}



- (BOOL) labelsMode
// Answer current labels mode.
{
    return displayLabels;
}

- setLabelsMode: (BOOL) enabled
// Set new labels mode.
// PRIVATE
{
    if( displayLabels != enabled )
    {
	displayLabels = enabled;
	[view setCacheInvalid];
    }
    
    return self;
}



- (BOOL) floatMode
// Answer current float mode.
{
    return floatAboveWindows;
}

- setFloatMode: (BOOL) enabled
// Set new float mode.
// PRIVATE
{
    // According to the documentation the Ruler windows don't qualify for
    // floating panels because
    // -- they become the key window,
    // -- they remain on-screen when the application is deactivated.
    // But we want to make the rulers float above other application
    // windows, too.
    
    floatAboveWindows = enabled;
    [window setFloatingPanel: enabled];
    
    return self;
}



- (BOOL) hideMode
// Answer current hide mode.
{
    return hideWhenDeactivated;
}

- setHideMode: (BOOL) enabled
// Set new hide mode.
// PRIVATE
{
    hideWhenDeactivated = enabled;
    [window setHideOnDeactivate: enabled];
    
    return self;
}



- (NXColor) color
// Answer current color.
{
    return color;
}

- (NXColor) marksColor
// Answer current color for drawing the marks and numbers.
{
    return marksColor;
}

- setColor: (NXColor) col
// Set new color.
// See also subclasses.
{
    float	r, g, b;

    color = col;

    // The RulerView will take the new color automatically.
    // The other controls, however, need to have their images changed.

    [self makeColoredImage: &knobImage[0]
     shape: "RulerKnob" mask: "RulerKnobMask"];
    [knob setImage: knobImage[0]];

    [self makeColoredImage: &knobImage[1]
     shape: "RulerKnobH" mask: "RulerKnobMask"];
    [knob setAltImage: knobImage[1]];
    
    // Select the marks color: If the ruler color is very dark we use
    // white instead of black.
    //
    NXConvertColorToRGB( color, &r, &g, &b );
    if( 1.2*r + 1.5*g + b < 0.3 ) {
	marksColor = NX_COLORWHITE;
    } else {
	marksColor = NX_COLORBLACK;
    }

    [view setCacheInvalid];

    return self;
}



- window
// Answer ruler window.
{
    return window;
}



#define INIT_LOCATION_OFFSET	12

- initLocation
// Place the window next to the current main ruler.
{
    id		ref = [[NXApp delegate] mainRuler];
    NXRect	refRect, rect;
    NXSize	screenSize;
    
    if( ! ref ) return nil;
    
    [[ref window] getFrame: &refRect];
    [window getFrame: &rect];
    [NXApp getScreenSize: &screenSize];
    
    // Make top left corner equal to reference.
    rect.origin.x = refRect.origin.x;
    rect.origin.y = refRect.origin.y + refRect.size.height - rect.size.height;
    
    // Make sure the window is completely visible.
    if( rect.origin.x < 0 ) {
	rect.origin.x = 0;
    }
    else if( rect.origin.x + rect.size.width > screenSize.width ) {
	rect.origin.x = screenSize.width - rect.size.width;
    }
    if( rect.origin.y < 0 ) {
	rect.origin.y = 0;
    }
    else if( rect.origin.y + rect.size.height > screenSize.height ) {
	rect.origin.y = screenSize.height - rect.size.height;
    }
    
    // Move towards the center of the screen.
    if( rect.origin.x + rect.size.width/2 < screenSize.width/2 ) {
	rect.origin.x += INIT_LOCATION_OFFSET;
    } else {
	rect.origin.x -= INIT_LOCATION_OFFSET;
    }
    if( rect.origin.y + rect.size.height/2 < screenSize.height/2 ) {
	rect.origin.y += INIT_LOCATION_OFFSET;
    } else {
	rect.origin.y -= INIT_LOCATION_OFFSET;
    }
    
    // Move the window to its initial location.
    [window moveTo: rect.origin.x : rect.origin.y];
    
    return self;
}



- awakeFromNib
// Initialize yourself after nib file has been loaded.
{
    static	int	nextId = 0;
    
    id		preferences = [[NXApp delegate] preferencesControl];
    
    if( preferences ) {
	[self takePreferencesFrom: preferences];
    } else {
	[self setUnitsMode: [RulerPreferencesControl defaultUnitsMode]];
	// The screen mode is not an instance variable.
	[self setInchMode: [RulerPreferencesControl defaultInchMode]];
	[self setLabelsMode: [RulerPreferencesControl defaultLabelsMode]];
	[self setFloatMode: [RulerPreferencesControl defaultFloatMode]];
	[self setHideMode: [RulerPreferencesControl defaultHideMode]];
	[self setColor: [RulerPreferencesControl defaultColor]];
    }
    
    myId = nextId ++;
    [self initLocation];

    // The following statement in the RulerView's initFrame: method
    // doesn't work.  It seems that the window is not yet placed
    // correctly on the screen and the translation to screen coordinates
    // gives wrong results.  Therefore we choose this place to complete
    // the initialization.
    //
    [view synchScreenOrigin];
    
    [window makeKeyAndOrderFront: nil];
    
    return self;
}



- drag: sender
// Drag the ruler window across the screen.
// If the window is not dragged it becomes the key window
// and the application is activated.
{
    NXEvent	*event = [NXApp currentEvent];		// MouseDown
    NXEvent	nextEvent;
    int		oldMask = [window eventMask];
    int		trackMask = NX_MOUSEUPMASK | NX_MOUSEDRAGGEDMASK;
    
    // The next event determines whether we're dragging or not.
    [window setEventMask: (oldMask | trackMask)];
    [NXApp peekNextEvent: trackMask
		    into: &nextEvent
		 waitFor: NX_FOREVER
	       threshold: NX_MODALRESPTHRESHOLD
    ];    
    
    if( nextEvent.type == NX_MOUSEUP ) {
	[window makeKeyAndOrderFront: nil];
	[NXApp activateSelf: YES];
    } else {
	[window dragFrom: event->location.x
			: event->location.y
		eventNum: event->data.mouse.eventNum
	];
	rulerWasDragged = ([sender tag] > 0);	// see windowDidMove: below
    }

    [window setEventMask: oldMask];
    
    return self;
}



- resize: (SEL) action
// Resize ruler window using action.
{
    [view showRubberband];
    [view trackMouseFor: view action: action];
    [view hideRubberband];

    [[window delegate] windowDidResize: nil];	// not generated automatically

    return self;
}



- applyPreferences: sender
// Get current settings from the preferences control panel.
{
    [self takePreferencesFrom: sender];
    [window display];
    
    return self;
}



- takePreferencesFrom: sender
// Get all preferences settings from the sender object.
// Subclasses may ask for more preferences and finish with
// return [super takePreferencesFrom: sender].
{
    [self setUnitsMode: [sender unitsMode]];
    // The screen mode is not an instance variable.
    [self setInchMode: [sender inchMode]];
    [self setLabelsMode: [sender labelsMode]];
    [self setFloatMode: [sender floatMode]];
    [self setHideMode: [sender hideMode]];
    [self setColor: [sender color]];
    
    return self;
}


// Some aspects of the ruler can be controlled by keystrokes.
//
- keyDown: (NXEvent *) theEvent
{
    switch( theEvent->data.key.charSet ) {
      
      case NX_ASCIISET:
	
	switch( theEvent->data.key.charCode ) {
	  case 'c':
	    [self setUnitsMode: UNIT_CM];
	    break;
	  case 'C':
	    [self setUnitsMode: UNIT_TRUE_CM];
	    break;
	  case 'f':
	    [self setFloatMode: ! [self floatMode]];
	    break;
	  case 'h':
	    [self setHideMode: ! [self hideMode]];
	    break;
	  case 'i':
	    [self setUnitsMode: UNIT_IN];
	    break;
	  case 'I':
	    [self setUnitsMode: UNIT_TRUE_IN];
	    break;
	  case 'p':
	    [self setUnitsMode: UNIT_PC];
	    break;
	  case 't':
	    [self setUnitsMode: UNIT_PT];
	    break;
	  case '8':
	    [self setInchMode: INCH_DIV_8];
	    break;
	  case '0':
	    [self setInchMode: INCH_DIV_10];
	    break;
	  default:
	    return nil;
	} // switch charCode
	
	break; // case NX_ASCIISET
	
      
      case NX_SYMBOLSET:
	
	switch( theEvent->data.key.charCode ) {
	  default:
	    return nil;
	} // switch charCode
	
	break; // case NX_SYMBOLSET
	
    } // switch charSet
    
    [window display];
    return self; // event was handled
}



- makeColoredImage: (NXImage **) canvas
  shape: (const char *) shapeName
  mask: (const char *) maskName
// Compose an image with the given shape and the current color.
// The mask defines transparent areas.
   // If the shape icon doesn't exist, the image is filled with the color.
   // If the mask icon doesn't exist, the image is opaque.
{
    NXImage	*shape;
    NXImage	*mask;
    NXRect	rect;

    shape = [NXImage findImageNamed: shapeName];
    mask  = [NXImage findImageNamed: maskName];

    // Create a new image having the same size as the shape.
    [shape getSize: &rect.size];
    rect.origin.x = rect.origin.y = 0;
    
    if( ! *canvas ) {
	*canvas = [[NXImage alloc] initSize: &rect.size];
	[*canvas useCacheWithDepth: NX_TwelveBitRGBDepth];
    }

    // Draw into the canvas image.
    [*canvas lockFocus];
    
    // Fill it with the current color.
    NXSetColor( [self color] );
    NXRectFill( &rect );
    
    // Composite the shape over it; the shape's transparency will determine
    // where the color is visible.
    [shape composite: NX_SOVER toPoint: &rect.origin];
    
    // Composite the mask over it; the image will take on the transparency
    // of the mask.
    [mask composite: NX_DIN toPoint: &rect.origin];
    
    [*canvas unlockFocus];
    
    return self;
}

- makeKnobImage: (NXImage *) canvas
{
    return nil;
}

- makeBattenImage: (NXImage *) canvas rotate: (int) turn
{
    return nil;
}


@end // RulerControl




@implementation RulerControl( WindowDelegate )


- windowWillClose: sender
// Clean up before ruler window is closed.
{
    [sender setDelegate: nil];
    [[NXApp delegate] rulerWillClose: self];
    [self free];
    
    return self;
}



- windowDidMove: sender
// If the window was dragged by its title bar the ruler view
// will be adjusted so that its origin stays at the same
// screen coordinates as before dragging.
{
    if( rulerWasDragged ) {
	[view synchScreenOrigin];	// keep view coordinates
	rulerWasDragged = NO;
    } else {
	[view synchOrigin];		// move view coordinates
	[window display];
    }
    
    return self;
}



- highlightMainRuler
{
    [controlBoxBackground setTransparent: NO];
    [controlBox display];
    
    return self;
}


- unhighlightMainRuler
{
    [controlBoxBackground setTransparent: YES];
    [window display];
    
    return self;
}



- windowDidBecomeKey: sender
// We are now the main ruler.
{
    [[NXApp delegate] rulerDidBecomeMain: self];
    [self highlightMainRuler];
    
    return self;
}


- windowDidResignKey: sender
// We are no longer the main ruler.
{
    [[NXApp delegate] rulerDidBecomeMain: self];
    [self unhighlightMainRuler];
    
    return self;
}


@end // RulerControl( WindowDelegate )
