mrDarker
2025-03-24 3628a707a38e1c590216c5983c9b97b9c742f86c
SourceCode/Bond/Servo/EqsGraphWnd.cpp
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,2400 @@
#include "stdafx.h"
#include "EqsGraphWnd.h"
#include "ColorTransfer.h"
#include "MapPosWnd.h"
#define INPIN      1
#define OUTPIN      2
#define ITEM_CX_SMALL   150
#define ITEM_CY_SMALL   90
#define ITEM_CX_NORMAL   250
#define ITEM_CY_NORMAL   150
#define ITEM_CX_LARGE   400
#define ITEM_CY_LARGE   240
#define HT_NOWHERE      0x1
#define HT_ITEM         0x2
#define HT_PIN         0x4
#define HT_LINE         0x8
#define PINWIDTH      8
#define PINHEIGHT      12
#define TIMER_FLASH            1
#define TIMER_ANIMATION_RECT   2
#define MAPPOSSIZE               150
#define MAPPOSWND_PADDING_RIGHT      12
#define MAPPOSWND_ID            1001
CEqsGraphWnd::CEqsGraphWnd()
{
   m_bUseGdiPlus = TRUE;
   m_hWnd = NULL;
   m_crFrame = GetSysColor(COLOR_WINDOWFRAME);
   m_crBkgnd = RGB(255, 255, 255);
   m_listener.onConnectPin = nullptr;
   m_listener.onCheckConnectPin = nullptr;
   m_listener.onDisconnectPin = nullptr;
   m_listener.onDeleteEqItem = nullptr;
   m_listener.onEqItemPosChanged = nullptr;
   m_listener.onDblckEqItem = nullptr;
   m_listener.onRclickEqItem = nullptr;
   m_crItemBackground[0] = RGB(218, 218, 218);
   m_crItemBackground[1] = RGB(193, 208, 227);
   m_crItemFrame[0] = RGB(128, 128, 128);
   m_crItemFrame[1] = RGB(147, 172, 206);
   m_crItemNameText[0] = RGB(0, 0, 0);
   m_crItemNameText[1] = RGB(0, 0, 0);
   m_crItemIdText[0] = CColorTransfer::ApproximateColor(m_crItemNameText[0], -0.3f);
   m_crItemIdText[1] = m_crItemIdText[0];
   m_nCurSel = -1;
   m_bMultiSelect = FALSE;
   m_nItemRound = 0;
   m_pCurItem = NULL;
   m_pCurPin = NULL;
   m_pSelLineOutPin = NULL;
   m_crPinBkgnd[0] = RGB(218, 218, 218);
   m_crPinBkgnd[1] = RGB(193, 0, 0);
   m_crPinBkgnd[2] = RGB(193, 0, 0);
   m_nStageCx = 4000;
   m_nStageCy = 3000;
   m_nOffsetX = 0;
   m_nOffsetY = 0;
   m_pFlashItem = NULL;
   m_nFlashCount = 0;
   m_hWndMapPos = NULL;
   m_bEnableScroll = FALSE;
   m_nMagneticLinHoz = 0;
   m_nMagneticLinVer = 0;
   m_hFontTitle = nullptr;
}
CEqsGraphWnd::~CEqsGraphWnd()
{
   ReleaseAllItems();
}
BOOL CEqsGraphWnd::RegisterWndClass()
{
   WNDCLASS wc;
   wc.lpszClassName = EQSGRAPHWND_CLASS;
   wc.hInstance = AfxGetInstanceHandle();
   wc.lpfnWndProc = WindowProc;
   wc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
   wc.hIcon = 0;
   wc.lpszMenuName = NULL;
   wc.hbrBackground = NULL;
   wc.style = CS_GLOBALCLASS | CS_DBLCLKS;
   wc.cbClsExtra = 0;
   wc.cbWndExtra = 0;
   // æ³¨å†Œçª—口类
   return (::RegisterClass(&wc) != 0);
}
CEqsGraphWnd* CEqsGraphWnd::FromHandle(HWND hWnd)
{
   CEqsGraphWnd* pEqsGraphWnd = (CEqsGraphWnd*)::GetProp(hWnd, EQSGRAPHWND_TAG);
   return pEqsGraphWnd;
}
CEqsGraphWnd* CEqsGraphWnd::Hook(HWND hWnd)
{
   CEqsGraphWnd* pEqsGraphWnd = (CEqsGraphWnd*)GetProp(hWnd, EQSGRAPHWND_TAG);
   if (pEqsGraphWnd == NULL) {
      pEqsGraphWnd = new CEqsGraphWnd();
      pEqsGraphWnd->m_hWnd = hWnd;
      SetProp(hWnd, EQSGRAPHWND_TAG, (HANDLE)pEqsGraphWnd);
   }
   return pEqsGraphWnd;
}
void CEqsGraphWnd::InitFont()
{
   HDC hDC = GetDC(NULL);
   HFONT hFont = (HFONT)::GetStockObject(DEFAULT_GUI_FONT);
   {
      LOGFONT lf = { 0 };
      ::GetObject(hFont, sizeof(LOGFONT), &lf);
      m_hFontName = CreateFontIndirect(&lf);
   }
   {
      LOGFONT lf = { 0 };
      ::GetObject(hFont, sizeof(LOGFONT), &lf);
      int nSize = int(-lf.lfHeight * 72.0 / GetDeviceCaps(hDC, LOGPIXELSY) + 0.5);
      lf.lfHeight = MulDiv(0 - (nSize - 2), GetDeviceCaps(hDC, LOGPIXELSX), 72);
      m_hFontId = CreateFontIndirect(&lf);
   }
   ::ReleaseDC(NULL, hDC);
}
void CEqsGraphWnd::SetItemRound(int nRound)
{
   m_nItemRound = nRound;
}
void CEqsGraphWnd::SetDefaultItemBackgroundColor(COLORREF crNormal, COLORREF crSel)
{
   m_crItemBackground[0] = crNormal;
   m_crItemBackground[1] = crSel;
}
void CEqsGraphWnd::SetDefaultItemFrameColor(COLORREF crNormal, COLORREF crSel)
{
   m_crItemFrame[0] = crNormal;
   m_crItemFrame[1] = crSel;
}
void CEqsGraphWnd::SetDefaultItemTextColor(COLORREF crNormal, COLORREF crSel)
{
   m_crItemNameText[0] = crNormal;
   m_crItemNameText[1] = crSel;
   m_crItemIdText[0] = CColorTransfer::ApproximateColor(m_crItemNameText[0], -0.3f);
   m_crItemIdText[1] = CColorTransfer::ApproximateColor(m_crItemNameText[1], -0.3f);
}
void CEqsGraphWnd::EnableScroll(BOOL bEnable)
{
   m_bEnableScroll = bEnable;
}
void CEqsGraphWnd::EnableMultiSelect()
{
   m_bMultiSelect = TRUE;
}
void CEqsGraphWnd::Init()
{
   InitFont();
   CalculateScollbar();
   long style = GetWindowLong(m_hWnd, GWL_STYLE);
   SetWindowLong(m_hWnd, GWL_STYLE, style | WS_CLIPCHILDREN);
   // MapPosWnd
   if (m_hWndMapPos == NULL) {
      m_hWndMapPos = CreateWindowEx(0, MAPPOSWND_CLASS,
         NULL, WS_CHILD | WS_VISIBLE,
         0, 0, 400, 400,
         m_hWnd, (HMENU)MAPPOSWND_ID, NULL, NULL);
      long styleex = GetWindowLong(m_hWndMapPos, GWL_EXSTYLE);
      SetWindowLong(m_hWndMapPos, GWL_EXSTYLE, styleex | WS_EX_CLIENTEDGE);
      CMapPosWnd *pMapPosWnd = CMapPosWnd::FromHandle(m_hWndMapPos);
      pMapPosWnd->SetWndMaxSize(MAPPOSSIZE);
      pMapPosWnd->SetStageSize(m_nStageCx, m_nStageCy, TRUE);
   }
}
void CEqsGraphWnd::CalculateMapPos()
{
   CRect rcClient;
   GetClientRect(m_hWnd, &rcClient);
   ::OffsetRect(&rcClient, m_nOffsetX, m_nOffsetY);
   CMapPosWnd *pMapPosWnd = CMapPosWnd::FromHandle(m_hWndMapPos);
   pMapPosWnd->SetViewPort(&rcClient, TRUE);
}
void CEqsGraphWnd::CalculateScollbar()
{
   RECT rcClient;
   GetClientRect(m_hWnd, &rcClient);
   // vert scroll
   if(m_bEnableScroll) {
      SCROLLINFO scrinffo;
      scrinffo.cbSize = sizeof(SCROLLINFO);
      scrinffo.fMask = SIF_ALL;
      scrinffo.nMax = m_nStageCy;
      scrinffo.nMin = 0;
      scrinffo.nPos = m_nOffsetY;
      scrinffo.nTrackPos = 0;
      scrinffo.nPage = rcClient.bottom - rcClient.top;
      SetScrollInfo(m_hWnd, SB_VERT, &scrinffo, TRUE);
   }
   // horz scroll
   if (m_bEnableScroll) {
      SCROLLINFO scrinffo;
      scrinffo.cbSize = sizeof(SCROLLINFO);
      scrinffo.fMask = SIF_ALL;
      scrinffo.nMax = m_nStageCx;
      scrinffo.nMin = 0;
      scrinffo.nPos = m_nOffsetX;
      scrinffo.nTrackPos = 0;
      scrinffo.nPage = rcClient.right - rcClient.left;
      SetScrollInfo(m_hWnd, SB_HORZ, &scrinffo, TRUE);
   }
}
/*
 * è®¡ç®—磁力线位置
 */
void CEqsGraphWnd::CalculateMagneticLine(EQITEM* pItem, LPRECT lprcItemRect, int &hoz, int &ver)
{
   hoz = 0;
   ver = 0;
#define MAGNETIC_DIS      10
   // æ£€æµ‹æ˜¯å¦æŽ¥è¿‘或对齐
   for (int i = 0; i < m_arItem.GetSize(); i++) {
      EQITEM *pTemp = (EQITEM*)m_arItem.GetAt(i);
      if (pTemp != pItem) {
         if (abs(lprcItemRect->left - pTemp->rect.left) < MAGNETIC_DIS) {
            ver = pTemp->rect.left;
            break;
         }
         else if (abs(lprcItemRect->right - pTemp->rect.right) < MAGNETIC_DIS) {
            ver = pTemp->rect.right - (lprcItemRect->right- lprcItemRect->left);
            break;
         }
      }
   }
   for (int i = 0; i < m_arItem.GetSize(); i++) {
      EQITEM* pTemp = (EQITEM*)m_arItem.GetAt(i);
      if (pTemp != pItem) {
         if (abs(lprcItemRect->top - pTemp->rect.top) < MAGNETIC_DIS) {
            hoz = pTemp->rect.top;
            break;
         }
         else if (abs(lprcItemRect->bottom - pTemp->rect.bottom) < MAGNETIC_DIS) {
            hoz = pTemp->rect.bottom - (lprcItemRect->bottom - lprcItemRect->top);
            break;
         }
      }
   }
}
void CEqsGraphWnd::Release()
{
   ::DeleteObject(m_hFontName);
   ::DeleteObject(m_hFontId);
   if (m_hFontTitle != nullptr) {
      ::DeleteObject(m_hFontTitle);
   }
   // delete
   delete this;
}
/*
 * å–å¾—In Pin的区域
 * pItem -- EQITEM
 * lpRect -- å¾—到的Rect
 * è¿”回是否成功
 */
BOOL CEqsGraphWnd::GetItemRect(EQITEM* pItem, LPRECT lpRect)
{
   ASSERT(pItem);
   if (pItem == m_pAnimationItem) {
      lpRect->left = (int)(m_rcAnimation.left - m_nOffsetX);
      lpRect->top = (int)(m_rcAnimation.top - m_nOffsetY);
      lpRect->right = (int)(m_rcAnimation.right - m_nOffsetX);
      lpRect->bottom = (int)(m_rcAnimation.bottom - m_nOffsetY);
   }
   else {
      lpRect->left = (int)(pItem->rect.left - m_nOffsetX);
      lpRect->top = (int)(pItem->rect.top - m_nOffsetY);
      lpRect->right = (int)(pItem->rect.right - m_nOffsetX);
      lpRect->bottom = (int)(pItem->rect.bottom - m_nOffsetY);
   }
   return TRUE;
}
BOOL CEqsGraphWnd::GetItemWarperRect(EQITEM* pItem, LPRECT lpRect)
{
   CopyRect(lpRect, &pItem->rect);
   lpRect->left -= PINWIDTH;
   lpRect->right += PINWIDTH;
   return TRUE;
}
/*
 * å–å¾—In Pin的区域
 * pItem -- EQITEM
 * nPinIndex -- in pin索引
 * lpRect -- å¾—到的Rect
 * è¿”回是否成功
 */
BOOL CEqsGraphWnd::GetInPinRect(EQITEM* pItem, int nPinIndex, LPRECT lpRect)
{
   CPtrArray * pPins = (CPtrArray *)pItem->pInPins;
   if (nPinIndex >= pPins->GetSize()) {
      return FALSE;
   }
   int nBottomMargin = pPins->GetCount() >= 4 ? 8 : 0;
   int nSpace = ((pItem->rect.bottom - nBottomMargin - pItem->rect.top) - (int)pPins->GetSize() * PINHEIGHT) / (pPins->GetSize() + 1);
   lpRect->right = pItem->rect.left+1 - m_nOffsetX;
   lpRect->left = lpRect->right - PINWIDTH;
   lpRect->bottom = pItem->rect.top + (nSpace + PINHEIGHT) * (nPinIndex+1) - m_nOffsetY;
   lpRect->top = lpRect->bottom - PINHEIGHT;
   return TRUE;
}
/*
 * å–å¾—Out Pin的区域
 * pItem -- EQITEM
 * nPinIndex -- in pin索引
 * lpRect -- å¾—到的Rect
 * è¿”回是否成功
 */
BOOL CEqsGraphWnd::GetOutPinRect(EQITEM* pItem, int nPinIndex, LPRECT lpRect)
{
   CPtrArray * pPins = (CPtrArray *)pItem->pOutPins;
   if (nPinIndex >= pPins->GetSize()) {
      return FALSE;
   }
   int nSpace = ((pItem->rect.bottom - pItem->rect.top) - (int)pPins->GetSize() * PINHEIGHT) / (pPins->GetSize() + 1);
   lpRect->left = pItem->rect.right-1 - m_nOffsetX;
   lpRect->right = lpRect->left + PINWIDTH;
   lpRect->bottom = pItem->rect.top + (nSpace + PINHEIGHT) * (nPinIndex + 1) - m_nOffsetY;
   lpRect->top = lpRect->bottom - PINHEIGHT;
   return TRUE;
}
/*
 * å–å¾—Pin的Point
 * pItem -- EQITEM
 * nPinIndex -- in pin索引
 * lpRect -- å¾—到的Rect
 * è¿”回是否成功
 */
BOOL CEqsGraphWnd::GetPinPoint(PIN *pPin, LPPOINT lpPoint)
{
   ASSERT(pPin);
   ASSERT(pPin->pItem);
   CPtrArray * pPins;
   RECT rcPin;
   // in pin?
   pPins = (CPtrArray *)pPin->pItem->pInPins;
   for(int i=0; i<pPins->GetCount(); i++) {
      if (pPins->GetAt(i) == pPin) {
         if (GetInPinRect(pPin->pItem, i, &rcPin)) {
            lpPoint->x = rcPin.left + (rcPin.right - rcPin.left) / 2;
            lpPoint->y = rcPin.top + (rcPin.bottom - rcPin.top) / 2;
            return TRUE;
         }
      }
   }
   // out pin?
   pPins = (CPtrArray *)pPin->pItem->pOutPins;
   for (int i = 0; i<pPins->GetCount(); i++) {
      if (pPins->GetAt(i) == pPin) {
         if (GetOutPinRect(pPin->pItem, i, &rcPin)) {
            lpPoint->x = rcPin.left + (rcPin.right - rcPin.left) / 2;
            lpPoint->y = rcPin.top + (rcPin.bottom - rcPin.top) / 2;
            return TRUE;
         }
      }
   }
   return FALSE;
}
void CEqsGraphWnd::ReleaseItem(EQITEM* pItem)
{
   ASSERT(pItem);
   CPtrArray *pArray = (CPtrArray *)pItem->pInPins;
   for (int j = 0; j < pArray->GetSize(); j++) {
      PIN *pPin = (PIN *)pArray->GetAt(j);
      if (pPin->pConnectedPin != NULL) {
         pPin->pConnectedPin->pConnectedPin = NULL;
      }
      delete pPin;
   }
   delete pArray;
   pArray = (CPtrArray *)pItem->pOutPins;
   for (int j = 0; j < pArray->GetSize(); j++) {
      PIN *pPin = (PIN *)pArray->GetAt(j);
      if (pPin->pConnectedPin != NULL) {
         pPin->pConnectedPin->pConnectedPin = NULL;
      }
      delete pPin;
   }
   delete pArray;
   delete pItem;
}
void CEqsGraphWnd::ReleaseAllItems()
{
   for (int i = 0; i < m_arItem.GetCount(); i++) {
      ReleaseItem((EQITEM*)m_arItem.GetAt(i));
   }
   m_arItem.RemoveAll();
}
int CEqsGraphWnd::GetPinState(PIN *pPin)
{
   if (pPin == m_pCurPin) {
      return 1;
   }
   return 0;
}
/*
 * æ¸…空PIN连接线缓存点,以便重新计算和绘制
 */
void CEqsGraphWnd::ClearConnectedLinePoint(EQITEM*& pItem)
{
   ASSERT(pItem);
   CPtrArray *pPins;
   PIN *pPin;
   pPins = (CPtrArray *)pItem->pInPins;
   for (int j = 0; j < pPins->GetSize(); j++) {
      pPin = (PIN *)pPins->GetAt(j);
      if (pPin->pConnectedPin != NULL) {
         pPin->pConnectedPin->nLinePtCount = 0;
      }
   }
   pPins = (CPtrArray *)pItem->pOutPins;
   for (int j = 0; j < pPins->GetSize(); j++) {
      pPin = (PIN *)pPins->GetAt(j);
      pPin->nLinePtCount = 0;
   }
}
void CEqsGraphWnd::SetOnListener(EqsGraphListener& listener)
{
   m_listener.onConnectPin = listener.onConnectPin;
   m_listener.onCheckConnectPin = listener.onCheckConnectPin;
   m_listener.onDisconnectPin = listener.onDisconnectPin;
   m_listener.onDeleteEqItem = listener.onDeleteEqItem;
   m_listener.onEqItemPosChanged = listener.onEqItemPosChanged;
   m_listener.onDblckEqItem = listener.onDblckEqItem;
   m_listener.onRclickEqItem = listener.onRclickEqItem;
}
BOOL CEqsGraphWnd::SetCurSel(int nSel)
{
   if (!(nSel == -1 || nSel < m_arItem.GetCount())) {
      return FALSE;
   }
   m_nCurSel = nSel;
   RECT rcClient;
   GetClientRect(m_hWnd, &rcClient);
   ::InvalidateRect(m_hWnd, &rcClient, TRUE);
   return TRUE;
}
BOOL CEqsGraphWnd::SetCurSel(CString strItemName)
{
   int nIndex = -1;
   for (int i = 0; i < m_arItem.GetCount(); i++) {
      EQITEM* pItem = (EQITEM*)m_arItem.GetAt(i);
      if (strItemName.Compare(pItem->text) == 0) {
         nIndex = i;
         break;
      }
   }
   if (nIndex == -1) {
      return FALSE;
   }
   m_nCurSel = nIndex;
   RECT rcClient;
   GetClientRect(m_hWnd, &rcClient);
   ::InvalidateRect(m_hWnd, &rcClient, TRUE);
   return TRUE;
}
BOOL CEqsGraphWnd::SetCurSel(DWORD_PTR pData)
{
   int nIndex = -1;
   for (int i = 0; i < m_arItem.GetCount(); i++) {
      EQITEM* pItem = (EQITEM*)m_arItem.GetAt(i);
      if (pItem->pData == pData) {
         nIndex = i;
         break;
      }
   }
   if (nIndex == -1) {
      return FALSE;
   }
   m_nCurSel = nIndex;
   RECT rcClient;
   GetClientRect(m_hWnd, &rcClient);
   ::InvalidateRect(m_hWnd, &rcClient, TRUE);
   return TRUE;
}
/*
 * nType: ITEM_SMALL, ITEM_NORMAL or ITEM_LARGE
 */
EQITEM* CEqsGraphWnd::AddItem(int id, CString strText, DWORD_PTR dwData, int nType/* = ITEM_NORMAL*/)
{
   // éœ€è¦è®¡ç®—一个新位置,不然全部重叠在一起
   int x, y;
   x = (m_arItem.GetCount() % 4) * 218;
   y = (m_arItem.GetCount() / 4) * 168;
   EQITEM* pItem = new EQITEM;
   memset(pItem, 0, sizeof(EQITEM));
   pItem->id = id;
   pItem->nShowType = nType;
   pItem->rect.left = x + 20 + m_nOffsetX;
   pItem->rect.top = y + 50 + m_nOffsetY;
   if (pItem->nShowType == ITEM_SMALL) {
      pItem->rect.right = pItem->rect.left + ITEM_CX_SMALL;
      pItem->rect.bottom = pItem->rect.top + ITEM_CY_SMALL;
   }
   else if(pItem->nShowType == ITEM_LARGE){
      pItem->rect.right = pItem->rect.left + ITEM_CX_LARGE;
      pItem->rect.bottom = pItem->rect.top + ITEM_CY_LARGE;
   }
   else {
      pItem->rect.right = pItem->rect.left + ITEM_CX_NORMAL;
      pItem->rect.bottom = pItem->rect.top + ITEM_CY_NORMAL;
   }
   pItem->pData = dwData;
   pItem->pInPins = (DWORD_PTR)new CPtrArray();
   pItem->pOutPins = (DWORD_PTR)new CPtrArray();
   int len = min(63, strText.GetLength());
   memcpy(pItem->text, (LPTSTR)(LPCTSTR)strText, len);
   pItem->text[len] = '\0';
   m_arItem.Add(pItem);
   RECT rcClient;
   GetClientRect(m_hWnd, &rcClient);
   ::InvalidateRect(m_hWnd, &rcClient, TRUE);
   return pItem;
}
void CEqsGraphWnd::RemoveItem(EQITEM* pItem)
{
   BOOL bChanged = FALSE;
   if (m_listener.onDeleteEqItem != NULL) {
      if (m_listener.onDeleteEqItem(m_pCurItem)) {
         bChanged = DeleteItem(m_pCurItem) >= 0;
      }
   }
   if (bChanged) {
      if (pItem == m_pCurItem) {
         m_pCurItem = NULL;
      }
      RECT rcClient;
      GetClientRect(m_hWnd, &rcClient);
      ::InvalidateRect(m_hWnd, &rcClient, TRUE);
   }
}
void CEqsGraphWnd::SetItemText(EQITEM* pItem, CString strText)
{
   int len = min(63, strText.GetLength());
   memcpy(pItem->text, (LPTSTR)(LPCTSTR)strText, len);
   pItem->text[len] = '\0';
   ::InvalidateRect(m_hWnd, &pItem->rect, TRUE);
}
void CEqsGraphWnd::SetItemType(EQITEM* pItem, int nType)
{
   pItem->nShowType = nType;
   if (pItem->nShowType == ITEM_SMALL) {
      pItem->rect.right = pItem->rect.left + ITEM_CX_SMALL;
      pItem->rect.bottom = pItem->rect.top + ITEM_CY_SMALL;
   }
   else if (pItem->nShowType == ITEM_LARGE) {
      pItem->rect.right = pItem->rect.left + ITEM_CX_LARGE;
      pItem->rect.bottom = pItem->rect.top + ITEM_CY_LARGE;
   }
   else {
      pItem->rect.right = pItem->rect.left + ITEM_CX_NORMAL;
      pItem->rect.bottom = pItem->rect.top + ITEM_CY_NORMAL;
   }
   ::InvalidateRect(m_hWnd, &pItem->rect, TRUE);
}
void CEqsGraphWnd::SetItemPos(EQITEM* pItem, int x, int y)
{
   ASSERT(pItem);
   pItem->rect.left = x;
   pItem->rect.top = y;
   if (pItem->nShowType == ITEM_SMALL) {
      pItem->rect.right = pItem->rect.left + ITEM_CX_SMALL;
      pItem->rect.bottom = pItem->rect.top + ITEM_CY_SMALL;
   }
   else if (pItem->nShowType == ITEM_LARGE) {
      pItem->rect.right = pItem->rect.left + ITEM_CX_LARGE;
      pItem->rect.bottom = pItem->rect.top + ITEM_CY_LARGE;
   }
   else {
      pItem->rect.right = pItem->rect.left + ITEM_CX_NORMAL;
      pItem->rect.bottom = pItem->rect.top + ITEM_CY_NORMAL;
   }
   ::InvalidateRect(m_hWnd, &pItem->rect, TRUE);
}
PIN * CEqsGraphWnd::AddPin(EQITEM* pItem, int nType, CString strName, DWORD_PTR dwData)
{
   ASSERT(pItem);
   ASSERT(nType == INPIN || nType == OUTPIN);
   ASSERT(pItem->pInPins);
   ASSERT(pItem->pOutPins);
   PIN *pPin = new PIN;
   memset(pPin, 0, sizeof(PIN));
   pPin->pItem = pItem;
   pPin->nIndex = nType == INPIN ? ((CPtrArray *)pItem->pInPins)->GetSize() : ((CPtrArray *)pItem->pOutPins)->GetSize();
   pPin->nType = nType;
   pPin->pData = dwData;
   int len = MIN(sizeof(pPin->text), strName.GetLength());
   memcpy(pPin->text, (LPTSTR)(LPCTSTR)strName, len);
   pPin->text[len] = '\0';
   CPtrArray *pArray = NULL;
   if (nType == INPIN) {
      pArray = (CPtrArray *)pItem->pInPins;
   }
   else {
      pArray = (CPtrArray *)pItem->pOutPins;
   }
   ASSERT(pItem->pOutPins);
   pArray->Add(pPin);
   return pPin;
}
EQITEM* CEqsGraphWnd::GetItem(DWORD_PTR dwData)
{
   for (int i = 0; i < m_arItem.GetCount(); i++) {
      EQITEM* pItem = (EQITEM*)m_arItem.GetAt(i);
      if (pItem->pData == dwData) {
         return pItem;
      }
   }
   return NULL;
}
PIN * CEqsGraphWnd::GetPin(DWORD_PTR dwItemData, DWORD_PTR dwPinData)
{
   EQITEM* pItem = GetItem(dwItemData);
   if (pItem != NULL) {
      CPtrArray *pArray = (CPtrArray *)pItem->pInPins;
      for (int i = 0; i < pArray->GetCount(); i++) {
         PIN *pPin = (PIN *)pArray->GetAt(i);
         if (pPin->pData == dwPinData) {
            return pPin;
         }
      }
      pArray = (CPtrArray *)pItem->pOutPins;
      for (int i = 0; i < pArray->GetCount(); i++) {
         PIN *pPin = (PIN *)pArray->GetAt(i);
         if (pPin->pData == dwPinData) {
            return pPin;
         }
      }
   }
   return NULL;
}
int CEqsGraphWnd::ConnectPin(DWORD_PTR dwItem1Data, DWORD_PTR dwPin1Data, DWORD_PTR dwItem2Data, DWORD_PTR dwPin2Data)
{
   PIN *pPin1, *pPin2;
   pPin1 = GetPin(dwItem1Data, dwPin1Data);
   if (pPin1 == NULL) {
      return -1;
   }
   pPin2 = GetPin(dwItem2Data, dwPin2Data);
   if (pPin2 == NULL) {
      return -2;
   }
   pPin1->pConnectedPin = pPin2;
   pPin2->pConnectedPin = pPin1;
   return 0;
}
// åˆ é™¤Item, å¦‚æžœpin有连接,注意先断开
int CEqsGraphWnd::DeleteItem(EQITEM* pItem)
{
   for (int i = 0; i < m_arItem.GetSize(); i++) {
      if (pItem == (EQITEM*)m_arItem.GetAt(i)) {
         m_arItem.RemoveAt(i);
         ReleaseItem(pItem);
         return 0;
      }
   }
   return -1;
}
void CEqsGraphWnd::DeleteAllItems()
{
   ReleaseAllItems();
   RECT rcClient;
   GetClientRect(m_hWnd, &rcClient);
   ::InvalidateRect(m_hWnd, &rcClient, TRUE);
}
void CEqsGraphWnd::SetCurrentItem(EQITEM* pItem)
{
   if (m_pCurItem != NULL) {
      m_pCurItem->bHighlight = FALSE;
   }
   m_pCurItem = pItem;
   if (m_pCurItem != NULL) {
      m_pCurItem->bHighlight = TRUE;
   }
   RECT rcClient;
   GetClientRect(m_hWnd, &rcClient);
   ::InvalidateRect(m_hWnd, &rcClient, TRUE);
}
/*
 * è®¾ç½®å­é¡¹çš„选中状态
 */
void CEqsGraphWnd::SetItemSelectState(int nIndex, BOOL bSelect)
{
   if (nIndex >= m_arItem.GetCount()) {
      return;
   }
   EQITEM *pItem = (EQITEM*)m_arItem.GetAt(nIndex);
   pItem->bHighlight = bSelect;
   RECT rcClient;
   GetClientRect(m_hWnd, &rcClient);
   ::InvalidateRect(m_hWnd, &rcClient, TRUE);
}
void CEqsGraphWnd::Notify(int nCode, int dwData, int dwData1/* = 0*/, int dwData2/* = 0*/)
{
   HWND hParent;
   hParent = GetParent(m_hWnd);
   if (hParent != NULL) {
      EQSGRAPHWND_NMHDR nmhdr;
      nmhdr.nmhdr.hwndFrom = m_hWnd;
      nmhdr.nmhdr.idFrom = GetWindowLong(m_hWnd, GWL_ID);
      nmhdr.nmhdr.code = nCode;
      nmhdr.dwData = dwData;
      nmhdr.dwData1 = dwData1;
      nmhdr.dwData2 = dwData2;
      SendMessage(hParent, WM_NOTIFY, (WPARAM)nmhdr.nmhdr.idFrom, (LPARAM)&nmhdr);
   }
}
/*
 * æ£€æµ‹åæ ‡ç‚¹æ‰€åœ¨çš„项
 * è¿”回项类型, å¦‚HT_ITEM, HT_PIN, HT_LINE
 * pItem - æ‰€åœ¨çš„EQITEM
 * pPin --所在的pin, å¦‚果在连线上,表示所属pin, out pin;
 */
int CEqsGraphWnd::HighTest(POINT pt, OUT EQITEM*& pItem, OUT PIN *& pPin)
{
   // æ£€æµ‹æ˜¯å¦åœ¨æŸä¸ªå­é¡¹
   int nRet = HT_NOWHERE;
   pItem = NULL;
   pPin = NULL;
   RECT rcItem;
   for (int i = m_arItem.GetSize() - 1; i >= 0 ; i--) {
      EQITEM *pTempItem = (EQITEM*)m_arItem.GetAt(i);
      GetItemRect(pTempItem, &rcItem);
      if (::PtInRect(&rcItem, pt)) {
         // åœ¨Item
         pItem = pTempItem;
         nRet = HT_ITEM;
         break;
      }
      else {
         RECT rcPin;
         CPtrArray * pPins = (CPtrArray *)pTempItem->pInPins;
         for (int j = 0; j < pPins->GetSize(); j++) {
            if (GetInPinRect(pTempItem, j, &rcPin) && ::PtInRect(&rcPin, pt)) {
               // åœ¨in pin上
               pPin = (PIN *)pPins->GetAt(j);
               pItem = pTempItem;
               nRet = HT_PIN;
               break;
            }
         }
         if (nRet == HT_NOWHERE) {
            pPins = (CPtrArray *)pTempItem->pOutPins;
            for (int j = 0; j < pPins->GetSize(); j++) {
               if (GetOutPinRect(pTempItem, j, &rcPin) && ::PtInRect(&rcPin, pt)) {
                  // åœ¨out pin
                  pPin = (PIN *)pPins->GetAt(j);
                  pItem = pTempItem;
                  nRet = HT_PIN;
                  break;
               }
               else {
                  // æ˜¯å¦åœ¨pin连接线上,即判断点是否在线上
                  // ç‚¹åˆ°ç›´çº¿çš„距离公式(先通过p1,p2用两点式求出直线的表达式,再套距离公式);abs()为取绝对值函数,sqrt()为开根号函数
                  PIN *pTempPin = (PIN *)pPins->GetAt(j);
                  if (pTempPin->pConnectedPin != NULL && pTempPin->nLinePtCount > 1) {
                     for (int i = 0; i < pTempPin->nLinePtCount - 1; i++) {
                        double distance = PointToSegDist(pt.x + m_nOffsetX, pt.y + m_nOffsetY,
                           pTempPin->ptConnectedLine[i].x, pTempPin->ptConnectedLine[i].y,
                           pTempPin->ptConnectedLine[i+1].x, pTempPin->ptConnectedLine[i+1].y);
                        if (distance < 5.0) {
                           nRet = HT_LINE;
                           pPin = pTempPin;
                           break;
                        }
                     }
                     if (nRet == HT_LINE) {
                        break;
                     }
                  }
               }
            }
         }
         if (nRet != HT_NOWHERE) {
            break;
         }
      }
   }
   return nRet;
}
/*
 * ç»˜åˆ¶è™šçº¿æ¡†ï¼Œä»£è¡¨æ­£åœ¨æ‹–动的item
 */
void CEqsGraphWnd::DrawDropItemRectangle(LPRECT lpRect1, LPRECT lpRect2)
{
   HDC hDC = GetDC(m_hWnd);
   HBRUSH hBrush = CreateSolidBrush(RGB(255, 255, 255));
   HPEN hPen = CreatePen(PS_DASH, 1, RGB(0, 0, 0));
   int oldRop = SetROP2(hDC, R2_NOTXORPEN);
   HBRUSH hOldBrush = (HBRUSH)::SelectObject(hDC, hBrush);
   HBRUSH hOldPen = (HBRUSH)::SelectObject(hDC, hPen);
   if (lpRect1 != NULL) {
      ::Rectangle(hDC, lpRect1->left, lpRect1->top, lpRect1->right, lpRect1->bottom);
   }
   if (lpRect2 != NULL) {
      ::Rectangle(hDC, lpRect2->left, lpRect2->top, lpRect2->right, lpRect2->bottom);
   }
   ::SetROP2(hDC, oldRop);
   ::SelectObject(hDC, hOldBrush);
   ::SelectObject(hDC, hPen);
   ::DeleteObject(hBrush);
   ::DeleteObject(hOldPen);
   ::ReleaseDC(m_hWnd, hDC);
}
/*
 * ç»˜åˆ¶ç£å¸çº¿
 */
void CEqsGraphWnd::DrawMagneticLine(LPRECT lprcClient, int nHozLine1, int nHozLine2, int nVerLine1, int nVerLine2)
{
   HDC hDC = GetDC(m_hWnd);
   HPEN hPen = CreatePen(PS_DASH, 1, RGB(64, 64, 64));
   int oldRop = SetROP2(hDC, R2_NOTXORPEN);
   HBRUSH hOldPen = (HBRUSH)::SelectObject(hDC, hPen);
   if (nHozLine1) {
      ::MoveToEx(hDC, 1, nHozLine1, NULL);
      ::LineTo(hDC, lprcClient->right-1, nHozLine1);
   }
   if (nHozLine2) {
      ::MoveToEx(hDC, 1, nHozLine2, NULL);
      ::LineTo(hDC, lprcClient->right - 1, nHozLine2);
   }
   if (nVerLine1) {
      ::MoveToEx(hDC, nVerLine1, 1, NULL);
      ::LineTo(hDC, nVerLine1, lprcClient->bottom - 1);
   }
   if (nVerLine2) {
      ::MoveToEx(hDC, nVerLine2, 1, NULL);
      ::LineTo(hDC, nVerLine2, lprcClient->bottom - 1);
   }
   ::SetROP2(hDC, oldRop);
   ::SelectObject(hDC, hPen);
   ::DeleteObject(hOldPen);
   ::ReleaseDC(m_hWnd, hDC);
}
/*
 * ç¼“制Pin连接线
 * pBrush -- ç”»åˆ·
 * pPen - ç”»ç¬”
 * lpPt1, lpPt2 -- Pin脚的位置
 * lpRect1, lpRect2 -- ä¸¤ä¸ªItem的Rect
 */
void CEqsGraphWnd::DrawPinConnectedLine(Gdiplus::Graphics *pGraphics, Gdiplus::Brush *pBrush, Gdiplus::Pen *pPen, LPPOINT lpPt1, LPPOINT lpPt2,
   LPRECT lpRect1, LPRECT lpRect2, PIN *pOwnerPin)
{
   // å¦‚果没有缓存线条的POINT,则先计算并缓存
   ASSERT(pOwnerPin);
   int nPinCount = ((CPtrArray*)pOwnerPin->pItem->pOutPins)->GetSize();
   int nArrowLen = 8;
   int nStartMinX = 8;
   int nMargin = 12;
   int x1, x2, y1;
   if (pOwnerPin->nLinePtCount == 0) {                  // ç¬¬ä¸€ä¸ªç‚¹çš„æœ€å°æŠ˜çº¿é•¿
      ::OffsetRect(lpRect1, +m_nOffsetX, +m_nOffsetY);
      ::OffsetRect(lpRect2, +m_nOffsetX, +m_nOffsetY);
      lpPt1->x += m_nOffsetX;            // æ¶ˆé™¤åç§»
      lpPt1->y += m_nOffsetY;
      lpPt2->x += m_nOffsetX;
      lpPt2->y += m_nOffsetY;
      int nMinX = 10 + nMargin * nPinCount + nArrowLen;
      int xEnd = lpPt2->x - 5;
      x1 = lpPt1->x + 10 + pOwnerPin->nIndex * nMargin;
      if (lpPt2->x - lpPt1->x > nMinX) {
         pOwnerPin->ptConnectedLine[0].x = lpPt1->x;
         pOwnerPin->ptConnectedLine[0].y = lpPt1->y;
         pOwnerPin->ptConnectedLine[1].x = x1;
         pOwnerPin->ptConnectedLine[1].y = lpPt1->y;
         pOwnerPin->ptConnectedLine[2].x = x1;
         pOwnerPin->ptConnectedLine[2].y = lpPt2->y;
         pOwnerPin->ptConnectedLine[3].x = xEnd;
         pOwnerPin->ptConnectedLine[3].y = lpPt2->y;
         pOwnerPin->nLinePtCount = 4;
      }
      else if (lpRect1 != NULL && lpRect2 != NULL) {
         if (lpRect2->top - lpRect1->bottom > 20 || lpRect1->top - lpRect2->bottom > 20) {
            if (lpRect2->top - lpRect1->bottom > 20) {
               y1 = lpRect1->bottom + 10 + pOwnerPin->nIndex * nMargin;
               x2 = min(lpPt2->x - nArrowLen, x1) - (nPinCount - pOwnerPin->nIndex) * nMargin;
            }
            else {
               y1 = lpRect1->top - 10 - pOwnerPin->nIndex * nMargin;
               x2 = min(lpPt2->x - nArrowLen, x1) - (nPinCount - pOwnerPin->nIndex) * nMargin;
            }
            pOwnerPin->ptConnectedLine[0].x = lpPt1->x;
            pOwnerPin->ptConnectedLine[0].y = lpPt1->y;
            pOwnerPin->ptConnectedLine[1].x = x1;
            pOwnerPin->ptConnectedLine[1].y = lpPt1->y;
            pOwnerPin->ptConnectedLine[2].x = x1;
            pOwnerPin->ptConnectedLine[2].y = y1;
            pOwnerPin->ptConnectedLine[3].x = x2;
            pOwnerPin->ptConnectedLine[3].y = y1;
            pOwnerPin->ptConnectedLine[4].x = x2;
            pOwnerPin->ptConnectedLine[4].y = lpPt2->y;
            pOwnerPin->ptConnectedLine[5].x = xEnd;
            pOwnerPin->ptConnectedLine[5].y = lpPt2->y;
            pOwnerPin->nLinePtCount = 6;
         }
         else {
            x2 = min(lpRect1->left, lpRect2->left) - 30;
            y1 = max(lpRect1->bottom, lpRect2->bottom) + 30;
            pOwnerPin->ptConnectedLine[0].x = lpPt1->x;
            pOwnerPin->ptConnectedLine[0].y = lpPt1->y;
            pOwnerPin->ptConnectedLine[1].x = x1;
            pOwnerPin->ptConnectedLine[1].y = lpPt1->y;
            pOwnerPin->ptConnectedLine[2].x = x1;
            pOwnerPin->ptConnectedLine[2].y = y1;
            pOwnerPin->ptConnectedLine[3].x = x2;
            pOwnerPin->ptConnectedLine[3].y = y1;
            pOwnerPin->ptConnectedLine[4].x = x2;
            pOwnerPin->ptConnectedLine[4].y = lpPt2->y;
            pOwnerPin->ptConnectedLine[5].x = xEnd;
            pOwnerPin->ptConnectedLine[5].y = lpPt2->y;
            pOwnerPin->nLinePtCount = 6;
         }
      }
   }
   if (pOwnerPin->nLinePtCount >= 2) {
      for (int i = 0; i < pOwnerPin->nLinePtCount - 1; i++) {
         pGraphics->DrawLine(pPen, pOwnerPin->ptConnectedLine[i].x - m_nOffsetX, pOwnerPin->ptConnectedLine[i].y - m_nOffsetY,
            pOwnerPin->ptConnectedLine[i + 1].x - m_nOffsetX, pOwnerPin->ptConnectedLine[i + 1].y - m_nOffsetY);
      }
      DrawArrow(pGraphics, pBrush, pPen, pOwnerPin->ptConnectedLine[pOwnerPin->nLinePtCount-1].x - m_nOffsetX,
         pOwnerPin->ptConnectedLine[pOwnerPin->nLinePtCount-1].y - m_nOffsetY, nArrowLen);
   }
}
void CEqsGraphWnd::DrawArrow(Gdiplus::Graphics *pGraphics, Gdiplus::Brush* pBrush, Gdiplus::Pen *pPen,
   int x, int y, int nArrowLen)
{
   Gdiplus::Point pt[4];
   pt[0].X = x;
   pt[0].Y = y;
   pt[1].X = x - nArrowLen;
   pt[1].Y = y - 3;
   pt[2].X = pt[1].X;
   pt[2].Y = y + 3;
   pt[3].X = x;
   pt[3].Y = y;
   pGraphics->FillPolygon(pBrush, pt, 4);
   pGraphics->DrawPolygon(pPen, pt, 4);
}
void CEqsGraphWnd::DrawPinWillConnectLine(COLORREF color, LPPOINT lpPt1, LPPOINT lpPt2)
{
   HDC hDC = GetDC(m_hWnd);
   HPEN hPen = CreatePen(PS_SOLID, 2, color);
   int oldRop = SetROP2(hDC, R2_NOTXORPEN);
   HBRUSH hOldPen = (HBRUSH)::SelectObject(hDC, hPen);
   if (lpPt1 != NULL && lpPt2 != NULL) {
      ::MoveToEx(hDC, lpPt1->x, lpPt1->y, NULL);
      ::LineTo(hDC, lpPt2->x, lpPt2->y);
   }
   ::SetROP2(hDC, oldRop);
   ::SelectObject(hDC, hPen);
   ::DeleteObject(hOldPen);
   ::ReleaseDC(m_hWnd, hDC);
}
/*
 * WindowProc,窗口过程
 */
LRESULT CALLBACK CEqsGraphWnd::WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
   CEqsGraphWnd* pEqsGraphWnd = (CEqsGraphWnd*)GetProp(hWnd, EQSGRAPHWND_TAG);
   if (pEqsGraphWnd == NULL && uMsg != WM_NCCREATE)
   {
      return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
   }
   // å¤„理窗口消息
   ASSERT(hWnd);
   switch (uMsg)
   {
   case WM_NCCREATE:
      return CEqsGraphWnd::OnNcCreate(hWnd, wParam, lParam);
   case WM_DESTROY:
      return pEqsGraphWnd->OnDestroy(wParam, lParam);
   case WM_NCPAINT:
      return pEqsGraphWnd->OnNcPaint(wParam, lParam);
   case WM_PAINT:
      return pEqsGraphWnd->OnPaint(wParam, lParam);
   case WM_TIMER:
      return pEqsGraphWnd->OnTimer(wParam, lParam);
   case WM_MOUSEMOVE:
      return pEqsGraphWnd->OnMouseMove(wParam, lParam);
   case WM_LBUTTONDOWN:
      return pEqsGraphWnd->OnLButtonDown(wParam, lParam);
   case WM_RBUTTONDOWN:
      return pEqsGraphWnd->OnRButtonDown(wParam, lParam);
   case WM_LBUTTONDBLCLK:
      return pEqsGraphWnd->OnLButtonDblclk(wParam, lParam);
   case WM_MOUSEWHEEL:
      return pEqsGraphWnd->OnMouseWheel(wParam, lParam);
   case WM_MOUSEHWHEEL:
      return pEqsGraphWnd->OnMouseHWheel(wParam, lParam);
   case WM_KEYDOWN:
      return pEqsGraphWnd->OnKeyDown(wParam, lParam);
   case WM_SIZE:
      return pEqsGraphWnd->OnSize(wParam, lParam);
   case WM_VSCROLL:
      return pEqsGraphWnd->OnVScroll(wParam, lParam);
   case WM_HSCROLL:
      return pEqsGraphWnd->OnHScroll(wParam, lParam);
   case WM_NOTIFY:
      return pEqsGraphWnd->OnNitify(wParam, lParam);
   case WM_GETDLGCODE:
      return DLGC_WANTALLKEYS;
   default:
      break;
   }
   return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
}
/*
 * WM_NCCREATE
 * çª—口创建
 */
LRESULT CEqsGraphWnd::OnNcCreate(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
   CEqsGraphWnd* pEqsGraphWnd = (CEqsGraphWnd*)GetProp(hWnd,EQSGRAPHWND_TAG);
   ASSERT(pEqsGraphWnd == NULL);
   Hook(hWnd)->Init();
   return ::DefWindowProc(hWnd, WM_NCCREATE, wParam, lParam);
}
/*
 * WM_DESTROY
 * çª—口销毁
 */
LRESULT CEqsGraphWnd::OnDestroy(WPARAM wParam, LPARAM lParam)
{
   Release();
   return ::DefWindowProc(m_hWnd, WM_DESTROY, wParam, lParam);
}
/*
 * WM_TIMER
 */
LRESULT CEqsGraphWnd::OnTimer(WPARAM wParam, LPARAM lParam)
{
   if (wParam == TIMER_FLASH) {
      if (m_pFlashItem != NULL && m_nFlashCount > 0) {
         m_nFlashCount--;
         m_pFlashItem->nFlashFlag = (m_nFlashCount % 2);
         RECT rcItem;
         GetItemWarperRect(m_pFlashItem, &rcItem);
         InvalidateRect(m_hWnd, &rcItem, TRUE);
      }
      else {
         m_pFlashItem = NULL;
         m_nFlashCount = 0;
      }
   }
   else if (TIMER_ANIMATION_RECT == wParam) {
      if (m_pAnimationItem != NULL) {
         if (m_nAninationStep > 0) {
            m_nAninationStep--;
            m_rcAnimation.left += m_rcAninationStep.left;
            m_rcAnimation.right += m_rcAninationStep.right;
            m_rcAnimation.top += m_rcAninationStep.top;
            m_rcAnimation.bottom += m_rcAninationStep.bottom;
            RECT rcItem;
            GetItemWarperRect(m_pAnimationItem, &rcItem);
            InvalidateRect(m_hWnd, &rcItem, TRUE);
         }
         else {
            KillTimer(m_hWnd, TIMER_ANIMATION_RECT);
            RECT rcItem;
            GetItemWarperRect(m_pAnimationItem, &rcItem);
            InvalidateRect(m_hWnd, &rcItem, TRUE);
            m_pAnimationItem = NULL;
         }
      }
      else {
         KillTimer(m_hWnd, TIMER_ANIMATION_RECT);
         RECT rcClient;
         GetClientRect(m_hWnd, &rcClient);
         InvalidateRect(m_hWnd, &rcClient, TRUE);
      }
   }
   return ::DefWindowProc(m_hWnd, WM_TIMER, wParam, lParam);
}
/*
 * WM_MOUSEMOVE
 * é¼ æ ‡æ»šåЍ
 */
LRESULT CEqsGraphWnd::OnMouseMove(WPARAM wParam, LPARAM lParam)
{
   return ::DefWindowProc(m_hWnd, WM_MOUSEMOVE, wParam, lParam);
}
/*
 * WM_LBUTTONDOWN
 * é¼ æ ‡å·¦é”®æŒ‰ä¸‹
 */
LRESULT CEqsGraphWnd::OnLButtonDown(WPARAM wParam, LPARAM lParam)
{
   POINT pt, ptNew;
   pt.x = LOWORD(lParam);
   pt.y = HIWORD(lParam);
   RECT rcClient, rcItem, rcNewItem, rcLast;
   GetClientRect(m_hWnd, &rcClient);
   rcLast = {0, 0, 0, 0};
   int nMaxOffsetX = m_nStageCx - (rcClient.right - rcClient.left);
   int nMaxOffsetY = m_nStageCy - (rcClient.bottom - rcClient.top);
   int nLastHozLine = 0;
   int nLastVerLine = 0;
   // æ£€æµ‹ç‚¹å‡»åæ ‡æ˜¯å¦åœ¨æŸä¸€å­é¡¹ä¸Šï¼Œå¦‚是,则高亮显示
   EQITEM* pLastItem = m_pCurItem;
   PIN *pLastPin = m_pCurPin;
   PIN *pLastSelLineOutPin = m_pSelLineOutPin;
   BOOL bChanged = FALSE;
   EQITEM* pHitItem = NULL;
   PIN *pHitPin = NULL;
   PIN *pPin2 = NULL;
   int nRet = HighTest(pt, pHitItem, pHitPin);
   if (pHitItem != m_pCurItem || nRet != HT_ITEM) {
      if (m_pCurItem != NULL) {
         m_pCurItem->bHighlight = FALSE;
      }
      m_pCurItem = NULL;
   }
   if (pHitPin != m_pCurPin || nRet != HT_PIN) {
      if (m_pCurPin != NULL) {
         m_pCurPin->bHighlight = FALSE;
      }
      m_pCurPin = NULL;
   }
   if (pHitPin != m_pSelLineOutPin || nRet != HT_LINE) {
      m_pSelLineOutPin = NULL;
   }
   if (nRet == HT_ITEM) {
      m_pCurItem = pHitItem;
      m_pCurItem->bHighlight = TRUE;
   }
   else if (nRet == HT_PIN) {
      m_pCurPin = pHitPin;
      m_pCurPin->bHighlight = TRUE;
   }
   else if (nRet == HT_LINE) {
      m_pSelLineOutPin = pHitPin;
   }
   bChanged = pLastItem != m_pCurItem || pLastPin != m_pCurPin || pLastSelLineOutPin != m_pSelLineOutPin;
   // åˆ·æ–°
   SetFocus(m_hWnd);
   if (bChanged) {
      ::InvalidateRect(m_hWnd, &rcClient, TRUE);
   }
   // æ•捉鼠标消息,检测是否拖动
   if (nRet == HT_ITEM && m_pCurItem != NULL) {
      GetItemRect(m_pCurItem, &rcItem);
      if (::GetCapture() == NULL) {
         SetCapture(m_hWnd);
         ASSERT(m_hWnd == GetCapture());
         AfxLockTempMaps();
         for (;;)
         {
            MSG msg;
            VERIFY(::GetMessage(&msg, NULL, 0, 0));
            if (GetCapture() != m_hWnd) break;
            switch (msg.message)
            {
            case WM_MOUSEMOVE:
               ptNew = msg.pt;
               ::ScreenToClient(m_hWnd, &ptNew);
               rcNewItem.left = rcItem.left + (ptNew.x - pt.x);
               rcNewItem.right = rcItem.right + (ptNew.x - pt.x);
               rcNewItem.top = rcItem.top + (ptNew.y - pt.y);
               rcNewItem.bottom = rcItem.bottom + (ptNew.y - pt.y);
               CalculateMagneticLine(m_pCurItem, &rcNewItem, m_nMagneticLinHoz, m_nMagneticLinVer);
               DrawDropItemRectangle(&rcNewItem, &rcLast);
               DrawMagneticLine(&rcClient, m_nMagneticLinHoz, nLastHozLine, m_nMagneticLinVer, nLastVerLine);
               nLastHozLine = m_nMagneticLinHoz;
               nLastVerLine = m_nMagneticLinVer;
               CopyRect(&rcLast, &rcNewItem);
               break;
            case WM_LBUTTONUP:
               ptNew = msg.pt;
               ::ScreenToClient(m_hWnd, &ptNew);
               m_pCurItem->rect.left = m_nMagneticLinVer > 0 ? m_nMagneticLinVer : (rcItem.left + (ptNew.x - pt.x) + m_nOffsetX);
               m_pCurItem->rect.right = m_pCurItem->rect.left + (rcItem.right - rcItem.left);
               m_pCurItem->rect.top = m_nMagneticLinHoz > 0 ? m_nMagneticLinHoz : (rcItem.top + (ptNew.y - pt.y) + m_nOffsetY);
               m_pCurItem->rect.bottom = m_pCurItem->rect.top + (rcItem.bottom - rcItem.top);
               if (m_pCurItem->rect.left != rcItem.left || m_pCurItem->rect.top != rcItem.top) {
                  if (m_listener.onEqItemPosChanged != nullptr) {
                     m_listener.onEqItemPosChanged(m_pCurItem, m_pCurItem->rect.left, m_pCurItem->rect.top);
                  }
               }
               DrawDropItemRectangle(NULL, &rcLast);
               ReleaseCapture();
               ClearConnectedLinePoint(m_pCurItem);
               ::InvalidateRect(m_hWnd, &rcClient, TRUE);
               goto ExitLoop;
            case WM_KEYDOWN:
               if (msg.wParam != VK_ESCAPE)
                  break;
            default:
               DispatchMessage(&msg);
               break;
            }
         }
         ReleaseCapture();
      ExitLoop:
         m_nMagneticLinHoz = 0;
         m_nMagneticLinVer = 0;
         AfxUnlockTempMaps(FALSE);
      }
   }
   // æ•捉鼠标消息,检测是否连接引脚
   else if (nRet == HT_PIN && m_pCurPin != NULL) {
      if (::GetCapture() == NULL) {
         BOOL bLast = FALSE;
         bool bCanConnect;
         POINT ptPin, ptLast;
         COLORREF lineColor;
         GetPinPoint(m_pCurPin, &ptPin);
         ptLast.x = ptPin.x;
         ptLast.y = ptPin.y;
         SetCapture(m_hWnd);
         ASSERT(m_hWnd == GetCapture());
         AfxLockTempMaps();
         for (;;)
         {
            MSG msg;
            VERIFY(::GetMessage(&msg, NULL, 0, 0));
            if (GetCapture() != m_hWnd) break;
            switch (msg.message)
            {
            case WM_MOUSEMOVE:
               ptNew = msg.pt;
               ::ScreenToClient(m_hWnd, &ptNew);
               // æ“¦é™¤ä¸Šä¸€æ¬¡
               if (bLast) {
                  DrawPinWillConnectLine(lineColor, &ptPin, &ptLast);
               }
               // æ£€æµ‹æ˜¯å¦å¯ä»¥è¿žæŽ¥
               bCanConnect = false;
               nRet = HighTest(ptNew, pHitItem, pHitPin);
               if (nRet == HT_PIN) {
                  if (m_listener.onCheckConnectPin != nullptr) {
                     bCanConnect = m_listener.onCheckConnectPin(m_pCurPin, pHitPin);
                  }
               }
               if (bCanConnect) {
                  lineColor = RGB(0, 255, 0);
                  DrawPinWillConnectLine(lineColor, &ptPin, &ptNew);
               }
               else {
                  lineColor = RGB(0, 0, 0);
                  DrawPinWillConnectLine(lineColor, &ptPin, &ptNew);
               }
               ptLast.x = ptNew.x;
               ptLast.y = ptNew.y;
               bLast = TRUE;
               break;
            case WM_LBUTTONUP:
               ptNew = msg.pt;
               ::ScreenToClient(m_hWnd, &ptNew);
               // æ“¦é™¤ä¸Šä¸€æ¬¡
               if (bLast) {
                  DrawPinWillConnectLine(lineColor, &ptPin, &ptLast);
               }
               // æ£€æµ‹æ˜¯å¦å¯ä»¥è¿žæŽ¥
               bCanConnect = false;
               nRet = HighTest(ptNew, pHitItem, pHitPin);
               if (nRet == HT_PIN) {
                  if (m_listener.onConnectPin != nullptr) {
                     bCanConnect = m_listener.onConnectPin(m_pCurPin, pHitPin);
                  }
               }
               if (bCanConnect) {
                  m_pCurPin->pConnectedPin = pHitPin;
                  pHitPin->pConnectedPin = m_pCurPin;
               }
               bLast = FALSE;
               ReleaseCapture();
               ::InvalidateRect(m_hWnd, &rcClient, TRUE);
               goto ExitLoop2;
            case WM_KEYDOWN:
               if (msg.wParam != VK_ESCAPE)
                  break;
            default:
               DispatchMessage(&msg);
               break;
            }
         }
         ReleaseCapture();
      ExitLoop2:
         AfxUnlockTempMaps(FALSE);
      }
   }
   // æ£€æµ‹é¼ æ ‡æ¶ˆæ¯ï¼Œæ£€æµ‹æ˜¯å¦ç§»åŠ¨ç”»å¸ƒ
   else if (nRet == HT_NOWHERE) {
      if (::GetCapture() == NULL) {
         int nLastOffsetX = m_nOffsetX;
         int nLastOffsetY = m_nOffsetY;
         POINT ptStart;
         ptStart.x = pt.x;
         ptStart.y = pt.y;
         SetCursor(LoadCursor(NULL, IDC_SIZEALL));
         SetCapture(m_hWnd);
         ASSERT(m_hWnd == GetCapture());
         AfxLockTempMaps();
         for (;;) {
            MSG msg;
            VERIFY(::GetMessage(&msg, NULL, 0, 0));
            if (GetCapture() != m_hWnd) break;
            switch (msg.message)
            {
            case WM_MOUSEMOVE:
               ptNew = msg.pt;
               ::ScreenToClient(m_hWnd, &ptNew);
               m_nOffsetX = min(nMaxOffsetX, max(0, nLastOffsetX - (ptNew.x - ptStart.x)));
               m_nOffsetY = min(nMaxOffsetY, max(0, nLastOffsetY - (ptNew.y - ptStart.y)));
               CalculateScollbar();
               CalculateMapPos();
               ::InvalidateRect(m_hWnd, &rcClient, TRUE);
               break;
            case WM_LBUTTONUP:
               ptNew = msg.pt;
               ::ScreenToClient(m_hWnd, &ptNew);
               ReleaseCapture();
               CalculateScollbar();
               CalculateMapPos();
               ::InvalidateRect(m_hWnd, &rcClient, TRUE);
               goto ExitLoop3;
            case WM_KEYDOWN:
               if (msg.wParam != VK_ESCAPE)
                  break;
            default:
               DispatchMessage(&msg);
               break;
            }
         }
         ReleaseCapture();
      ExitLoop3:
         AfxUnlockTempMaps(FALSE);
      }
   }
   return ::DefWindowProc(m_hWnd, WM_LBUTTONDOWN, wParam, lParam);
}
/*
 * WM_LBUTTONDBLCLK
 * é¼ æ ‡å·¦é”®åŒå‡»
 */
LRESULT CEqsGraphWnd::OnLButtonDblclk(WPARAM wParam, LPARAM lParam)
{
   POINT pt;
   pt.x = LOWORD(lParam);
   pt.y = HIWORD(lParam);
   RECT rcClient, rcLast;
   GetClientRect(m_hWnd, &rcClient);
   rcLast = { 0, 0, 0, 0 };
   // æ£€æµ‹ç‚¹å‡»åæ ‡æ˜¯å¦åœ¨æŸä¸€å­é¡¹ä¸Šï¼Œå¦‚是,则高亮显示
   EQITEM* pLastItem = m_pCurItem;
   BOOL bChanged = FALSE;
   EQITEM* pHitItem = NULL;
   PIN *pHitPin = NULL;
   int nRet = HighTest(pt, pHitItem, pHitPin);
   if ( nRet == HT_ITEM) {
      m_pCurItem = pHitItem;
      m_pCurItem->bHighlight = FALSE;
      if (m_listener.onDblckEqItem != nullptr) {
         m_listener.onDblckEqItem(pHitItem);
      }
   }
   return ::DefWindowProc(m_hWnd, WM_LBUTTONDBLCLK, wParam, lParam);
}
/*
 * WM_MOUSEWHEEL
 * é¼ æ ‡æ»šåЍ
 */
LRESULT CEqsGraphWnd::OnMouseWheel(WPARAM wParam, LPARAM lParam)
{
   short zDelta;
   UINT nFlags;
   CPoint pt;
   nFlags = LOWORD(wParam);
   zDelta = (short)HIWORD(wParam);
   pt.x = (short)LOWORD(lParam);
   pt.y = (short)HIWORD(lParam);
   CRect rcClient;
   GetClientRect(m_hWnd, &rcClient);
   m_nOffsetY -= zDelta;
   m_nOffsetY = max(0, min(m_nOffsetY, m_nStageCy - rcClient.Height()));
   CalculateScollbar();
   CalculateMapPos();
   ::InvalidateRect(m_hWnd, &rcClient, TRUE);
   return ::DefWindowProc(m_hWnd, WM_MOUSEWHEEL, wParam, lParam);
}
/*
* WM_MOUSEHWHEEL
* é¼ æ ‡æ»šåЍ
*/
LRESULT CEqsGraphWnd::OnMouseHWheel(WPARAM wParam, LPARAM lParam)
{
   short zDelta;
   UINT nFlags;
   CPoint pt;
   nFlags = LOWORD(wParam);
   zDelta = (short)HIWORD(wParam);
   pt.x = (short)LOWORD(lParam);
   pt.y = (short)HIWORD(lParam);
   CRect rcClient;
   GetClientRect(m_hWnd, &rcClient);
   m_nOffsetX += zDelta;
   m_nOffsetX = max(0, min(m_nOffsetX, m_nStageCx - rcClient.Width()));
   CalculateScollbar();
   CalculateMapPos();
   ::InvalidateRect(m_hWnd, &rcClient, TRUE);
   return ::DefWindowProc(m_hWnd, WM_MOUSEHWHEEL, wParam, lParam);
}
/*
 * WM_RBUTTONDOWN
 * é¼ æ ‡å·¦é”®æŒ‰ä¸‹
 */
LRESULT CEqsGraphWnd::OnRButtonDown(WPARAM wParam, LPARAM lParam)
{
   POINT pt, ptNew;
   pt.x = LOWORD(lParam);
   pt.y = HIWORD(lParam);
   RECT rcClient, rcItem, rcLast;
   GetClientRect(m_hWnd, &rcClient);
   rcLast = { 0, 0, 0, 0 };
   // æ£€æµ‹ç‚¹å‡»åæ ‡æ˜¯å¦åœ¨æŸä¸€å­é¡¹ä¸Šï¼Œå¦‚是,则高亮显示
   EQITEM* pLastItem = m_pCurItem;
   PIN *pLastPin = m_pCurPin;
   PIN *pLastSelLineOutPin = m_pSelLineOutPin;
   BOOL bChanged = FALSE;
   EQITEM* pHitItem = NULL;
   PIN *pHitPin = NULL;
   PIN *pPin2 = NULL;
   int nRet = HighTest(pt, pHitItem, pHitPin);
   if (pHitItem != m_pCurItem || nRet != HT_ITEM) {
      if (m_pCurItem != NULL) {
         m_pCurItem->bHighlight = FALSE;
      }
      m_pCurItem = NULL;
   }
   if (pHitPin != m_pCurPin || nRet != HT_PIN) {
      if (m_pCurPin != NULL) {
         m_pCurPin->bHighlight = FALSE;
      }
      m_pCurPin = NULL;
   }
   if (pHitPin != m_pSelLineOutPin || nRet != HT_LINE) {
      m_pSelLineOutPin = NULL;
   }
   if (nRet == HT_ITEM) {
      m_pCurItem = pHitItem;
      m_pCurItem->bHighlight = TRUE;
   }
   else if (nRet == HT_PIN) {
      m_pCurPin = pHitPin;
      m_pCurPin->bHighlight = TRUE;
   }
   else if (nRet == HT_LINE) {
      m_pSelLineOutPin = pHitPin;
   }
   bChanged = pLastItem != m_pCurItem || pLastPin != m_pCurPin || pLastSelLineOutPin != m_pSelLineOutPin;
   // åˆ·æ–°
   SetFocus(m_hWnd);
   if (bChanged) {
      ::InvalidateRect(m_hWnd, &rcClient, TRUE);
   }
   // æ•捉鼠标消息,检测是否拖动
   if (nRet == HT_ITEM && m_pCurItem != NULL) {
      CopyRect(&rcItem, &m_pCurItem->rect);
      if (::GetCapture() == NULL) {
         SetCapture(m_hWnd);
         ASSERT(m_hWnd == GetCapture());
         AfxLockTempMaps();
         for (;;)
         {
            MSG msg;
            VERIFY(::GetMessage(&msg, NULL, 0, 0));
            if (GetCapture() != m_hWnd) break;
            switch (msg.message)
            {
            case WM_MOUSEMOVE:
               break;
            case WM_RBUTTONUP:
               ptNew = msg.pt;
               ::ScreenToClient(m_hWnd, &ptNew);
               nRet = HighTest(ptNew, pHitItem, pHitPin);
               ReleaseCapture();
               if (m_listener.onRclickEqItem != NULL) {
                  m_listener.onRclickEqItem(pHitItem);
               }
               ::InvalidateRect(m_hWnd, &rcClient, TRUE);
               goto ExitLoop;
            case WM_KEYDOWN:
               if (msg.wParam != VK_ESCAPE)
                  break;
            default:
               DispatchMessage(&msg);
               break;
            }
         }
         ReleaseCapture();
      ExitLoop:
         AfxUnlockTempMaps(FALSE);
      }
   }
   return ::DefWindowProc(m_hWnd, WM_LBUTTONDOWN, wParam, lParam);
}
/*
 * WM_KEYDOWN
 * é”®ç›˜æ¶ˆæ¯ï¼ŒæŒ‰ä¸‹æŒ‰é”®
 */
LRESULT CEqsGraphWnd::OnKeyDown(WPARAM wParam, LPARAM lParam)
{
   BOOL bChanged = FALSE;
   if (wParam == VK_DELETE) {
      // å¦‚果当前选择为线,则断开连接
      if (m_pSelLineOutPin != NULL) {
         if (m_listener.onDisconnectPin != nullptr) {
            if (m_listener.onDisconnectPin(m_pSelLineOutPin)) {
               m_pSelLineOutPin->pConnectedPin->pConnectedPin = NULL;
               m_pSelLineOutPin->pConnectedPin = NULL;
               m_pSelLineOutPin = NULL;
               bChanged = TRUE;
            }
         }
      }
      else if (m_pCurItem != NULL) {
         if (m_listener.onDeleteEqItem != NULL) {
            if (m_listener.onDeleteEqItem(m_pCurItem)) {
               bChanged = DeleteItem(m_pCurItem) >= 0;
            }
         }
      }
   }
   if (bChanged) {
      RECT rcClient;
      GetClientRect(m_hWnd, &rcClient);
      ::InvalidateRect(m_hWnd, &rcClient, TRUE);
   }
   return ::DefWindowProc(m_hWnd, WM_KEYDOWN, wParam, lParam);
}
/*
 * WM_NCPAINT
 */
LRESULT CEqsGraphWnd::OnNcPaint(WPARAM wParam, LPARAM lParam)
{
   LRESULT lRet = ::DefWindowProc(m_hWnd, WM_NCPAINT, wParam, lParam);
   long styleEx = GetWindowLong(m_hWnd, GWL_EXSTYLE);
   if ((styleEx & WS_EX_CLIENTEDGE) == WS_EX_CLIENTEDGE) {
      RECT rect, rcClient;
      GetClientRect(m_hWnd, &rcClient);
      ::ClientToScreen(m_hWnd, (LPPOINT)&rcClient.left);
      ::ClientToScreen(m_hWnd, (LPPOINT)&rcClient.right);
      GetWindowRect(m_hWnd, &rect);
      rcClient.right = rect.right - 1;
      rcClient.bottom = rect.bottom - 1;
      ::OffsetRect(&rcClient, -rect.left, -rect.top);
      rect.right -= rect.left;
      rect.bottom -= rect.top;
      rect.left = 0;
      rect.top = 0;
      HRGN hRgnWnd = CreateRectRgnIndirect(&rect);
      HRGN hRgnClient = CreateRectRgnIndirect(&rcClient);
      HBRUSH hBrushBK, hBrushFrame;
      HDC hDC = ::GetWindowDC(m_hWnd);
      ::SelectClipRgn(hDC, hRgnWnd);
      ::ExtSelectClipRgn(hDC, hRgnClient, RGN_DIFF);
      hBrushBK = CreateSolidBrush(m_crBkgnd);
      ::FillRect(hDC, &rect, hBrushBK);
      DeleteObject(hBrushBK);
      hBrushFrame = CreateSolidBrush(m_crFrame);
      ::FrameRect(hDC, &rect, hBrushFrame);
      ::DeleteObject(hRgnWnd);
      ::DeleteObject(hRgnClient);
      DeleteObject(hBrushFrame);
      ::ReleaseDC(m_hWnd, hDC);
   }
   return lRet;
}
/*
 * WM_PAINT
 */
LRESULT CEqsGraphWnd::OnPaint(WPARAM wParam, LPARAM lParam)
{
   HDC hDC, hMemDC;
   HBITMAP hBitmap;
   RECT rcClient;
   CString strText;
   HBRUSH hBrushBK;
   // BeginPaint
   PAINTSTRUCT ps;
   hDC = BeginPaint(m_hWnd, &ps);
   GetClientRect(m_hWnd, &rcClient);
   hMemDC = ::CreateCompatibleDC(hDC);
   hBitmap = ::CreateCompatibleBitmap(hDC, rcClient.right - rcClient.left,
      rcClient.bottom - rcClient.top);
   ::SelectObject(hMemDC, hBitmap);
   // èƒŒæ™¯é¢œè‰²
   hBrushBK = CreateSolidBrush(m_crBkgnd);
   ::FillRect(hMemDC, &rcClient, hBrushBK);
   DeleteObject(hBrushBK);
   // æ ‡é¢˜
   if (m_hFontTitle == nullptr) {
      LOGFONT lf;
      HFONT hFontDefault = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
      ::GetObject(hFontDefault, sizeof(LOGFONT), &lf);
      lf.lfHeight -= 6;
      lf.lfWeight = FW_SEMIBOLD;
      m_hFontTitle = CreateFontIndirect(&lf);
   }
   {
      char szTitle[256];
      GetWindowText(m_hWnd, szTitle, 256);
      RECT rcTitle;
      rcTitle.left = rcClient.left + 5;
      rcTitle.top = rcClient.top + 12;
      rcTitle.bottom = rcClient.bottom - 5;
      rcTitle.right = rcClient.right - 5;
      ::SelectObject(hMemDC, m_hFontTitle);
      ::DrawText(hMemDC, szTitle, (int)strlen(szTitle), &rcTitle, DT_LEFT | DT_TOP);
   }
   // ç»˜åˆ¶å­é¡¹
   HBRUSH hbrItemBackground[2];
   HBRUSH hbrItemFrame[2];
   HBRUSH hbrPinBackground[3];
   hbrItemBackground[0] = CreateSolidBrush(m_crItemBackground[0]);
   hbrItemBackground[1] = CreateSolidBrush(m_crItemBackground[1]);
   hbrItemFrame[0] = CreateSolidBrush(m_crItemFrame[0]);
   hbrItemFrame[1] = CreateSolidBrush(m_crItemFrame[1]);
   for (int i = 0; i < 3; i++) {
      hbrPinBackground[i] = CreateSolidBrush(m_crPinBkgnd[i]);
   }
   // gdi+
   Gdiplus::Graphics graphics(hMemDC);
   Gdiplus::Pen pen1(Gdiplus::Color(255, 64, 64, 64), 2);
   Gdiplus::Pen pen2(Gdiplus::Color(255, 255, 127, 39), 2);
   Gdiplus::SolidBrush brush1(Gdiplus::Color(255, 64, 64, 64));
   if (m_bUseGdiPlus) {
      graphics.SetSmoothingMode(Gdiplus::SmoothingModeHighQuality);
   }
   SetBkMode(hMemDC, TRANSPARENT);
   {
      RECT rcItem;
      int nPinState;
      int nItemCount = (int)m_arItem.GetCount();
      for (int i = 0; i < nItemCount; i++) {
         EQITEM* pItem = (EQITEM*)m_arItem.GetAt(i);
         if (pItem->nFlashFlag == 1) {
            continue;
         }
         GetItemRect(pItem, &rcItem);
         // å­é¡¹èƒŒæ™¯å’Œè¾¹æ¡†
         if (m_nItemRound == 0) {
            ::FillRect(hMemDC, &rcItem, pItem->bHighlight ? hbrItemBackground[1] : hbrItemBackground[0]);
            ::FrameRect(hMemDC, &rcItem, pItem->bHighlight ? hbrItemFrame[1] : hbrItemFrame[0]);
         }
         else {
            HRGN hRgn = CreateRoundRectRgn(rcItem.left, rcItem.top, rcItem.right, rcItem.bottom, m_nItemRound, m_nItemRound);
            ::FillRgn(hMemDC, hRgn, pItem->bHighlight ? hbrItemBackground[1] : hbrItemBackground[0]);
            ::FrameRgn(hMemDC, hRgn, pItem->bHighlight ? hbrItemFrame[1] : hbrItemFrame[0], 1, 1);
            ::DeleteObject(hRgn);
         }
         // name和id
         HFONT hFontOld = (HFONT)::SelectObject(hMemDC, m_hFontName);
         ::SetTextColor(hMemDC, pItem->bHighlight ? m_crItemNameText[1] : m_crItemNameText[0]);
         ::DrawText(hMemDC, pItem->text, (int)strlen(pItem->text), &rcItem, DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS);
         if (pItem->nShowType != ITEM_SMALL) {
            RECT rcId = rcItem;
            rcId.left += 5;
            rcId.bottom -= 5;
            CString strId;
            strId.Format(_T("ID:%d"), pItem->id);
            ::SelectObject(hMemDC, m_hFontId);
            ::SetTextColor(hMemDC, pItem->bHighlight ? m_crItemIdText[1] : m_crItemIdText[0]);
            ::DrawText(hMemDC, strId, (int)strId.GetLength(), &rcId, DT_LEFT | DT_BOTTOM | DT_SINGLELINE | DT_END_ELLIPSIS);
         }
         // åŠ¨ç”»æ•ˆæžœä¸ç»˜pin
         if (m_pAnimationItem == pItem) {
            continue;
         }
         // ç»˜åˆ¶pin
         RECT rcPin, rcPin2, rcPinText;
         CPtrArray *pPins;
         rcPinText.left = rcItem.left + 8;
         rcPinText.right = rcItem.right - 8;
         // in pins
         PIN *pPin = NULL;
         pPins = (CPtrArray *)pItem->pInPins;
         for (int j = 0; j < pPins->GetSize(); j++) {
            if (GetInPinRect(pItem, j, &rcPin)) {
               pPin = (PIN *)pPins->GetAt(j);
               ::FrameRect(hMemDC, &rcPin, pItem->bHighlight ? hbrItemFrame[1] : hbrItemFrame[0]);
               rcPin2.left = rcPin.left + 1;
               rcPin2.right = rcPin.right;
               rcPin2.top = rcPin.top + 1;
               rcPin2.bottom = rcPin.bottom - 1;
               nPinState = GetPinState(pPin);
               ::FillRect(hMemDC, &rcPin2, nPinState == 0 ? (pItem->bHighlight ? hbrItemBackground[1] : hbrItemBackground[0]) : hbrPinBackground[nPinState]);
               if (pItem->nShowType != ITEM_SMALL) {
                  rcPinText.top = rcPin.top - 12;
                  rcPinText.bottom = rcPin.bottom + 12;
                  ::SetTextColor(hMemDC, pItem->bHighlight ? m_crItemIdText[1] : m_crItemIdText[0]);
                  ::DrawText(hMemDC, pPin->text, (int)strlen(pPin->text), &rcPinText, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS);
               }
            }
         }
         // out pins
         pPins = (CPtrArray *)pItem->pOutPins;
         for (int j = 0; j < pPins->GetSize(); j++) {
            pPin = (PIN *)pPins->GetAt(j);
            if (GetOutPinRect(pItem, j, &rcPin)) {
               ::FrameRect(hMemDC, &rcPin, pItem->bHighlight ? hbrItemFrame[1] : hbrItemFrame[0]);
               rcPin2.left = rcPin.left;
               rcPin2.right = rcPin.right - 1;
               rcPin2.top = rcPin.top + 1;
               rcPin2.bottom = rcPin.bottom - 1;
               nPinState = GetPinState(pPin);
               ::FillRect(hMemDC, &rcPin2, nPinState == 0 ? (pItem->bHighlight ? hbrItemBackground[1] : hbrItemBackground[0]) : hbrPinBackground[nPinState]);
               if (pItem->nShowType != ITEM_SMALL) {
                  rcPinText.top = rcPin.top - 12;
                  rcPinText.bottom = rcPin.bottom + 12;
                  ::SetTextColor(hMemDC, pItem->bHighlight ? m_crItemIdText[1] : m_crItemIdText[0]);
                  ::DrawText(hMemDC, pPin->text, (int)strlen(pPin->text), &rcPinText, DT_RIGHT | DT_VCENTER | DT_SINGLELINE | DT_END_ELLIPSIS);
               }
            }
         }
         ::DeleteObject(hbrItemFrame);
         ::SelectObject(hMemDC, hFontOld);
      }
      // ç»˜åˆ¶è¿žæŽ¥çº¿ï¼Œä¿å­˜çº¿æ¡åœ¨æœ€åŽç»˜åˆ¶
      for (int i = 0; i < nItemCount; i++) {
         EQITEM *pItem = (EQITEM*)m_arItem.GetAt(i);
         if (pItem->nFlashFlag == 1) {
            continue;
         }
         PIN *pPin = NULL;
         CPtrArray *pPins;
         // out pins边线
         RECT rcItem1, rcItem2;
         pPins = (CPtrArray *)pItem->pOutPins;
         for (int j = 0; j < pPins->GetSize(); j++) {
            pPin = (PIN *)pPins->GetAt(j);
            if (pPin->pConnectedPin != NULL) {
               POINT pt1, pt2;
               if (GetPinPoint(pPin, &pt1) && GetPinPoint(pPin->pConnectedPin, &pt2)) {
                  GetItemRect(pItem, &rcItem1);
                  GetItemRect(pPin->pConnectedPin->pItem, &rcItem2);
                  DrawPinConnectedLine(&graphics, &brush1, pPin == m_pSelLineOutPin ? &pen2 : &pen1,
                     &pt1, &pt2, &rcItem1, &rcItem2, pPin);
               }
            }
         }
      }
      for (int i = 0; i < 3; i++) {
         ::DeleteObject(hbrPinBackground[i]);
      }
      ::DeleteObject(hbrItemBackground[0]);
      ::DeleteObject(hbrItemBackground[1]);
      ::DeleteObject(hbrItemFrame[0]);
      ::DeleteObject(hbrItemFrame[1]);
   }
   // EndPaint
   ::BitBlt(hDC, 0, 0, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top,
      hMemDC, 0, 0, SRCCOPY);
   EndPaint(m_hWnd, &ps);
   ::DeleteObject(hBitmap);
   ::DeleteDC(hMemDC);
   return 1;
}
/*
 * WM_SIZE
 */
LRESULT CEqsGraphWnd::OnSize(WPARAM wParam, LPARAM lParam)
{
   LRESULT lRet = ::DefWindowProc(m_hWnd, WM_SIZE, wParam, lParam);
   CalculateScollbar();
   if (m_hWndMapPos != NULL) {
      CalculateMapPos();
      CRect rcItem, rcClient;
      GetClientRect(m_hWnd, &rcClient);
      GetWindowRect(m_hWndMapPos,& rcItem);
      ::MoveWindow(m_hWndMapPos, rcClient.right- rcItem.Width() - MAPPOSWND_PADDING_RIGHT,
         MAPPOSWND_PADDING_RIGHT, rcItem.Width(), rcItem.Height(), TRUE);
   }
   return lRet;
}
/*
 * WM_VSCROLL
 */
LRESULT CEqsGraphWnd::OnVScroll(WPARAM wParam, LPARAM lParam)
{
   int nSBCode = LOWORD(wParam);
   int nPos = HIWORD(wParam);
   SCROLLINFO info = { 0 };
   info.cbSize = sizeof(SCROLLINFO);
   info.fMask = SIF_ALL;
   GetScrollInfo(m_hWnd, SB_VERT, &info);
   int nMaxPos = info.nMax - info.nPage;
   int inc = 10;
   switch (nSBCode)
   {
   case SB_BOTTOM:
      if (info.nPos < nMaxPos) {
         // ScrollWindow(m_hWnd, 0, -1 * inc*(iMaxPos - info.nPos), NULL, NULL);
         info.nPos = nMaxPos;
      }
      break;
   case SB_TOP:
      if (info.nPos > info.nMin) {
         // ScrollWindow(m_hWnd, 0, inc*(info.nPos - info.nMin), NULL, NULL);
         info.nPos = info.nMin;
      }
      break;
   case SB_LINEUP:
      if (info.nPos > info.nMin) {
         //ScrollWindow(m_hWnd, 0, inc, NULL, NULL);
         info.nPos -= 1;
      }
      break;
   case SB_LINEDOWN:
      if (info.nPos < nMaxPos) {
         // ScrollWindow(m_hWnd, 0, -1 * inc, NULL, NULL);
         info.nPos += 1;
      }
      break;
   case SB_PAGEUP:
      if (info.nPos - 100 >= info.nMin) {
         //ScrollWindow(m_hWnd, 0, 100 * inc, NULL, NULL);
         info.nPos -= 100;
      }
      else {
         if (info.nPos <= 0) {
            // ScrollWindow(m_hWnd, 0, 0, NULL, NULL);
         }
         else {
            // ScrollWindow(m_hWnd, 0, info.nPos, NULL, NULL);
         }
         info.nPos = info.nMin;
      }
      break;
   case SB_PAGEDOWN:
      if (info.nPos + 100 <= nMaxPos) {
         // ScrollWindow(m_hWnd, 0, -100*inc, NULL, NULL);
         info.nPos += 100;
      }
      else {
         // ScrollWindow(m_hWnd, 0, (info.nPos - iMaxPos) * inc, NULL, NULL);
         info.nPos = nMaxPos;
      }
      break;
   case SB_ENDSCROLL:
      break;
   case SB_THUMBPOSITION:
      break;
   case SB_THUMBTRACK:
      // ScrollWindow(m_hWnd, 0, inc * (info.nPos - nPos), NULL, NULL);
      info.nPos = nPos;
      break;
   default:
      break;
   }
   m_nOffsetY = info.nPos;
   SetScrollInfo(m_hWnd, SB_VERT, &info, TRUE);
   CalculateMapPos();
   RECT rcClient;
   GetClientRect(m_hWnd, &rcClient);
   ::InvalidateRect(m_hWnd, &rcClient, TRUE);
   LRESULT lRet = ::DefWindowProc(m_hWnd, WM_VSCROLL, wParam, lParam);
   return lRet;
}
/*
 * WM_HSCROLL
 */
LRESULT CEqsGraphWnd::OnHScroll(WPARAM wParam, LPARAM lParam)
{
   int nSBCode = LOWORD(wParam);
   int nPos = HIWORD(wParam);
   SCROLLINFO info = { 0 };
   info.cbSize = sizeof(SCROLLINFO);
   info.fMask = SIF_ALL;
   GetScrollInfo(m_hWnd, SB_HORZ, &info);
   int nMaxPos = info.nMax - info.nPage;
   int inc = 10;
   switch (nSBCode)
   {
   case SB_RIGHT:
      if (info.nPos < nMaxPos) {
         // ScrollWindow(m_hWnd, 0, -1 * inc*(iMaxPos - info.nPos), NULL, NULL);
         info.nPos = nMaxPos;
      }
      break;
   case SB_LEFT:
      if (info.nPos > info.nMin) {
         // ScrollWindow(m_hWnd, 0, inc*(info.nPos - info.nMin), NULL, NULL);
         info.nPos = info.nMin;
      }
      break;
   case SB_LINELEFT:
      if (info.nPos > info.nMin) {
         //ScrollWindow(m_hWnd, 0, inc, NULL, NULL);
         info.nPos -= 1;
      }
      break;
   case SB_LINERIGHT:
      if (info.nPos < nMaxPos) {
         // ScrollWindow(m_hWnd, 0, -1 * inc, NULL, NULL);
         info.nPos += 1;
      }
      break;
   case SB_PAGELEFT:
      if (info.nPos - 100 >= info.nMin) {
         //ScrollWindow(m_hWnd, 0, 100 * inc, NULL, NULL);
         info.nPos -= 100;
      }
      else {
         if (info.nPos <= 0) {
            // ScrollWindow(m_hWnd, 0, 0, NULL, NULL);
         }
         else {
            // ScrollWindow(m_hWnd, 0, info.nPos, NULL, NULL);
         }
         info.nPos = info.nMin;
      }
      break;
   case SB_PAGERIGHT:
      if (info.nPos + 100 <= nMaxPos) {
         // ScrollWindow(m_hWnd, 0, -100*inc, NULL, NULL);
         info.nPos += 100;
      }
      else {
         // ScrollWindow(m_hWnd, 0, (info.nPos - iMaxPos) * inc, NULL, NULL);
         info.nPos = nMaxPos;
      }
      break;
   case SB_ENDSCROLL:
      break;
   case SB_THUMBPOSITION:
      break;
   case SB_THUMBTRACK:
      // ScrollWindow(m_hWnd, 0, inc * (info.nPos - nPos), NULL, NULL);
      info.nPos = nPos;
      break;
   default:
      break;
   }
   m_nOffsetX = info.nPos;
   SetScrollInfo(m_hWnd, SB_HORZ, &info, TRUE);
   CalculateMapPos();
   RECT rcClient;
   GetClientRect(m_hWnd, &rcClient);
   ::InvalidateRect(m_hWnd, &rcClient, TRUE);
   LRESULT lRet = ::DefWindowProc(m_hWnd, WM_HSCROLL, wParam, lParam);
   return lRet;
}
/*
 * WM_NOTIFY
 */
LRESULT CEqsGraphWnd::OnNitify(WPARAM wParam, LPARAM lParam)
{
   LRESULT lRet = ::DefWindowProc(m_hWnd, WM_NOTIFY, wParam, lParam);
   NMHDR *pNmhdr = (NMHDR *)lParam;
   if (pNmhdr->idFrom == MAPPOSWND_ID) {
      MAPPOSWND_NMHDR *pNmhdr2 = (MAPPOSWND_NMHDR *)lParam;
      m_nOffsetX = pNmhdr2->dwData;
      m_nOffsetY = pNmhdr2->dwData1;
      CalculateScollbar();
      CalculateMapPos();
      RECT rcClient;
      GetClientRect(m_hWnd, &rcClient);
      ::InvalidateRect(m_hWnd, &rcClient, TRUE);
   }
   return lRet;
}
/*
 * è®¾ç½®èƒŒæ™¯é¢œè‰²
 * color -- èƒŒæ™¯è‰²
 */
void CEqsGraphWnd::SetBkgndColor(COLORREF color)
{
   m_crBkgnd = color;
}
/*
 * è¾¹æ¡†é¢œè‰²
 * color -- è¾¹æ¡†è‰²
 */
void CEqsGraphWnd::SetFrameColor(COLORREF color)
{
   m_crFrame = color;
}
void CEqsGraphWnd::FlashItem(EQITEM *pItem)
{
   if (m_pFlashItem != NULL) {
      KillTimer(m_hWnd, TIMER_FLASH);
   }
   m_nFlashCount = 5;
   m_pFlashItem = pItem;
   SetTimer(m_hWnd, TIMER_FLASH, 100, NULL);
}
void CEqsGraphWnd::AnimationItem(EQITEM* pItem)
{
   if (m_pAnimationItem != NULL) {
      KillTimer(m_hWnd, TIMER_ANIMATION_RECT);
   }
   m_pAnimationItem = NULL;
   KillTimer(m_hWnd, TIMER_ANIMATION_RECT);
   UINT uElpase = 50;
   m_nAninationDuration = 200;
   m_nAninationStep = m_nAninationDuration / uElpase;
   m_pAnimationItem = pItem;
   m_rcAnimation.left = pItem->rect.left + (pItem->rect.right - pItem->rect.left) / 2.0f;
   m_rcAnimation.right = m_rcAnimation.left;
   m_rcAnimation.top = pItem->rect.top + (pItem->rect.bottom - pItem->rect.top) / 2.0f;
   m_rcAnimation.bottom = m_rcAnimation.top;
   m_rcAninationStep.left = (pItem->rect.left - m_rcAnimation.left) / (float)m_nAninationStep;
   m_rcAninationStep.right = (pItem->rect.right - m_rcAnimation.right) / (float)m_nAninationStep;
   m_rcAninationStep.top = (pItem->rect.top - m_rcAnimation.top) / (float)m_nAninationStep;
   m_rcAninationStep.bottom = (pItem->rect.bottom - m_rcAnimation.bottom) / (float)m_nAninationStep;
   SetTimer(m_hWnd, TIMER_ANIMATION_RECT, uElpase, NULL);
}
double CEqsGraphWnd::PointToSegDist(double x, double y, double x1, double y1, double x2, double y2)
{
   double cross = (x2 - x1) * (x - x1) + (y2 - y1) * (y - y1);
   if (cross <= 0) return sqrt((x - x1) * (x - x1) + (y - y1) * (y - y1));
   double d2 = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1);
   if (cross >= d2) return sqrt((x - x2) * (x - x2) + (y - y2) * (y - y2));
   double r = cross / d2;
   double px = x1 + (x2 - x1) * r;
   double py = y1 + (y2 - y1) * r;
   return sqrt((x - px) * (x - px) + (py - y) * (py - y));
}