
/* IconView.m */

#import <objc/List.h>
#import <appkit/Bitmap.h>
#import <appkit/Listener.h>
#import <appkit/graphics.h>
#import <dpsclient/psops.h>
#import <dpsclient/wraps.h>
#import <strings.h>
#import <libc.h>
#import <math.h>
#import "MyApp.h"
#import "DockSpeaker.h"
#import "IconView.h"

#define UPPER_RIGHT 1
#define LOWER_RIGHT 2

@implementation IconView

+ new:(char *)name :icon :(int)xCoord :(int)yCoord
{
    self = [super new];
    
    /* copy the app's name */
    appName = (char *)malloc(strlen(name) + 1);
    strcpy(appName, name);
    
    /* initialize bitmap locations */
    bitmap = icon;
    activeBitmap = [Bitmap findBitmapFor:"active"];

    /* save location in dock grid */
    xGridLocation = xCoord;
    yGridLocation = yCoord;
    
    /* figure out if we're the dock window */
    if (!strcmp(appName, "Dock")) {
      theDock = YES;
      altDocAppHidden = NO;
    }
    
    /* initialize */
    highLighted = NO;
    active = NO;
    appsPSContext = 0;
    
    return self;
}

- initialize:list :(int)minX :(int)minY :(int)maxX :(int)maxY
/* give the dock the bounding box and list of all app windows */
{
    xMin = minX;
    xMax = maxX;
    yMin = minY;
    yMax = maxY;
    
    windowList = list;
    
    /* create the drawing order lists for moving diagonally */
    upperRight = [self createOrderedListForCorner:UPPER_RIGHT];
    lowerRight = [self createOrderedListForCorner:LOWER_RIGHT];
        
    return self;
}

- createOrderedListForCorner:(int)corner
/* returns a list of windows ordered based on the corner parameter 	*/
/* (this is the order in which they should be moved when moving toward  */
/* this corner								*/
{
    int    i, x1, y1, x2, y2, j;
    id     list, tempList, iconWindow;
    
    list = [List new];
    tempList = [List new];   
    i = [windowList count];
    
    /* insert all windows into the temporary list (ordered by x position) */
    while (i--) {
      iconWindow = [windowList objectAt:i];
      [[iconWindow contentView] coordinates:&x1 :&y1];
      
      j = 0;
      while (j < [tempList count]) {
        [[[tempList objectAt:j] contentView] coordinates:&x2 :&y2];
	
	/* sort by x position */
	if (corner == UPPER_RIGHT && x1 >= x2) {
	  break;
	} else if (x1 >= x2) {
	  break;
	}
	j++;
      }
      if (j == [tempList count]) {
        [tempList addObject:iconWindow];
      } else if (![tempList insertObject:iconWindow at:j]) {
        [tempList addObject:iconWindow];
      }
    }

    i = [tempList count];
    
    /* insert tempList windows into the final list, sorted by x and y */
    while (i--) {
      iconWindow = [tempList objectAt:i];
      [[iconWindow contentView] coordinates:&x1 :&y1];
      
      j = 0;
      while (j < [list count]) {
        [[[list objectAt:j] contentView] coordinates:&x2 :&y2];
	
	/* sort by y */
	if (corner == UPPER_RIGHT && x1 >= x2 && y1 >= y2) {
	  break;
	} else if (x1 >= x2 && y1 <= y2) {
	  break;
	}
	j++;
      }
      if (j == [list count]) {
        [list addObject:iconWindow];
      } else if (![list insertObject:iconWindow at:j]) {
        [list addObject:iconWindow];
      }
    }

    [tempList free];
    
    return list;
}

- drawSelf:(const NXRect *)rects :(int)rectCount
{
    NXRect   drawRect;
    int      numBitmaps;
    NXPoint  origin, activePoint;
    
    /* initialize a drawing rectangle */
    drawRect.origin.x = drawRect.origin.y = 0.0;
    drawRect.size.width = drawRect.size.height = 64.0;
    origin.x = origin.y = 8.0;
    
    if (!theDock) {
      /* if we're not the dock, draw our bezel */
      NXDrawButton(&drawRect, 0);
      NXInsetRect(&drawRect, 1.0, 1.0);
      NXDrawButton(&drawRect, 0);
      NXInsetRect(&drawRect, -1.0, -1.0);
    
      /* draw our 48x48-bit icon */
      [bitmap composite:NX_SOVER toPoint:&origin];
      activePoint.x = 64.0 - 2.0 - 18.0;
      activePoint.y = drawRect.origin.y + 4.0;
    
      /* draw the three dots if our program isn't active */
      if (!active) {
        [activeBitmap composite:NX_COPY toPoint:&activePoint];
      }
      
      /* highlight ourself if we're getting our app to run or unhide */
      if (highLighted) {
        NXInsetRect(&drawRect, 2.0, 2.0);
        NXHighlightRect(&drawRect);
      }
    } else {
      /* we're the dock so just copy our 64x64-bit icon into ourself */
      origin.x = origin.y = 0.0;
      [bitmap composite:NX_COPY toPoint:&origin];
    }
         
    return self;
}    

#define MOVEMASK (NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK)
- mouseDown:(NXEvent *)theEvent
{
    NXPoint    mouseDownLocation;
    NXEvent    *currentEvent;
    NXCoord    oldX, oldY, newX, newY, dx, dy, x, y;
    NXRect     frameRect;
    int        windowNum;
    
    /* if we're not the dock, see if we should unhide our application */
    if (!theDock) {
      [self handleNonDock:theEvent];
      return self;
    }
    
    /* hide or unhide the dock's menus and icon window if a double click */
    if (theEvent->data.mouse.click == 2) {
      [self hideOrUnhide];
    }
    
    [window addToEventMask:MOVEMASK];
    
    /* get the dock window's location */
    [window getFrame:&frameRect];
 
    /* convert the mouse's location to global (screen) coordinates */
    mouseDownLocation = theEvent->location;
    PScurrenttoscreen(mouseDownLocation.x, mouseDownLocation.y, &oldX, &oldY);
    
    /* initialization */
    newX = frameRect.origin.x;
    newY = frameRect.origin.y;
    
    currentEvent = [NXApp getNextEvent:MOVEMASK];
    
    while (currentEvent->type != NX_MOUSEUP) {
      /* things get messed up if we move our window and ask the window */
      /* to convert our mouse location to screen coordinates, so we    */
      /* use this call instead to get the mouse's screen location      */
      PScurrentmouse(0, &x, &y);
      
      /* offset from previous mouse location */
      dx = x - oldX;
      dy = y - oldY;
      
      /* compute the dock window's proposed origin */
      newX = dx + frameRect.origin.x;
      newY = dy + frameRect.origin.y;
      
      /* make sure this is ok (and fix it if not) */
      [self checkCoords:&newX :&newY];
      
      /* move everyone the correct amount */ 
      [self moveWindows:(newX - frameRect.origin.x)
      		       :(newY - frameRect.origin.y)];
      
      /* get our new origin and save the current mouse location */
      [window getFrame:&frameRect];
      oldX = x;
      oldY = y;
      
      /* wait for another mouse event */
      currentEvent = [NXApp getNextEvent:MOVEMASK];
    }
    
    /* impose the NeXT dock's grid behaviour on the window positions */
    [self snapToGrid:&newX :&newY];
    [self moveWindows:(newX - frameRect.origin.x)
    		     :(newY - frameRect.origin.y)];
    
    return self;
}

- checkCoords:(float *)x :(float *)y
/* constrain the dock window's location based on the bounding box */
{
    float    maxX, maxY;
    
    if (*x < 4) *x = 4;
    if (*y < 0) *y = 0;
    
    maxX = (xMax - xMin) * 64.0 + 4.0;
    maxY = (yMax - yMin) * 64.0;
    
    if (*y > maxY) *y = maxY;
    
    if (*x > maxX) *x = maxX;
    
    return self;
}

- snapToGrid:(float *)x :(float *)y
/* make sure windows lay on 64-bit boundaries */
{
    *x = rint((*x - 4) / 64.0) * 64.0 + 4.0;
    *y = rint(*y / 64.0) * 64.0;
    return self;
}

- moveWindows:(float)dx :(float)dy
/* move the windows dx and dy */
{
    int     numberOfWindows, startX, stopX, startY, stopY, i;
    id      theList, moveWindow;
    NXRect  frameRect;
    BOOL    reverse;
    
    /* note that when moving to upper left, we can use the lower right  */
    /* window list, just traverse it in the opposite direction		*/
    if (dx <= 0 && dy < 0) {
      theList = upperRight;
      reverse = YES;
    } else if (dx >= 0 && dy > 0) {
      theList = upperRight;
      reverse = NO;
    } else if (dx < 0 && dy >= 0) {
      theList = lowerRight;
      reverse = YES;
    } else {	/* (dx > 0 && dy <= 0) */
      theList = lowerRight;
      reverse = NO;
    }
        
    numberOfWindows = [theList count];
    
    if (!reverse) {
      i = 0;
      while (i < numberOfWindows) {
	moveWindow = [theList objectAt:i++];
	[moveWindow getFrame:&frameRect];
	[moveWindow moveTo:(frameRect.origin.x + dx)
			  :(frameRect.origin.y + dy)];
      }
    } else {
      i = numberOfWindows;
      while (i--) {
	moveWindow = [theList objectAt:i];
	[moveWindow getFrame:&frameRect];
	[moveWindow moveTo:(frameRect.origin.x + dx)
			  :(frameRect.origin.y + dy)];
      }
    }
    
    return self;
}

- handleNonDock:(NXEvent *)theEvent
/* click in a non-dock window */
{
    port_t  appPort;
    int     flag;
    
    /* only concern ourselves with double clicks */
    if (theEvent->data.mouse.click == 2) {
      /* simulate NeXT dock functionality */
      highLighted = YES;
      [self display];
      [NXApp deactivateSelf];
      
      /* get the WorkSpace to make sure our app is running */
      appPort = NXPortFromName((rindex(appName, '/') + 1), NULL);
      if (appPort == PORT_NULL) {
      	/* something went wrong */
        highLighted = NO;
	[self display];
        [NXApp activateSelf:YES];
        return self;
      }
      /* now unhide the app (just like the WorkSpace) */
      [[NXApp appSpeaker] setSendPort:appPort];
      [[NXApp appSpeaker] unhide];
      
      /* save our app's PostScript context (for a later version, maybe) */
      appsPSContext = [NXApp activeApp];
      
      /* all done, so show app is active and unhighlight ourselves */
      highLighted = NO;
      active = YES;
      [self display];
    }
    
    return self;
}

- hideOrUnhide
/* hide or unhide the dock app's menu */
{
    if (altDocAppHidden) {
      [[NXApp mainMenu] orderFront:self];
      altDocAppHidden = NO;
    } else {
      [[NXApp mainMenu] orderOut:self];
      altDocAppHidden = YES;
    }      
    
    return self;
}

- (BOOL)theDock
{
    return theDock;
}

- coordinates:(int *)xCoord :(int *)yCoord
/* return our grid coordinates */
{
    *xCoord = xGridLocation;
    *yCoord = yGridLocation;
    return self;
}

@end
