/*************************
  rubberBanUtil.cpp
  last revised 31 MAR 02
  by Lon Kelly
*************************/
// You've got to include StdAfx.h to avoid the C1010 error if you're using 
// automatic precompiled headers (default project setting). I found this fact
// frustrating and difficult to figure out. Typical Microsoft:
// do it their way or suffer. 

#include "StdAfx.h"         // MFC core and standard components

#include "rubberBandUtil.h"

//local utility routines
namespace {
	int minOf3(int a, int b, int c)
	{
		int result;
		result = (a<b) ? a : b;
		result = (result<c) ? result : c;
		return result;
	}
	
	int maxOf3(int a, int b, int c)
	{
		int result;
		result = (a>b) ? a : b;
		result = (result>c) ? result : c;
		return result;
	}
}

void trackRubberLine(CView* pView, CPoint start, CPoint *finish)
{
	CRect viewRect;
	pView->GetClientRect(&viewRect);

	int maxX = viewRect.Width();
	int maxY = viewRect.Height();

	int baseX = start.x;
	int baseY = start.y;
	int mouseX = start.x;
	int mouseY = start.y;
	int updateMinX = baseX;
	int updateMaxX = updateMinX;
	int updateMinY = baseY;
	int updateMaxY = updateMinY;

	//create a device context for the onscreen view
	CClientDC dcWnd(pView);
	
	//create a compatible bitmap from the onscreen view
	CBitmap bitmapOld;
	bitmapOld.CreateCompatibleBitmap(&dcWnd,maxX,maxY);

	//create a memory device context and copy in
	//the original onscreen bitmap
	CDC dcMemOld;
	dcMemOld.CreateCompatibleDC(&dcWnd);
	dcMemOld.SelectObject(&bitmapOld);
	dcMemOld.BitBlt(0,0,maxX,maxY,&dcWnd,0,0,SRCCOPY);

	//create a memory device context to draw our rubber line into
	CDC dcMemNew;
	dcMemNew.CreateCompatibleDC(&dcWnd);
	CBitmap bitmapNew;
	bitmapNew.CreateCompatibleBitmap(&dcWnd,maxX,maxY);
	dcMemNew.SelectObject(&bitmapNew);
	
	// capture the mouse and process messages
	pView->SetCapture();
	ASSERT(pView == CWnd::GetCapture());
	bool done = false;
	int prevMouseX = mouseX;
	int prevMouseY = mouseY;
	while (!done) {
		MSG msg;
		VERIFY(::GetMessage(&msg, NULL, 0, 0));

		if (CWnd::GetCapture() != pView)
			break;

		switch (msg.message) {
			case WM_LBUTTONUP:
				//put back the old view contents
				dcWnd.BitBlt(0,0,maxX,maxY,&dcMemOld,0,0,SRCCOPY);
				done = true;
				break;
			
			case WM_MOUSEMOVE:
				prevMouseX = mouseX;
				prevMouseY = mouseY;
				mouseX = (int)(short)LOWORD(msg.lParam);
				mouseY = (int)(short)HIWORD(msg.lParam);
				
				//calculate the update bounds, which needs to be inflated one pixel
				//do avoid little bits of line being left around on the view
				updateMinX = minOf3(baseX,prevMouseX,mouseX);
				if (updateMinX>0) updateMinX -= 1;
				updateMaxX = maxOf3(baseX,prevMouseX,mouseX);
				if (updateMaxX<maxX) updateMaxX += 1;
				updateMinY = minOf3(baseY,prevMouseY,mouseY);
				if (updateMinY>0) updateMinY -=1;
				updateMaxY = maxOf3(baseY,prevMouseY,mouseY);
				if (updateMaxY<maxY) updateMaxY +=1;

				//copy in the original bits to the memory device context
				dcMemNew.BitBlt(updateMinX,updateMinY,updateMaxX-updateMinX,updateMaxY-updateMinY,
					&dcMemOld,updateMinX,updateMinY,SRCCOPY);
				//draw line in memory device context
				dcMemNew.MoveTo(baseX,baseY);
				dcMemNew.LineTo(mouseX,mouseY);
				
				//copy the updated area to the onscreen view
				dcWnd.BitBlt(updateMinX,updateMinY,updateMaxX-updateMinX,updateMaxY-updateMinY,
					&dcMemNew,updateMinX,updateMinY,SRCCOPY);
			
				break;

			// handle cancel messages
			case WM_KEYDOWN:
			case WM_RBUTTONDOWN:
				dcWnd.BitBlt(0,0,maxX,maxY,&dcMemOld,0,0,SRCCOPY);
				done = true;
				break;

			default:
				DispatchMessage(&msg);
				break;
		}
	}
	ReleaseCapture();
	finish->x = mouseX;
	finish->y = mouseY;
}
