/**********************************************************************
 *          Copyright (c) 1992 by Borland International, Inc.         *
 *                                                                    *
 *                            BCWDEMO.C                               *
 *                                                                    *
 *  This program demonstrates how to write a simple Microsoft Windows *
 *  graphics program in Borland C++.  It's called Simple Paint and    *
 *  provides three objects the user can draw, a line, an ellipse,     *
 *  and a rectangle.  Simple Paint also provides the user with        *
 *  three choices of line thicknesses and three choices of colors:    *
 *  red, green, and black.                                            *
 **********************************************************************/

#define STRICT

#include <windows.h>
#include "bcwdemo.h"

/*****************************************************
 * FUNCTION PROTOTYPES
 *****************************************************/

/*
 * DoWMCommand processes WM_COMMAND messages.  WM_COMMAND
 * is generated when the user selects something from the menu.
 */
int DoWMCommand(WORD wParam, HWND hWnd);

/*
 * DoLButtonDown process WM_LBUTTONDOWN messages.  WM_LBUTTONDOWN
 * is generated when the user presses the left mouse button.
 */
void DoLButtonDown(HWND hWnd,LONG lParam);

/*
 * DoLButtonUp processes WM_LBUTTONUP messages.  WM_LBUTTONUP
 * is generated when the user releases the left mouse button.
 */
void DoLButtonUp(HWND hWnd,LONG lParam);

/*
 * DoMouseMove processes WM_MOUSEMOVE messages.  WM_MOUSEMOVE
 * is generated when the user moves the mouse.
 */
void DoMouseMove(HWND hWnd,LONG lParam);

/*
 * DoPaint processes WM_PAINT messages.  WM_PAINT is generated
 * whenever UpdateWindow is called or another window is moved,
 * revealing a portion of the window receiving this message.
 */
void DoPaint(HWND hWnd);

/*
 * WndProc is the callback function (window proc)
 * for the Simple Paint class of windows.
 */
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

/*
 * DrawShape draws a shape into a device context
 * at a given location.
 */
void DrawShape(HDC hdc,int x,int y,int x2, int y2,int Shape,
	       int PenWidth,COLORREF PenColor,int Slope);

/***************************************************************
 * Global Variables
 ***************************************************************/

int CurrentShape = LINE;           /* Maintains the status of the shape
				    * the user is drawing. Default is
				    * to draw with a line.             */

int PenWidth = 3;                  /* Maintains the current pen width.
				    * Default width is medium.         */

COLORREF PenColor = RGB(255,0,0);  /* Maintains the current pen color.
				    * Default color is red.            */


/*
 * Structure definition to track
 * what shapes have been drawn.
 */
struct SSHAPE {
	RECT     Points;           /* Location of the shape.           */
	int      PenWidth,         /* Pen width for the shape.         */
		 theShape;         /* Shape this structure represents. */
	COLORREF PenColor;         /* Color of the shape.              */
	int      Slope;            /* Used to determine direction lines
				    * should be drawn. If slope > 0 then
				    * draw from UpperLeft to LowerRight.
				    * Else draw from LowerLeft
				    * to UpperRight.                   */
};

typedef struct SSHAPE SHAPE;

#define nPoints 100
SHAPE thisShape[nPoints];          /* Array that stores the shapes
				    * the user draws.                 */
int CurrentPoint = -1;             /* Indicates the number of shapes
				    * the user has drawn.             */

/**********************************************************
 * function WinMain
 *    Standard WinMain function.
 **********************************************************/
#pragma argsused
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
		   LPSTR lpszCmdLine, int nCmdShow )
{
    WNDCLASS    wndClass;
    MSG         msg;
    HWND        hWnd;

    /*
     * Register window class style if first
     * instance of this program.
     */
    if ( !hPrevInstance )
    {
	wndClass.style          = CS_HREDRAW | CS_VREDRAW ;
	wndClass.lpfnWndProc    = WndProc;
	wndClass.cbClsExtra     = 0;
	wndClass.cbWndExtra     = 0;
	wndClass.hInstance      = hInstance;
	wndClass.hIcon          = LoadIcon(hInstance, "IDI_SIMPLEPAINT");
	wndClass.hCursor        = LoadCursor(NULL, IDC_ARROW );
	wndClass.hbrBackground  = GetStockObject(WHITE_BRUSH );
	wndClass.lpszMenuName   = szAppName;
	wndClass.lpszClassName  = szAppName;

    if (!RegisterClass(&wndClass))
	return FALSE;
    }

    /*
     * Create and display the window.
     */
    hWnd = CreateWindow(szAppName,"Simple Paint",
			WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,0,
			CW_USEDEFAULT,0,NULL,NULL,hInstance,NULL);

    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);

    while (GetMessage(&msg, NULL, 0, 0))
    {
	TranslateMessage(&msg );
	DispatchMessage(&msg );
    }
    return 0;
}

/*******************************************************
 * function WndProc
 *    Handles all messages received by the window
 *******************************************************/

LRESULT CALLBACK WndProc (HWND hWnd, UINT Message,
			 WPARAM wParam, LPARAM lParam)
{
    switch(Message)
    {
	case WM_COMMAND:
	    return DoWMCommand(wParam, hWnd);

	case WM_LBUTTONDOWN:
	    DoLButtonDown(hWnd,lParam);
	    break;

	case WM_LBUTTONUP:
	    DoLButtonUp(hWnd,lParam);
	    break;

	case WM_MOUSEMOVE:
	    DoMouseMove(hWnd,lParam);
	    break;

	case WM_PAINT:
	    DoPaint(hWnd);
	    break;

	case WM_DESTROY:
	    PostQuitMessage(0);
	    break;

	default:
	    return DefWindowProc(hWnd,Message,wParam,lParam);
    }
    return 0;
}

/*****************************************************************
 * function DrawShape
 *
 *    Draws the shape given by Shape parameter using PenWidth
 *    and PenColor in the rectangle bounded by x,y,x2,y2.  The
 *    Slope parameter is used with line shapes to determine if
 *    lines should be drawn with a negative or positive slope.
 *****************************************************************/

void DrawShape(HDC hdc, int x, int y, int x2, int y2, int Shape,
	       int PenWidth, COLORREF PenColor, int Slope)
{
    HANDLE      saveObject;

    /*
     * Create the proper pen for this shape.  Save the
     * previously selected object from this DC.
     */
    saveObject = SelectObject(hdc, CreatePen(PS_SOLID,
					     PenWidth,
					     PenColor));
    switch(Shape)
    {
	case LINE:
	    /*
	     *  Rectangles that save a shape's position must be stored
	     *  as upper-left and lower-right.  To draw a line from
	     *  upper-right to lower-left, the line must have a
	     *  negative slope.
	     */
	    if (Slope > 0)
	    {
		MoveTo(hdc,x,y);
		LineTo(hdc,x2,y2);
	    }
	    else
	    {
		MoveTo(hdc,x,y2);
		LineTo(hdc,x2,y);
	    }
	    break;

	case ELLIPSE:
	    Ellipse(hdc,x,y,x2,y2);
	    break;

	case RECTANGLE:
	    Rectangle(hdc,x,y,x2,y2);
	    break;
    }
    /*
     *  Delete the currently selected object (the pen) and select
     *  whatever object was currently selected when we entered
     *  this routine.
     */
    DeleteObject(SelectObject(hdc,saveObject));
}

/****************************************************************
 * function DoPaint
 *    Processes WM_PAINT messages.  WM_PAINT is generated
 *    whenever UpdateWindow is called or another window is moved,
 *    revealing a portion of the window receiving this message.
 ****************************************************************/

void DoPaint(HWND hWnd)
{
    int         i;

    HDC         hdc,
		hMemDC;

    RECT        theRect,
		destRect;

    HBITMAP     theBitmap;
    PAINTSTRUCT ps;

    hdc = BeginPaint(hWnd,&ps);

    if (CurrentPoint >= 0)
    {
	/*
	 * Determine which rectangle on the window is invalid.
	 * If no rectangle is marked invalid, it will be a full
	 * window repaint.
	 */
	GetUpdateRect(hWnd,&theRect,0);
	if (IsRectEmpty(&theRect))
	    GetClientRect(hWnd,&theRect);

	/*
	 * Create a memory DC and bitmap the same
	 * size as the update rectangle.
	 */
	hMemDC = CreateCompatibleDC(hdc);
	theBitmap = CreateCompatibleBitmap(hdc,
					   theRect.right-theRect.left,
					   theRect.bottom-theRect.top);
	SelectObject(hMemDC,theBitmap);

	/*
	 * Erase the memBitmap.
	 */
	BitBlt(hMemDC, 0, 0,
	       theRect.right-theRect.left,
	       theRect.bottom-theRect.top,
	       hdc, 0, 0, SRCCOPY);

	/*
	 * Draw only those shapes that lie
	 * within the update rectangle.
	 */
	for (i = 0; i <= CurrentPoint; ++i)
	{
	    IntersectRect(&destRect, &thisShape[i].Points, &theRect);
	    if (!IsRectEmpty(&destRect))
		DrawShape(hMemDC,
			  thisShape[i].Points.left-theRect.left,
			  thisShape[i].Points.top-theRect.top,
			  thisShape[i].Points.right-theRect.left,
			  thisShape[i].Points.bottom-theRect.top,
			  thisShape[i].theShape,thisShape[i].PenWidth,
			  thisShape[i].PenColor,thisShape[i].Slope);
		/*
		 * Note that when drawing the shape, the shape's
		 * position was transformed so that the origin was
		 * at the upper-left corner of the update rectangle.
		 * This is the point (0,0) on the bitmap that will
		 * map onto (theRect.left,theRect.right).
		 */
	}

	/*
	 * Finally, copy the bitmap onto the update rectangle.
	 */
	BitBlt(hdc, theRect.left, theRect.top,
	       theRect.right-theRect.left,
	       theRect.bottom-theRect.top,
	       hMemDC, 0, 0, SRCCOPY);

	DeleteDC(hMemDC);
	DeleteObject(theBitmap);
    }

    EndPaint(hWnd,&ps);
}


/**********************************************************
 * static variables oldx, oldy, mouseDown
 *    Used to maintain both the state of the mouse position
 *    and the button status between mouse messages.
 **********************************************************/

static  oldx = -1, oldy = -1, mouseDown = 0;

/******************************************************************
 * function DoLButtonDown
 *    When the left button on the mouse is pressed, this routine
 *    saves the origin of this shape, the current pen parameters,
 *    and the current shape into the shapes array.  The mouse
 *    button is also marked as pressed.
 ******************************************************************/

void DoLButtonDown(HWND hWnd, LONG lParam)
{
    /*
     * Redirect all subsequent mouse movements to this
     * window until the mouse button is released.
     */
    SetCapture(hWnd);
    oldy = thisShape[++CurrentPoint].Points.top = HIWORD(lParam);
    oldx = thisShape[CurrentPoint].Points.left = LOWORD(lParam);
    thisShape[CurrentPoint].theShape = CurrentShape;
    thisShape[CurrentPoint].PenWidth = PenWidth;
    thisShape[CurrentPoint].PenColor = PenColor;

    mouseDown = 1;

}

/******************************************************************
 * function DoLButtonUp
 *    When the Left mouse button is released, this routine
 *    allows other windows to receive mouse messages and saves
 *    the position of the mouse as the other corner of a bounding
 *    rectangle for the shape.
 ******************************************************************/

void DoLButtonUp(HWND hWnd, LONG lParam)
{
    if (mouseDown == 0) return;
    ReleaseCapture();

    /*
     * For rectangles to work with the IntersectRect function,
     * they must be stored as left, top, right, bottom.
     */
    SetRect(&thisShape[CurrentPoint].Points,
	    min(thisShape[CurrentPoint].Points.left, LOWORD(lParam)),
	    min(thisShape[CurrentPoint].Points.top, HIWORD(lParam)),
	    max(thisShape[CurrentPoint].Points.left, LOWORD(lParam)),
	    max(thisShape[CurrentPoint].Points.top, HIWORD(lParam)));

    /*
     * if the origin of the line has changed, it should be drawn
     * from upper-right to lower left and therefore has negative
     * slope.  Otherwise it will have positive slope.
     */
    if (CurrentShape == LINE)
	thisShape[CurrentPoint].Slope =
	    thisShape[CurrentPoint].Points.left ==
	    LOWORD(lParam) ||
	    thisShape[CurrentPoint].Points.top ==
	    HIWORD(lParam) ?
	    -1 : 1;

    /*
     * Mark this region on the window as
     * needing redrawing and force an update.
     */
    InvalidateRect(hWnd, &thisShape[CurrentPoint].Points, 0);
    UpdateWindow(hWnd);
    mouseDown = 0;
    oldx = -1;
    oldy = -1;
}

static int SaveROP;

/**********************************************************************
 * function DoMouseMove
 *    When the mouse is moved and the button is down, this function
 *    draws the current shape.  It uses the Raster Operation NOTXORPEN
 *    to draw the shape.  When this mode is used, drawing the
 *    same image twice returns the image to its original state.
 *    NOTXORPEN turns black on black white, black on white black
 *    and white on white white.
 **********************************************************************/

void DoMouseMove(HWND hWnd,LONG lParam)
{
    HDC hdc;

    if (mouseDown)
    {
	hdc = GetDC(hWnd);
	/*
	 * Erase the old shape.
	 */
	SaveROP = SetROP2(hdc,R2_NOTXORPEN);
	DrawShape(hdc, thisShape[CurrentPoint].Points.left,
		  thisShape[CurrentPoint].Points.top, oldx, oldy,
		  thisShape[CurrentPoint].theShape,
		  thisShape[CurrentPoint].PenWidth,
		  thisShape[CurrentPoint].PenColor, 1);
	/*
	 * At this point, the slope must be positive because
	 * the coordinates could not have been switched.
	 * The next step is to draw the new shape.
	 */

	oldx = LOWORD(lParam);
	oldy = HIWORD(lParam);
	DrawShape(hdc, thisShape[CurrentPoint].Points.left,
		  thisShape[CurrentPoint].Points.top, oldx, oldy,
		  thisShape[CurrentPoint].theShape,
		  thisShape[CurrentPoint].PenWidth,
		  thisShape[CurrentPoint].PenColor, 1);
	SetROP2(hdc,SaveROP);
	ReleaseDC(hWnd,hdc);
    }

}

/*********************************************************************
 * function DoWMCommand
 *    When a menu item is selected, this function changes the current
 *    state of shape selections to match the user's menu selection.
 *******************************************************************/

int DoWMCommand(WORD wParam, HWND hWnd)
{
    switch(wParam)
    {
	case MID_QUIT:
	    DestroyWindow(hWnd);
	    break;

	case MID_LINE:
	    CurrentShape = LINE;
	    break;

	case MID_ELLIPSE:
	    CurrentShape = ELLIPSE;
	    break;

	case MID_RECTANGLE:
	    CurrentShape = RECTANGLE;
	    break;

	case MID_THIN:
	    PenWidth = 1;
	    break;

	case MID_REGULAR:
	    PenWidth = 3;
	    break;

	case MID_THICK:
	    PenWidth = 5;
	    break;

	case MID_RED:
	    PenColor = RGB(255,0,0);
	    break;

	case MID_GREEN:
	    PenColor = RGB(0,255,0);
	    break;

	case MID_BLACK:
	    PenColor = RGB(0,0,0);
	    break;

	default:
	    return 0;
    }

    return 1;
}
