/******************************************************************************/ /* T_CURVE.CPP: IMPLEMENTATION OF THE CCurveTool CLASS */ /* */ /* */ /******************************************************************************/ /* */ /* Methods in this file */ /* */ /* CCurve Tool Class Object */ /* CCurveTool::CCurveTool */ /* CCurveTool::~CCurveTool */ /* CCurveTool::AdjustBoundingRect */ /* CCurveTool::AddPoint */ /* CCurveTool::SetCurrentPoint */ /* CCurveTool::DrawCurve */ /* CCurveTool::AdjustPointsForConstraint */ /* CCurveTool::PreProcessPoints */ /* CCurveTool::Render */ /* CCurveTool::OnStartDrag */ /* CCurveTool::OnEndDrag */ /* CCurveTool::OnDrag */ /* CCurveTool::OnCancel */ /* CCurveTool::CanEndMultiptOperation */ /* CCurveTool::EndMultiptOperation */ /******************************************************************************/ /* */ /* Briefly, this Object draws a curve from 4 (currently) points. It generates */ /* a list of points which are placed in the array, and then calls polyline */ /* to draw line segments to build a curve. */ /* */ /* The array is divided into 2 pieces. The first piece is the anchor points, */ /* the 2nd piece is the array of points which will be passed to polyline. */ /* The anchor points are placed in the array in the following order */ /* 2,3,4,...1. See the addpoint method below for info on this order. */ /* */ /******************************************************************************/ #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 "pbrusvw.h" #include "t_curve.h" #ifdef _DEBUG #undef THIS_FILE static CHAR BASED_CODE THIS_FILE[] = __FILE__; #endif IMPLEMENT_DYNAMIC(CCurveTool, CRubberTool) #include "memtrace.h" extern CLineTool NEAR g_lineTool; CCurveTool NEAR g_curveTool; /******************************************************************************/ CCurveTool::CCurveTool() { m_nCmdID = IDMB_CURVETOOL; m_iNumAnchorPoints = 0; m_cRectBounding.SetRectEmpty(); } /******************************************************************************/ CCurveTool::~CCurveTool() { m_cRectBounding.SetRectEmpty(); } /******************************************************************************/ /* recalculate the bounding rectangle for the polyline/curve */ void CCurveTool::AdjustBoundingRect(void) { int iStrokeWidth = GetStrokeWidth(); int i; if (m_iNumAnchorPoints >= 1) { //set the rect to equal the 1st value m_cRectBounding.SetRect(m_PolyPoints[0].x, m_PolyPoints[0].y, m_PolyPoints[0].x, m_PolyPoints[0].y); } for (i=1; i < m_iNumAnchorPoints; i++) { m_cRectBounding.SetRect( min(m_PolyPoints[i].x, m_cRectBounding.left), min(m_PolyPoints[i].y, m_cRectBounding.top), max(m_PolyPoints[i].x, m_cRectBounding.right), max(m_PolyPoints[i].y, m_cRectBounding.bottom)); } // Adjust rectangle for Windows GDI (Non-inclusive right/bottom) m_cRectBounding.bottom++; m_cRectBounding.right++; // Adjust for width of current drawing line/border m_cRectBounding.OffsetRect(-(iStrokeWidth/2),-(iStrokeWidth/2)); m_cRectBounding.InflateRect(iStrokeWidth, iStrokeWidth); } /******************************************************************************/ // This method adds a new point into the array and increases the number of // anchor points currently in the array. If there are no points in the // array, it adds a point to the 1st position (index 0). If there are any // points currently in the array, it copies the last point to the new // location, and then adds the new point where the old last point was (1 point // before the last point. The 1st point added is always the last point in the // array, and the 2nd point added is always the 1st point. // The order of the points in the array are: 2,3,4,....,1 void CCurveTool::AddPoint(POINT ptNewPoint) { BOOL bRC = TRUE; if (m_iNumAnchorPoints == 0) { m_PolyPoints[m_iNumAnchorPoints] = ptNewPoint; m_iNumAnchorPoints++; } else { if (m_iNumAnchorPoints < MAX_ANCHOR_POINTS) { m_PolyPoints[m_iNumAnchorPoints] = m_PolyPoints[m_iNumAnchorPoints-1]; m_PolyPoints[m_iNumAnchorPoints-1] = ptNewPoint; m_iNumAnchorPoints++; } } AdjustBoundingRect(); } /******************************************************************************/ // This method changes the value of the last point in the array. If there are // 2 points, then it modifies the 2nd point, knowing that when there are only // 2 points, we are draing a straight line (between 2 points). If there are // more than 2 points, it modified the second to last point in the array, // which is actually the last point dropped/placed. See above for expl of // order of points in the array. void CCurveTool::SetCurrentPoint(POINT ptNewPoint) { if (m_iNumAnchorPoints == 2) { m_PolyPoints[m_iNumAnchorPoints-1] = ptNewPoint; } else { if (m_iNumAnchorPoints > 2) { m_PolyPoints[m_iNumAnchorPoints-2] = ptNewPoint; } } AdjustBoundingRect(); } /******************************************************************************/ BOOL CCurveTool::DrawCurve(CDC* pDC) { POINT ptCurve[MAX_ANCHOR_POINTS]; UINT uPoints = m_iNumAnchorPoints; int i; for (i=uPoints-1; i>=0; --i) { ptCurve[i] = m_PolyPoints[i]; } // HACK: PolyBezier cannot handle 3 points, so repeat the middle point if (uPoints == 3) { ptCurve[3] = ptCurve[2]; ptCurve[2] = ptCurve[1]; uPoints = 4; } PolyBezier(pDC->m_hDC, ptCurve, uPoints); return(TRUE); } /******************************************************************************/ /* Call the line's adjustpointsforconstraint member function */ /* only do this if there are 2 points (i.e. drawing a straight line */ void CCurveTool::AdjustPointsForConstraint(MTI *pmti) { if (m_iNumAnchorPoints == 2) { g_lineTool.AdjustPointsForConstraint(pmti); } } /******************************************************************************/ // ptDown must be anchor point for our line, not where we did mouse button down // on a subsequent point in the multipt operation void CCurveTool::PreProcessPoints(MTI *pmti) { pmti->ptDown = m_PolyPoints[0]; CRubberTool::PreProcessPoints(pmti); } /******************************************************************************/ /* Render sets up the pen and brush, and then calls either Render */ /* The pen and brush is set up exactly the same as the parent routine in */ /* CRubberTool. If there are only 2 points, do the standard line drawing */ /* using moveto and lineto, instead of trying to create a curve between 2 pts */ void CCurveTool::Render(CDC* pDC, CRect& rect, BOOL bDraw, BOOL bCommit, BOOL bCtrlDown) { // Setup Pen/Brush SetupPenBrush( pDC->m_hDC, bDraw, TRUE, bCtrlDown ); if (m_iNumAnchorPoints == 2) { pDC->MoveTo( m_PolyPoints[0].x, m_PolyPoints[0].y ); pDC->LineTo( m_PolyPoints[1].x, m_PolyPoints[1].y ); } else { if (m_iNumAnchorPoints > 2) { DrawCurve( pDC ); } } // Cleanup Pen/Brush SetupPenBrush( pDC->m_hDC, bDraw, FALSE, bCtrlDown ); // Need to return the bounding rect rect = m_cRectBounding; } void CCurveTool::OnActivate( BOOL bActivate ) { if (!bActivate && m_bMultPtOpInProgress) { CImgWnd* pImgWnd = ((CPBView*)((CFrameWnd*)AfxGetMainWnd())->GetActiveView())->m_pImgWnd; // Stolen from CPBView::OnEscape // I don't think this can ever be NULL, but just in case if (pImgWnd != NULL) { EndMultiptOperation( FALSE ); // end the multipt operation Render(CDC::FromHandle(pImgWnd->m_pImg->hDC), m_cRectBounding, TRUE, TRUE, FALSE); InvalImgRect(pImgWnd->m_pImg, &m_cRectBounding); CommitImgRect(pImgWnd->m_pImg, &m_cRectBounding); pImgWnd->FinishUndo(m_cRectBounding); ClearStatusBarSize(); } } CRubberTool::OnActivate( bActivate ); } /******************************************************************************/ /* 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 */ /* We only call the parent OnStartDrag the first time, because it does some */ /* setup which we do not want done each time */ void CCurveTool::OnStartDrag( CImgWnd* pImgWnd, MTI* pmti ) { if (m_bMultPtOpInProgress) { AddPoint(pmti->pt); OnDrag(pImgWnd, pmti); } else { // must reset numAnchorPoints before calling addpoint the 1st time. m_iNumAnchorPoints = 0; AddPoint(pmti->pt); 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 add 1 point in array of points. AddPoint(pmti->pt); CRubberTool::OnStartDrag(pImgWnd, pmti); } } /******************************************************************************/ /* 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 CCurveTool::OnEndDrag( CImgWnd* pImgWnd, MTI* pmti ) { PreProcessPoints(pmti); SetCurrentPoint(pmti->pt); if (m_bMultPtOpInProgress) { // 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 CRubberTool::OnDrag is // incorrect. InvalImgRect(pImgWnd->m_pImg, &m_cRectBounding); CRubberTool::OnDrag(pImgWnd, pmti); } else { 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 CCurveTool::OnDrag( CImgWnd* pImgWnd, MTI* pmti ) { PreProcessPoints(pmti); SetCurrentPoint(pmti->pt); CRubberTool::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 CCurveTool::OnCancel(CImgWnd* pImgWnd) { EndMultiptOperation(TRUE); CImgTool::OnCancel(pImgWnd); } /******************************************************************************/ /* we can only end if the number of maximum points was entered. We must stay */ /* in capture/multiptmode until we get EXACTLY the desired number of anchor */ /* points */ BOOL CCurveTool::CanEndMultiptOperation(MTI* pmti ) { if (m_iNumAnchorPoints == MAX_ANCHOR_POINTS) { m_bMultPtOpInProgress = FALSE; } else { m_bMultPtOpInProgress = TRUE; } return (CRubberTool::CanEndMultiptOperation(pmti)); } /******************************************************************************/ /* If bAbort is true, this means an error occurred, or the user cancelled the */ /* multipoint operation in the middle of it. We just set the num of anchor */ /* points to 0 to stop drawing and call the default endmultiptoperation */ void CCurveTool::EndMultiptOperation(BOOL bAbort) { if (bAbort) { m_iNumAnchorPoints = 0; m_cRectBounding.SetRectEmpty(); } CRubberTool::EndMultiptOperation(); }