/******************************************************************************/ /* T_POLY.CPP: IMPLEMENTATION OF THE CPolygonTool CLASS */ /* */ /* */ /******************************************************************************/ /* */ /* Methods in this file */ /* */ /* Polygon Tool Class Object */ /* CPolygonTool::CPolygonTool */ /* CPolygonTool::~CPolygonTool */ /* CPolygonTool::DeleteArrayContents */ /* CPolygonTool::AdjustBoundingRect */ /* CPolygonTool::CopyPointsToMemArray */ /* CPolygonTool::AddPoint */ /* CPolygonTool::SetCurrentPoint */ /* CPolygonTool::RenderInProgress */ /* CPolygonTool::RenderFinal */ /* CPolygonTool::SetupPenBrush */ /* CPolygonTool::AdjustPointsForConstraint */ /* CPolygonTool::PreProcessPoints */ /* CPolygonTool::Render */ /* CPolygonTool::OnStartDrag */ /* CPolygonTool::OnEndDrag */ /* CPolygonTool::OnDrag */ /* CPolygonTool::OnCancel */ /* CPolygonTool::CanEndMultiptOperation */ /* CPolygonTool::EndMultiptOperation */ /******************************************************************************/ /* */ /* Briefly, This object stores the points of the polygon in a CObArray of */ /* CPoint Objects. For the in progress drawing, it calls PolyLine. When the */ /* polygon is closed or completed (by the user doubleclicking => asking us to */ /* close it), Polygon is called on the same points. */ /* */ /* The last point in the array of points is always the point the current line */ /* is being drawn to. The first time 2 points are added (the Anchor/first */ /* point, and the point the line is being drawn to) It does happen that this */ /* first time, they are the same point. It is necessary that the first time */ /* 2 points are added, since subsequent times, new points are not added, but */ /* the last point is reset. */ /* */ /******************************************************************************/ #include "stdafx.h" #include "global.h" #include "pbrush.h" #include "pbrusdoc.h" #include "pbrusfrm.h" #include "bmobject.h" #include "imgsuprt.h" #include "imgwnd.h" #include "imgwell.h" #include "t_poly.h" #ifdef _DEBUG #undef THIS_FILE static CHAR BASED_CODE THIS_FILE[] = __FILE__; #endif IMPLEMENT_DYNAMIC(CPolygonTool, CClosedFormTool) #include "memtrace.h" extern CLineTool NEAR g_lineTool; CPolygonTool NEAR g_polygonTool; extern MTI NEAR mti; /******************************************************************************/ CPolygonTool::CPolygonTool() { m_nCmdID = IDMB_POLYGONTOOL; m_cRectBounding.SetRectEmpty(); m_bMultPtOpInProgress = FALSE; m_nStrokeWidth = 1; } /******************************************************************************/ CPolygonTool::~CPolygonTool() { m_cRectBounding.SetRectEmpty(); DeleteArrayContents(); } /******************************************************************************/ /* delete all cpoint objects allocated and stored in the array */ /* also free any memory associated with the array */ void CPolygonTool::DeleteArrayContents(void) { int iSize = (int)m_cObArrayPoints.GetSize(); CPoint *pcPoint; for (int i = 0; i < iSize; i++) { pcPoint= (CPoint *)m_cObArrayPoints.GetAt( i ); delete pcPoint; } m_cObArrayPoints.RemoveAll(); } /******************************************************************************/ /* recalculate the bounding rectangle for the polyline/polygon */ void CPolygonTool::AdjustBoundingRect(void) { int iSize = (int)m_cObArrayPoints.GetSize(); CPoint *pcPoint; int iStrokeWidth = GetStrokeWidth(); int i; if (iSize >= 1) { pcPoint= (CPoint*)m_cObArrayPoints.GetAt( 0 ); //set the rect to equal the 1st value m_cRectBounding.SetRect(pcPoint->x, pcPoint->y, pcPoint->x, pcPoint->y); } for (i = 1; i < iSize; i++) { pcPoint = (CPoint *)m_cObArrayPoints.GetAt( i ); m_cRectBounding.SetRect( min( pcPoint->x, m_cRectBounding.left ), min( pcPoint->y, m_cRectBounding.top ), max( pcPoint->x, m_cRectBounding.right ), max( pcPoint->y, m_cRectBounding.bottom ) ); } // Adjust for width of current drawing line/border m_cRectBounding.OffsetRect ( -(iStrokeWidth / 2), -(iStrokeWidth / 2) ); m_cRectBounding.InflateRect( iStrokeWidth , iStrokeWidth); } /******************************************************************************/ /* This method will copy the CObArray structure of CPoints to a contiguous */ /* memory block of CPoint Structures */ BOOL CPolygonTool::CopyPointsToMemArray(CPoint **pcPoint, int *piNumElements) { BOOL bRC = TRUE; int i; int iSize = (int)m_cObArrayPoints.GetSize(); if (! iSize) { *piNumElements = 0; *pcPoint = NULL; return TRUE; } TRY { *pcPoint = new CPoint[iSize]; if (*pcPoint == NULL) { AfxThrowMemoryException(); } for (i=0; i < iSize; i++) { (*pcPoint)[i] = *((CPoint*) (m_cObArrayPoints[i])); } *piNumElements = iSize; } CATCH(CMemoryException,e) { *piNumElements = 0; bRC = FALSE; } END_CATCH return bRC; } /******************************************************************************/ /* This routine can Throw a CMemoryException!! */ /* It adds a new point to the end of the array, possibly increasing the size */ void CPolygonTool::AddPoint(POINT ptNewPoint) { CPoint *pcPoint; pcPoint = new CPoint(ptNewPoint); if (pcPoint == NULL) { AfxThrowMemoryException(); } m_cObArrayPoints.Add((CObject *)pcPoint); AdjustBoundingRect(); } /******************************************************************************/ /* This method changes the value of the last point in the array. It does not */ /* remove the point and add a new one. It just modifies it in place */ void CPolygonTool::SetCurrentPoint(POINT ptNewPoint) { int iLast = (int)m_cObArrayPoints.GetUpperBound(); if (iLast >= 0) { CPoint *pcPoint = (CPoint *) m_cObArrayPoints[iLast]; pcPoint->x = ptNewPoint.x; pcPoint->y = ptNewPoint.y; AdjustBoundingRect(); } } /******************************************************************************/ /* Render In Progress is called for all drawing during the multi-pt operation */ /* The only difference between this method and RenderFinal is that it calls */ /* polyline and RenderFinal calls polygon. */ void CPolygonTool::RenderInProgress(CDC* pDC) { CPoint *pcPointArray = 0; int iNumElements; if (CopyPointsToMemArray( &pcPointArray, &iNumElements ) && pcPointArray != NULL) { pDC->Polyline(pcPointArray, iNumElements); delete [] pcPointArray; } } /******************************************************************************/ /* Render Final is called at the end of the multi-pt drawing mode. The only */ /* difference between this method and RenderInProgress is that it calls */ /* polygon and RenderInProgress calls polyline. */ void CPolygonTool::RenderFinal(CDC* pDC) { CPoint *pcPointArray = 0; int iNumElements; if (CopyPointsToMemArray(&pcPointArray, &iNumElements) && pcPointArray != NULL) { // Remove RIP with only 2 points if (iNumElements > 2) pDC->Polygon(pcPointArray, iNumElements); delete [] pcPointArray; } } /******************************************************************************/ /* This routine is called before rendering onto the DC. It basically, calls */ /* the default setup to setup the pen and brush, and then overrides the Pen if*/ /* drawing in progress and drawing without any border. This case is necessary*/ /* since if you do not have a border, you need to see something during the in */ /* progress drawing mode. It uses the inverse (not) of the screen color as */ /* the border in this mode. */ BOOL CPolygonTool::SetupPenBrush(HDC hDC, BOOL bLeftButton, BOOL bSetup, BOOL bCtrlDown) { static int iOldROP2Code; static BOOL bCurrentlySetup = FALSE; BOOL bRC = CClosedFormTool::SetupPenBrush(hDC, bLeftButton, bSetup, bCtrlDown); // for multipt operations in progress (e.g. drawing outline, not fill yet // if there is no border, use the not of the screen color for the border. // When bMultiptopinprogress == FALSE, final drawing, we will use a null // pen and thus have no border. if (m_bMultPtOpInProgress) { if (bSetup) { if (! bCurrentlySetup) { bCurrentlySetup = TRUE; // if no border, draw inprogress border as inverse of screen color if (! m_bBorder) iOldROP2Code = SetROP2(hDC, R2_NOT); } else // Error: Will lose allocated Brush/Pen bRC = FALSE; } else { if (bCurrentlySetup) { bCurrentlySetup = FALSE; // if no border, restore drawing mode if (! m_bBorder) SetROP2(hDC, iOldROP2Code); } else // Error: Cannot Free/cleanup Brush/Pen -- Never allocated. bRC = FALSE; } } return bRC; } /******************************************************************************/ /* Call the line's adjustpointsforconstraint member function */ void CPolygonTool::AdjustPointsForConstraint(MTI *pmti) { g_lineTool.AdjustPointsForConstraint(pmti); } /******************************************************************************/ // ptDown must be anchor point for our line, not where we did mouse button down void CPolygonTool::PreProcessPoints(MTI *pmti) { int iLast = (int)m_cObArrayPoints.GetUpperBound(); if (iLast > 0) iLast--; CPoint* pcPoint; if (iLast >= 0) { pcPoint = (CPoint *)m_cObArrayPoints[iLast]; pmti->ptDown = *pcPoint; } CClosedFormTool::PreProcessPoints(pmti); } /******************************************************************************/ /* Render sets up the pen and brush, and then calls either RenderInProgress */ /* or RenderFinal. RenderInProgress is called if in the middle of a multipt */ /* operation, and RenderFinal is called when a multipt operation is complete */ /* The pen and brush is set up exactly the same as the parent routine in */ /* CRubberTool */ void CPolygonTool::Render(CDC* pDC, CRect& rect, BOOL bDraw, BOOL bCommit, BOOL bCtrlDown) { // Setup Pen/Brush SetupPenBrush(pDC->m_hDC, bDraw, TRUE, bCtrlDown); if (m_bMultPtOpInProgress) { RenderInProgress(pDC); } else { RenderFinal(pDC); } // Cleanup Pen/Brush SetupPenBrush(pDC->m_hDC, bDraw, FALSE, bCtrlDown); // Need to return the bounding rect rect = m_cRectBounding; } /******************************************************************************/ void CPolygonTool::OnActivate( BOOL bActivate ) { if (! bActivate && m_bMultPtOpInProgress) { if (m_pImgWnd != NULL) if (m_cObArrayPoints.GetSize() > 1) { OnStartDrag( m_pImgWnd, &m_MTI ); OnEndDrag ( m_pImgWnd, &m_MTI ); m_MTI.ptPrev = m_MTI.pt; EndMultiptOperation(); // end the multipt operation OnEndDrag( m_pImgWnd, &m_MTI ); mti.fLeft = FALSE; mti.fRight = FALSE; } else OnCancel( m_pImgWnd ); else EndMultiptOperation( TRUE ); } m_pImgWnd = NULL; if (bActivate) { m_nStrokeWidth = g_nStrokeWidth; } else { g_nStrokeWidth = m_nStrokeWidth; } CImgTool::OnActivate( bActivate ); } /******************************************************************************/ void CPolygonTool::OnEnter( CImgWnd* pImgWnd, MTI* pmti ) { m_pImgWnd = NULL; } /******************************************************************************/ void CPolygonTool::OnLeave( CImgWnd* pImgWnd, MTI* pmti ) { m_pImgWnd = pImgWnd; } /******************************************************************************/ /* On Start Drag is called on mouse button down. We basically call on Start */ /* Drag of the parent (default) class after adding in our point(s) into the */ /* array of points. If this is the first point (i.e. bMultiptOpInProgress == */ /* False, then we need 2 points in our array, and we can call the default */ /* OnStartDrag. If it is not the first point, then we just add the new point */ /* and call our OnDrag. In either case, OnDrag is called which eventually */ /* calls render to do our drawing on the mouse down */ void CPolygonTool::OnStartDrag( CImgWnd* pImgWnd, MTI* pmti ) { TRY { if (m_bMultPtOpInProgress) { CRect rect; CPoint pt = pmti->pt; pImgWnd->ImageToClient( pt ); pImgWnd->GetClientRect( &rect ); if (rect.PtInRect( pt )) { AddPoint( pmti->pt ); OnDrag( pImgWnd, pmti ); } } else { DeleteArrayContents(); m_cRectBounding.SetRectEmpty(); AddPoint( pmti->pt ); // must set m_bmultptopinprogress prior to calling onstartdrag // since that calls render,and render will call renderinprogress // or renderfinal depending on the sate of this variable. m_bMultPtOpInProgress = TRUE; // No Mult Pt In Progress => 1st Click // // add a 2nd point, last point is what we are draing to // 1st point is anchor. 1st time, need 2 points to draw a line // subsequent times, just re-use last point as anchor and only one // more point is added (above outside test for m_bmultptopinprogress) AddPoint( pmti->pt ); CClosedFormTool::OnStartDrag( pImgWnd, pmti ); } } CATCH(CMemoryException,e) { } END_CATCH } /******************************************************************************/ /* On End Drag is sent on a mouse button up. This basically is a clone of the*/ /* CRubberTool::OnEndDrag method, except that we use our bounding rect for all*/ /* the image invalidation, and commit, and undo function calls. */ /* if we are in the middle of a multipoint operation, we do not want to call */ /* all the routines to fix the drawing (e.g. invalImgRect, CommitImgRect, */ /* FinishUndo). We just want to save the current point, render, and return */ void CPolygonTool::OnEndDrag( CImgWnd* pImgWnd, MTI* pmti ) { PreProcessPoints(pmti); SetCurrentPoint(pmti->pt); if (m_bMultPtOpInProgress) { m_MTI = *pmti; // can't call OnDrag for this object/class, since it calls preprocesspt // again, and then onDrag. If you call preproces again, you will lose // bounding rectange box prev, and not be able to invalidate / repaint // Still have to invalidate bounding rect, since if rect is larger than // current rect, must invalidate to paint. E.g. If let off shift, then // let off button, end point would be adjusted and bouning rect would // also be correct, but rect calculated in CClosedFormTool::OnDrag is // incorrect. InvalImgRect(pImgWnd->m_pImg, &m_cRectBounding); CClosedFormTool::OnDrag(pImgWnd, pmti); return; } if (! m_cObArrayPoints.GetSize()) return; OnDrag(pImgWnd, pmti); // one last time to refresh display in prep for final render Render(CDC::FromHandle(pImgWnd->m_pImg->hDC), m_cRectBounding, pmti->fLeft, TRUE, pmti->fCtrlDown); InvalImgRect(pImgWnd->m_pImg, &m_cRectBounding); CommitImgRect(pImgWnd->m_pImg, &m_cRectBounding); pImgWnd->FinishUndo(m_cRectBounding); ClearStatusBarSize(); CImgTool::OnEndDrag(pImgWnd, pmti); } /******************************************************************************/ /* On Drag is sent when the mouse is moved with the button down. We basically*/ /* save the current point, and call the base class processing. Since the base*/ /* class processing invalidates the rect on the screen and cleans it up so we */ /* can paint a new line, we have to adjust the previous rectangle to be the */ /* bounding rectangle of our polyline. If we did not do this, our previous */ /* drawing would not get erased, and we would be drawing our new line over */ /* part of the previous line. The default processing finally calls Render */ /* which since our render is virtual, will call our render method above. */ void CPolygonTool::OnDrag( CImgWnd* pImgWnd, MTI* pmti ) { PreProcessPoints ( pmti ); SetCurrentPoint ( pmti->pt ); SetStatusBarPosition( pmti->pt ); SetStatusBarSize ( m_cRectBounding.Size() ); CClosedFormTool::OnDrag(pImgWnd, pmti); } /******************************************************************************/ /* On Cancel is sent when the user aborts an operation while in progress */ /* EndMultiptOperation with TRUE will do all our cleanup */ void CPolygonTool::OnCancel(CImgWnd* pImgWnd) { InvalImgRect( pImgWnd->m_pImg, &m_cRectBounding ); EndMultiptOperation(TRUE); CClosedFormTool::OnCancel(pImgWnd); } /******************************************************************************/ /* If point is on 1st point (i.e. closes the polygon) then can end is true */ // Use the stroke width to determine the width of the line and whether the */ /* end point touches the beginning point because of the line thickness */ BOOL CPolygonTool::CanEndMultiptOperation(MTI* pmti ) { CPoint *pcPoint = (CPoint *) m_cObArrayPoints[0]; CSize cSizeDiff = (*pcPoint) - pmti->pt; int iStrokeWidth = GetStrokeWidth() * 2; m_bMultPtOpInProgress = ! ((abs( cSizeDiff.cx ) <= iStrokeWidth) && (abs( cSizeDiff.cy ) <= iStrokeWidth)); return ( TRUE ); } /******************************************************************************/ /* If bAbort is true, this means an error occurred, or the user cancelled the */ /* multipoint operation in the middle of it. We need to clean up the */ /* allocated memory in our array of points. */ void CPolygonTool::EndMultiptOperation( BOOL bAbort ) { if (bAbort) { DeleteArrayContents(); } CClosedFormTool::EndMultiptOperation(); } /******************************************************************************/ void CPolygonTool::OnUpdateColors( CImgWnd* pImgWnd ) { if (m_cObArrayPoints.GetSize() && m_bMultPtOpInProgress) { OnStartDrag( pImgWnd, &m_MTI ); OnEndDrag ( pImgWnd, &m_MTI ); } } /******************************************************************************/