// handles.c #include "handles.h" #include "annotate.h" #include #include #include extern HINSTANCE hInst; extern HCURSOR hCursorRotate; #define ALMOST_ZERO 0.9 /* small float value suitable for comparing coordinates */ #define DIFFERENT(val1,val2,epsilon) (((val1) < (val2)-(epsilon))||((val1) > (val2)+(epsilon))) #define CLOSE_TO_ZERO(n) (fabs(n) < 0.00001) L_INT ROUND(L_DOUBLE a) { return (L_INT)((a < 0) ? a - 0.5 : a + 0.5); } L_INT ISDIFFERENT(pANNPOINT pptAnn, LPPOINT ppt) { int bRet; bRet = DIFFERENT(pptAnn->x, ppt->x, ALMOST_ZERO) || DIFFERENT(pptAnn->y,ppt->y, ALMOST_ZERO); return bRet; } L_INT AnnSetID(HANNOBJECT hObject, L_INT nMainID, L_INT nPartID) { L_INT nRet; nRet = L_AnnSetTag(hObject, (nMainID<<0x10) + nPartID, 0); return nRet; } L_INT AnnGetID(HANNOBJECT hObject, L_INT *pnMainID, L_INT *pnPartID) { L_INT nRet; L_UINT32 uTag; nRet = L_AnnGetTag(hObject, &uTag); if (nRet == SUCCESS) { if (pnMainID) *pnMainID = (0xFFFF0000 & uTag) >> 0x10; if (pnPartID) *pnPartID = (0xFFFF & uTag); } return nRet; } //************************************************************************************** // // GetHandleInfo--returns the location(in client coordinates), nIndex of nHandleID // //************************************************************************************** typedef struct tag_HANDLEINFO { L_BOOL bFound; L_INT nHandleID; ANNHANDLEINFO AnnHandleInfo; } HANDLEINFO, *pHANDLEINFO; L_INT L_EXPORT EXT_CALLBACK HandleInfoCallback(HANNOBJECT hObject, pANNHANDLEINFO pAnnHandleInfo, L_VOID L_FAR * pUserData) { pHANDLEINFO pHandleInfo = (pHANDLEINFO)pUserData; // If we haven't already found the node, mark it has been found if (!pHandleInfo->bFound && (pHandleInfo->nHandleID == pAnnHandleInfo->nID)) { pHandleInfo->AnnHandleInfo = *pAnnHandleInfo; pHandleInfo->bFound = TRUE; } return SUCCESS_NOCHANGE; } L_BOOL GetHandleInfo(HANNOBJECT hObject, L_INT nHandleID, LPPOINT pptLocation, pANNPOINT pptLocationContainer, L_INT *pnIndex) { HANDLEINFO HandleInfo; memset(&HandleInfo, 0, sizeof(HANDLEINFO)); HandleInfo.nHandleID = nHandleID; HandleInfo.bFound = FALSE; L_AnnEnumerateHandles(hObject, HandleInfoCallback, &HandleInfo); if (HandleInfo.bFound) { if (pptLocation) { pptLocation->x = HandleInfo.AnnHandleInfo.ptLocationClient.x; pptLocation->y = HandleInfo.AnnHandleInfo.ptLocationClient.y; } if (pnIndex) { *pnIndex = HandleInfo.AnnHandleInfo.nIndex; } if(pptLocationContainer) { *pptLocationContainer = HandleInfo.AnnHandleInfo.ptLocationContainer; } } return HandleInfo.bFound; } //************************************************************************************** // // ChangeHandleProperties--Displays a dialog to change properties of all handles in an annotatio object // //************************************************************************************** COLORREF MyChooseColor(HWND hWnd, COLORREF crInit) { CHOOSECOLOR choose; static COLORREF crCustom[16]; memset(&choose, 0, sizeof(choose)); choose.lStructSize = sizeof(choose); choose.hwndOwner = hWnd; choose.hInstance = (HWND)hInst; choose.lpCustColors = crCustom; choose.Flags = CC_RGBINIT; choose.rgbResult = crInit; if(!ChooseColor(&choose)) return crInit; return choose.rgbResult; } #define IDS_STRING_DESCRIPTION TEXT("") \ TEXT("Use this dialog to change the properties of both default and user-defined handles.\r\n\r\n") \ TEXT("The custom annotations are made from one or more 'grouped' annotation objects. ") \ TEXT("Each grouped annotation object has default handles, which are initially hidden. ") \ TEXT("The visible handles are 'user-defined' handles.\r\n\r\n") \ TEXT("Use the 'Break All Groups' menu option to see how the objects are grouped together ") \ TEXT("to build the custom annotation. ") LRESULT CALLBACK ChangeNodeDlgProc (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { pCHANGEHANDLE pChangeHandle; pANNHANDLEINFO pHandleInfo; TCHAR szTemp[300]; switch (message) { case WM_INITDIALOG: { pChangeHandle = (pCHANGEHANDLE) lParam; pHandleInfo = pChangeHandle->pHandleInfo; SetWindowLong(hDlg, GWL_USERDATA, lParam); SetWindowPos(hDlg, NULL, pChangeHandle->rcRect.left, pChangeHandle->rcRect.top, 0, 0, SWP_NOOWNERZORDER | SWP_NOSIZE); SetDlgItemText(hDlg, IDC_STATIC_TYPE, pHandleInfo->nType == ANNHANDLETYPE_DEFAULT_HANDLE ? TEXT("Default") : TEXT("User-Defined")); SetDlgItemInt(hDlg, IDC_STATIC_ANNOTATION_INDEX, pChangeHandle->nAnnotationNumber, FALSE); SetDlgItemInt(hDlg, IDC_STATIC_INDEX, pHandleInfo->nIndex, FALSE); SetDlgItemInt(hDlg, IDC_STATIC_TOTAL, pHandleInfo->nTotalHandles, FALSE); SetDlgItemText(hDlg, IDC_STATIC_DESCRIPTION, IDS_STRING_DESCRIPTION); { // CONTAINER LOCATION L_CHAR szBuff[300] ; sprintf(szBuff, "(%4.1f, %4.1f)", pHandleInfo->ptLocationContainer.x, pHandleInfo->ptLocationContainer.y); #ifdef UNICODE MultiByteToWideChar ( CP_ACP, MB_PRECOMPOSED, szBuff, -1, szTemp, 300); #else strcpy(szTemp, szBuff) ; #endif } // CONTAINER LOCATION SetDlgItemText(hDlg, IDC_STATIC_CONTAINER_LOCATION, szTemp); wsprintf(szTemp, TEXT("(%d, %d)"), pHandleInfo->ptLocationClient.x, pHandleInfo->ptLocationClient.y); SetDlgItemText(hDlg, IDC_STATIC_SCREEN_LOCATION, szTemp); CheckDlgButton(hDlg, IDC_CHECK_VISIBLE, pHandleInfo->bVisible ? BST_CHECKED : BST_UNCHECKED); CheckDlgButton(hDlg, IDC_RADIO_SHAPE_CIRCLE, pHandleInfo->nShape == ANNHANDLE_SHAPE_CIRCLE ? BST_CHECKED : BST_UNCHECKED); CheckDlgButton(hDlg, IDC_RADIO_SHAPE_SQUARE, pHandleInfo->nShape == ANNHANDLE_SHAPE_SQUARE ? BST_CHECKED : BST_UNCHECKED); return (TRUE); } case WM_COMMAND: pChangeHandle = (pCHANGEHANDLE) GetWindowLong(hDlg, GWL_USERDATA); pHandleInfo = pChangeHandle->pHandleInfo; GetWindowRect(hDlg, &pChangeHandle->rcRect); switch (CTLID(wParam, lParam)) { case IDC_BUTTON_PEN_COLOR: pHandleInfo->crPen = MyChooseColor(hDlg, pHandleInfo->crPen); break; case IDC_BUTTON_FILL_COLOR: pHandleInfo->crFill = MyChooseColor(hDlg, pHandleInfo->crFill); break; case IDC_CHANGE: pHandleInfo->bVisible = IsDlgButtonChecked(hDlg, IDC_CHECK_VISIBLE) == BST_CHECKED; if (IsDlgButtonChecked(hDlg, IDC_RADIO_SHAPE_CIRCLE) == BST_CHECKED) pHandleInfo->nShape = ANNHANDLE_SHAPE_CIRCLE; else pHandleInfo->nShape = ANNHANDLE_SHAPE_SQUARE; EndDialog (hDlg, IDOK); return (TRUE); case IDC_QUIT: case IDCANCEL: case IDC_NO_CHANGE: EndDialog (hDlg, CTLID(wParam, lParam)); return (TRUE); } } return (FALSE); } L_INT L_EXPORT EXT_CALLBACK ChangeHandlePropertiesCallback(HANNOBJECT hObject, pANNHANDLEINFO pHandleInfo, L_VOID L_FAR * pUserData) { L_INT nRet; //char szMsg[400]; pCHANGEHANDLE pChangeHandle = (pCHANGEHANDLE)pUserData; pChangeHandle->pHandleInfo = pHandleInfo; // sprintf(szMsg, "Click YES to make change, NO to leave same\n\n%s\nnIndex[%d]\nContainer[%f, %f]\nScreen[%d, %d]\nbVisible[%d]\ncrPen[%0x]\ncrFill[%0x]\nnShape[%s]\nnTotalNodes[%d]", // pHandleInfo->nType == ANNHANDLETYPE_DEFAULT_HANDLE ? "ANNHANDLETYPE_DEFAULT_HANDLE" : "ANNHANDLETYPE_USER_HANDLE", // pHandleInfo->nIndex, // pHandleInfo->ptLocationContainer.x, // pHandleInfo->ptLocationContainer.y, // pHandleInfo->ptLocationClient.x, // pHandleInfo->ptLocationClient.y, // pHandleInfo->bVisible, // pHandleInfo->crPen, // pHandleInfo->crFill, // pHandleInfo->nShape == ANNHANDLE_SHAPE_SQUARE ? "Square" : "Circle", // pHandleInfo->nTotalHandles // ); nRet = DialogBoxParam (hInst, MAKEINTRESOURCE(IDD_ANN_HANDLE), pChangeHandle->hWnd, (DLGPROC)ChangeNodeDlgProc, (LPARAM)pChangeHandle); switch(nRet) { case IDC_CHANGE: nRet = SUCCESS_CHANGE; break; case IDC_NO_CHANGE: nRet = SUCCESS_NOCHANGE; break; case IDCANCEL: case IDC_QUIT: nRet = -1; // abort break; } return nRet; } L_INT ChangeHandleProperties(HWND hWnd, HANNOBJECT hObject, pCHANGEHANDLE pChangeHandle ) { return L_AnnEnumerateHandles(hObject, ChangeHandlePropertiesCallback, pChangeHandle); } //************************************************************************************** // // HideDefaultHandles--hides the default handles of an annotation object // //************************************************************************************* L_INT L_EXPORT EXT_CALLBACK HideDefaultHandlesCallback(HANNOBJECT hObject, pANNHANDLEINFO pHandleInfo, L_VOID L_FAR * pUserData) { if (pHandleInfo->nType == ANNHANDLETYPE_DEFAULT_HANDLE) { pHandleInfo->bVisible = FALSE; return SUCCESS_CHANGE; } else return SUCCESS_NOCHANGE; } L_INT HideDefaultHandles(HANNOBJECT hObject) { L_INT nRet; nRet = L_AnnEnumerateHandles(hObject, HideDefaultHandlesCallback, NULL); return nRet; } //************************************************************************************** // // AnnGetNeighborObjects--call this once with phAnnObject == NULL to get the count // then allocate the proper size for phAnnObject (count * sizeof(HANNOBJECT)) // ann call again // //************************************************************************************* typedef struct tagANNOBJECTLIST { L_INT nCount; pHANNOBJECT phList; } ANNOBJECTLIST, *pANNOBJECTLIST; L_INT L_EXPORT EXT_CALLBACK AnnNeighborObjectsCallback (HANNOBJECT hObject, L_INT L_FAR * pUserData) { pANNOBJECTLIST pAnnObjectList = (pANNOBJECTLIST)pUserData; if(pAnnObjectList) { if (pAnnObjectList->phList) { pAnnObjectList->phList[pAnnObjectList->nCount] = hObject; } pAnnObjectList->nCount++; } return SUCCESS; } L_INT AnnGetNeighborObjects(HANNOBJECT hObject, HANNOBJECT *phAnnObject, L_INT32 *pnCount) { L_INT nRet; HANNOBJECT hContainer; ANNOBJECTLIST AnnObjectList; nRet = L_AnnGetContainer(hObject, &hContainer); if (nRet != SUCCESS) return ERROR_INV_PARAMETER; AnnObjectList.nCount = 0; AnnObjectList.phList = phAnnObject; nRet = L_AnnEnumerate(hContainer, AnnNeighborObjectsCallback, &AnnObjectList, ANNFLAG_NOTTHIS, NULL); if (pnCount) *pnCount = AnnObjectList.nCount; return nRet; } //************************************************************************************** // // AnnSortNeighborObjects--sorts the array of HANNOBJECTS by tag (lowest to highest) // //************************************************************************************* L_VOID AnnSortNeighborObjects(HANNOBJECT hObject, HANNOBJECT AnnObjectNeighbors[], L_INT32 nCount) { L_INT nMinTag; L_INT nMinIndex; L_INT nTag; L_INT i,j; HANNOBJECT hTemp; // Do a selection sort to sort by tag-- for (i=0; i rcBounds.left) dMinX = rcBounds.left; if(dMaxX < rcBounds.right) dMaxX = rcBounds.right; if(dMinY > rcBounds.top) dMinY = rcBounds.top; if(dMaxY < rcBounds.bottom) dMaxY = rcBounds.bottom; } prcBounds->left = dMinX; prcBounds->right = dMaxX; prcBounds->top = dMinY; prcBounds->bottom = dMaxY; } L_VOID ObjectClipCursor(LPCHILDDATA pData, HANNOBJECT hObject, POINT ptStart, L_INT nType, L_BOOL bRestore) { L_INT32 nCount; ANNRECT arcBounds; RECT rcBounds; if (nType == OBJECT_CLIP_CURSOR_GROUP) { AnnGetNeighborObjects( hObject, pData->AnnObjectNeighbors, &nCount); AnnSortNeighborObjects(hObject, pData->AnnObjectNeighbors, nCount); GetBoundingBox(pData->AnnObjectNeighbors, nCount, &arcBounds); } else // OBJECT_CLIP_CURSOR_SINGLETON { L_AnnGetRect(hObject, &arcBounds, NULL); } L_AnnConvert(pData->hContainer, (LPPOINT)&rcBounds, (pANNPOINT)&arcBounds, 2, ANNCONVERT_TO_CLIENT); L_AnnRestrictCursor(pData->hContainer, &rcBounds, &ptStart, &pData->rcOldClip, FALSE); } // Returns two endpoints of line segment containing the handle // hRect: handle to an annotation rectangle object // nID: user handle that lies on a rectangle segment (CP0, CP1, CP2, CP3) // aptLine[2] -- returned--array of two points representing segment--container coordinates // // ----------CP2-------- // | | // CP0 CP1 // | | // ----------CP3-------- // L_VOID GetRectSegmentWithHandle(HANNOBJECT hRect, L_INT nID, ANNPOINT aptLine[]) { L_INT nMinDistance; L_DOUBLE dMinDistance; L_DOUBLE dDistance; L_INT i; ANNPOINT aptRect[4]; ANNPOINT aptID; ANNPOINT aptMidPoint; // Get points of rectangle in container coordinates L_AnnGetPoints(hRect, aptRect); GetHandleInfo(hRect, nID, NULL, &aptID, NULL); nMinDistance = 3; aptMidPoint.x = (aptRect[3].x + aptRect[0].x) / 2; aptMidPoint.y = (aptRect[3].y + aptRect[0].y) / 2; dMinDistance = MY_DISTANCE(aptMidPoint, aptID); for(i=0; i<3; i++) { aptMidPoint.x = (aptRect[i].x + aptRect[i+1].x) / 2; aptMidPoint.y = (aptRect[i].y + aptRect[i+1].y) / 2; dDistance = MY_DISTANCE(aptMidPoint, aptID); if (dDistance < dMinDistance) { dMinDistance = dDistance; nMinDistance = i; } } // aptID lies on line (aptRect[nMinDistance], aptRect[(nMinDistance+1) % 4]) aptLine[0] = aptRect[nMinDistance]; aptLine[1] = aptRect[(nMinDistance+1) % 4]; } // returns distance from point aptC to line aptLine // line and point are in container coordinates L_DOUBLE DistanceToLine(ANNPOINT aptLine[], ANNPOINT aptC) { L_DOUBLE dNumerator; L_DOUBLE dDistance; ANNPOINT aptA, aptB; //L_DOUBLE s; // s<0 C is left of AB // s>0 C is right of AB // s==0 C is on AB L_DOUBLE L; // length of segment AB aptA = aptLine[0]; aptB = aptLine[1]; // A is ptLine[0] // B is ptLine[1] // C is *pptMove // L is the length of segment AB // // (Ay-Cy)(Bx-Ax)-(Ax-Cx)(By-Ay) // s = ----------------------------- // L^2 // // distance = s * L L = sqrt( SQR(aptB.x - aptA.x) + SQR(aptB.y - aptA.y) ); dNumerator = (aptA.y - aptC.y)*(aptB.x - aptA.x) - (aptA.x - aptC.x)*(aptB.y - aptA.y); dDistance = dNumerator / L; return dDistance; } // Returns distance from ptA to the line that passes throught nID // nID: either HANDLE_ID_CP2 or HANDLE_ID_CP3 // ptA: point in client coordinates // a negative distance means that ptA has crossed the line that nID is on // // ----------CP2-------- // | | // CP0 CP1 // | | // ----------CP3-------- L_DOUBLE DistanceUserHandleToLine(HANNOBJECT hRect, L_INT nID, POINT ptA) { ANNPOINT aptLine[2]; HANNOBJECT hContainer; ANNPOINT aptA; L_DOUBLE dDistance; // Convert ptA to container coordinates L_AnnGetTopContainer(hRect, &hContainer); L_AnnConvert(hContainer, &ptA, &aptA, 1, ANNCONVERT_TO_CONTAINER); // Find the line segment that aptID is on GetRectSegmentWithHandle(hRect, nID, aptLine); // Find distance from aptA to aptLine dDistance = DistanceToLine(aptLine, aptA); return dDistance; } // Returns the distance (container coordinates) from a user handle on hRect, and a point ptMove(Client coordinates) L_DOUBLE DistanceToUserHandle(HANNOBJECT hRect, L_INT nID, POINT ptMove) { ANNPOINT aptMove; ANNPOINT aptID; HANNOBJECT hContainer; L_AnnGetTopContainer(hRect, &hContainer); GetHandleInfo(hRect, nID, NULL, &aptID, NULL); L_AnnConvert(hContainer, &ptMove, &aptMove, 1, ANNCONVERT_TO_CONTAINER); return MY_DISTANCE(aptMove, aptID); } // returns a point that is distance dDistance from center line AB (hLine) that is colinear with PC // all values are in container coordinates // this is used when resizing, and crossing the center // Input: // AB is hLine (rotate line) // C is ptC (current WM_MOUSEMOVE location) // dDistance is |QP| // AB is perpendicular to PC // Output: // Coordinates of Q // Q is colinear with CP // |QP| id dDistance // // // A // . // . // . // P // . // Q . // . // C B // ANNPOINT PointAtDistanceToLine(ANNPOINT aptLine[], ANNPOINT aptC, L_DOUBLE dDistance) { L_DOUBLE r; L_DOUBLE L_Squared; // distance squared |AB|^2 ANNPOINT aptA, aptB, aptP, aptQ; L_DOUBLE tQ; aptA = aptLine[0]; aptB = aptLine[1]; // Calculate coordinates of P // // (Cx-Ax)(Bx-Ax) + (Cy-Ay)(By-Ay) // r = ------------------------------- // L^2 // // Px = Ax + r(Bx-Ax) // Py = Ay + r(By-Ay) L_Squared = SQR(aptA.x - aptB.x) + SQR(aptA.y - aptB.y); r = (aptC.x - aptA.x)*(aptB.x - aptA.x) + (aptC.y - aptA.y)*(aptB.y - aptA.y); r = r / L_Squared; aptP.x = aptA.x + r *(aptB.x - aptA.x); aptP.y = aptA.y + r *(aptB.y - aptA.y); // Calculate Q // // Now we have the two points P and C // Calculate the parameterized equation of the line from P to C, letting t go from 0 to 1 // When t is (dDistance)/length(CP), this gives Q where // x(t) = (Cx-Px)*t + Px // y(t) = (Cy-Py)*t + Py tQ = dDistance / MY_DISTANCE(aptC, aptP); aptQ.x = (aptC.x - aptP.x) * tQ + aptP.x; aptQ.y = (aptC.y - aptP.y) * tQ + aptP.y; return aptQ; } // Returns a point that is dDistance from the segment (on hRect) containing handle nID // dDistance--container coordinates--sign determines which side of the line // returns ptMove--client coordinates POINT PointAtDistanceToOppositeLine(HANNOBJECT hRect, L_INT nID, L_DOUBLE dDistance, POINT ptMove) { ANNPOINT aptLine[2]; ANNPOINT aptMove; ANNPOINT aptNew; POINT ptNew; HANNOBJECT hContainer; // Find segment of rectangle that contains nID GetRectSegmentWithHandle(hRect, nID, aptLine); L_AnnGetTopContainer(hRect, &hContainer); L_AnnConvert(hContainer, &ptMove, &aptMove, 1, ANNCONVERT_TO_CONTAINER); aptNew = PointAtDistanceToLine(aptLine, aptMove, dDistance); L_AnnConvert(hContainer, &ptNew, &aptNew, 1, ANNCONVERT_TO_CLIENT); return ptNew; } // Returns an adjusted point that is along the line from handle nID to ptMove // The adjusted point is dDistance along the line (the angle remains unchanged) POINT PointAtDistanceToUserHandle(HANNOBJECT hRect, L_INT nID, L_DOUBLE dDistance, POINT ptMove) { ANNPOINT aptH, aptM; ANNPOINT aptNew; HANNOBJECT hContainer; L_DOUBLE t; POINT ptNew; GetHandleInfo(hRect, nID, NULL, &aptH, NULL); L_AnnGetTopContainer(hRect, &hContainer); L_AnnConvert(hContainer, &ptMove, &aptM, 1, ANNCONVERT_TO_CONTAINER); // Calculate aptNew // // We have the two points H (handle) and M (move) // Calculate the parameterized equation of the line from H to M, letting t go from 0 to 1 // When t is (dDistance)/length(HM), this gives aptNew where // x(t) = (Mx-Hx)*t + Hx // y(t) = (My-Hy)*t + Hy t = dDistance / MY_DISTANCE(aptM, aptH); aptNew.x = (aptM.x - aptH.x)*t + aptH.x; aptNew.y = (aptM.y - aptH.y)*t + aptH.y; L_AnnConvert(hContainer, &ptNew, &aptNew, 1, ANNCONVERT_TO_CLIENT); return ptNew; } // Swaps pApt0 with pApt1 L_VOID SwapAnnPoints(pANNPOINT pApt0, pANNPOINT pApt1) { ANNPOINT aptTemp; if (!pApt0 || !pApt1) return; aptTemp = *pApt0; *pApt0 = *pApt1; *pApt1 = aptTemp; } // Vector A is (12) // Vector B is (23) // return cos angle between A and B L_DOUBLE annDebugGetAngle(ANNPOINT apt1, ANNPOINT apt2, ANNPOINT apt3) { L_DOUBLE dA, dB; ANNPOINT a, b; L_DOUBLE dCosTheta; dA = MY_DISTANCE(apt1, apt2); dB = MY_DISTANCE(apt2, apt3); a.x = apt1.x - apt2.x; a.y = apt1.y - apt2.y; b.x = apt3.x - apt2.x; b.y = apt3.y - apt2.y; dCosTheta = (a.x*b.x + a.y*b.y) / (dA * dB); return dCosTheta; } ANNPOINT annDebugMidpoint(ANNPOINT apt1, ANNPOINT apt2) { ANNPOINT apt; apt.x = (apt1.x + apt2.x) / 2; apt.y = (apt1.y + apt2.y) / 2; return apt; } L_INT L_EXPORT EXT_CALLBACK AnnClearIDCallback (HANNOBJECT hObject, L_INT L_FAR * pUserData) { AnnSetID( hObject, 0, 0); return SUCCESS; } L_VOID AnnClearAllID(HANNOBJECT hContainer) { L_INT nRet; nRet = L_AnnEnumerate(hContainer, AnnClearIDCallback, NULL, ANNFLAG_NOTTHIS, NULL); }