/*++ Copyright (C) 1996-1999 Microsoft Corporation Module Name: hatchwnd.h Abstract: Implementation of the CHatchWin class. CHatchWin when used as a parent window creates a thin hatch border around the child window. --*/ #include #include #include "hatchwnd.h" #include "resource.h" #include "globals.h" // Hit codes for computing handle code (Y_CODE + 3 * X_CODE) #define Y_TOP 0 #define Y_MIDDLE 1 #define Y_BOTTOM 2 #define X_LEFT 0 #define X_MIDDLE 1 #define X_RIGHT 2 #define NO_HIT -1 // Sizing flags #define SIZING_TOP 0x0001 #define SIZING_BOTTOM 0x0002 #define SIZING_LEFT 0x0004 #define SIZING_RIGHT 0x0008 #define SIZING_ALL 0x0010 // Sizing flags lookup (indexed by handle code) static UINT uSizingTable[9] = { SIZING_LEFT | SIZING_TOP, SIZING_TOP, SIZING_RIGHT | SIZING_TOP, SIZING_LEFT, SIZING_ALL, SIZING_RIGHT, SIZING_LEFT | SIZING_BOTTOM, SIZING_BOTTOM, SIZING_BOTTOM | SIZING_RIGHT }; // Cursor ID lookup (indexed by handle code) static UINT uCursIDTable[9] = { IDC_CURS_NWSE, IDC_CURS_NS, IDC_CURS_NESW, IDC_CURS_WE, IDC_CURS_MOVE, IDC_CURS_WE, IDC_CURS_NESW, IDC_CURS_NS, IDC_CURS_NWSE }; // Cursors (indexed by cursor ID) static HCURSOR hCursTable[IDC_CURS_MAX - IDC_CURS_MIN + 1]; #define IDTIMER_DEBOUNCE 1 #define MIN_SIZE 8 // Brush patterns static WORD wHatchBmp[]={0x11, 0x22, 0x44, 0x88, 0x11, 0x22, 0x44, 0x88}; static WORD wGrayBmp[]={0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa}; static HBRUSH hBrHatch; static HBRUSH hBrGray; // System parameters static INT iBorder; static INT iDragMinDist; static INT iDragDelay; static INT fLocalInit = FALSE; // Forward refs void DrawShading(HDC, LPRECT); void DrawHandles (HDC, LPRECT); void DrawDragRgn (HWND, HRGN); HRGN CreateDragRgn(LPRECT); TCHAR szHatchWinClassName[] = TEXT("Hatchwin") ; /* * CHatchWin:CHatchWin * CHatchWin::~CHatchWin * * Constructor Parameters: * hInst HINSTANCE of the application we're in. */ CHatchWin::CHatchWin( VOID ) { m_hWnd = NULL; m_hWndParent = NULL; m_hWndKid = NULL; m_hWndAssociate = NULL; m_hRgnDrag = NULL; m_iBorder = 0; m_uID = 0; m_uDragMode = DRAG_IDLE; m_bResizeInProgress = FALSE; SetRect(&m_rcPos, 0, 0, 0, 0); SetRect(&m_rcClip, 0, 0, 0, 0); return; } CHatchWin::~CHatchWin(void) { if (NULL != m_hWnd) DestroyWindow(m_hWnd); return; } /* * CHatchWin::Init * * Purpose: * Instantiates a hatch window within a given parent with a * default rectangle. This is not initially visible. * * Parameters: * hWndParent HWND of the parent of this window * uID UINT identifier for this window (send in * notifications to associate window). * hWndAssoc HWND of the initial associate. * * Return Value: * BOOL TRUE if the function succeeded, FALSE otherwise. */ BOOL CHatchWin::Init(HWND hWndParent, UINT uID, HWND hWndAssoc) { INT i; HBITMAP hBM; WNDCLASS wc; LONG_PTR lptrID = 0; BEGIN_CRITICAL_SECTION // If first time through if (pstrRegisteredClasses[HATCH_WNDCLASS] == NULL) { // Register the hatch window class wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; wc.hInstance = g_hInstance; wc.cbClsExtra = 0; wc.lpfnWndProc = (WNDPROC)HatchWndProc; wc.cbWndExtra = CBHATCHWNDEXTRA; wc.hIcon = NULL; wc.hCursor = NULL; wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszMenuName = NULL; wc.lpszClassName = szHatchWinClassName; if (RegisterClass(&wc)) { // Save class name for later unregistering pstrRegisteredClasses[HATCH_WNDCLASS] = szHatchWinClassName; // Get system metrics iBorder = GetProfileInt(TEXT("windows"), TEXT("OleInPlaceBorderWidth"), 4); iDragMinDist = GetProfileInt(TEXT("windows"), TEXT("DragMinDist"), DD_DEFDRAGMINDIST); iDragDelay = GetProfileInt(TEXT("windows"), TEXT("DragDelay"), DD_DEFDRAGDELAY); // Load the arrow cursors for (i = IDC_CURS_MIN; i <= IDC_CURS_MAX; i++) { hCursTable[i - IDC_CURS_MIN] = LoadCursor(g_hInstance, MAKEINTRESOURCE(i)); } // Create brushes for hatching and drag region hBM = CreateBitmap(8, 8, 1, 1, wHatchBmp); if ( NULL != hBM ) { hBrHatch = CreatePatternBrush(hBM); DeleteObject(hBM); } hBM = CreateBitmap(8, 8, 1, 1, wGrayBmp); if ( NULL != hBM ) { hBrGray = CreatePatternBrush(hBM); DeleteObject(hBM); } } } END_CRITICAL_SECTION if (pstrRegisteredClasses[HATCH_WNDCLASS] == NULL) return FALSE; lptrID = uID; m_hWnd = CreateWindowEx( WS_EX_NOPARENTNOTIFY, szHatchWinClassName, szHatchWinClassName, WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, 0, 0, 100, 100, hWndParent, (HMENU)lptrID, g_hInstance, this); m_uID = uID; m_hWndAssociate = hWndAssoc; m_hWndParent = hWndParent; return (NULL != m_hWnd); } /* * CHatchWin::HwndAssociateSet * CHatchWin::HwndAssociateGet * * Purpose: * Sets (Set) or retrieves (Get) the associate window of the * hatch window. * * Parameters: (Set only) * hWndAssoc HWND to set as the associate. * * Return Value: * HWND Previous (Set) or current (Get) associate * window. */ HWND CHatchWin::HwndAssociateSet(HWND hWndAssoc) { HWND hWndT = m_hWndAssociate; m_hWndAssociate = hWndAssoc; return hWndT; } HWND CHatchWin::HwndAssociateGet(void) { return m_hWndAssociate; } /* * CHatchWin::RectsSet * * Purpose: * Changes the size and position of the hatch window and the child * window within it using a position rectangle for the child and * a clipping rectangle for the hatch window and child. The hatch * window occupies prcPos expanded by the hatch border and clipped * by prcClip. The child window is fit to prcPos to give the * proper scaling, but it clipped to the hatch window which * therefore clips it to prcClip without affecting the scaling. * * Parameters: * prcPos LPRECT providing the position rectangle. * prcClip LPRECT providing the clipping rectangle. * * Return Value: * None */ void CHatchWin::RectsSet(LPRECT prcPos, LPRECT prcClip) { RECT rc; RECT rcPos; UINT uPosFlags = SWP_NOZORDER | SWP_NOACTIVATE; BOOL bChanged = TRUE; // If new rectangles, save them if (prcPos != NULL) { bChanged = !EqualRect ( prcPos, &m_rcPos ); m_rcPos = *prcPos; // If clipping rect supplied, use it // else just use the position rect again if (prcClip != NULL) { if ( !bChanged ) bChanged = !EqualRect ( prcClip, &m_rcClip ); m_rcClip = *prcClip; } else { m_rcClip = m_rcPos; } } if ( bChanged ) { // Expand position rect to include hatch border rcPos = m_rcPos; InflateRect(&rcPos, m_iBorder, m_iBorder); // Clip with clipping rect to get actual window rect IntersectRect(&rc, &rcPos, &m_rcClip); // Save hatch wnd origin relative to clipped window m_ptHatchOrg.x = rcPos.left - rc.left; m_ptHatchOrg.y = rcPos.top - rc.top; // Set flag to avoid reentrant call from window proc m_bResizeInProgress = TRUE; // Offset child window from hatch rect by border width // (maintaining its original size) SetWindowPos(m_hWndKid, NULL, m_ptHatchOrg.x + m_iBorder, m_ptHatchOrg.y + m_iBorder, m_rcPos.right - m_rcPos.left, m_rcPos.bottom - m_rcPos.top, uPosFlags); // Position the hatch window SetWindowPos(m_hWnd, NULL, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, uPosFlags); m_bResizeInProgress = FALSE; } // This is here to ensure that the control background gets redrawn // On a UI deactivate, the VC test container erases the control window // between the WM_ERASEBKGND and WM_PAINT, so the background ends up // the container color instead of the control color if (m_iBorder == 0) InvalidateRect(m_hWndKid, NULL, TRUE); return; } /* * CHatchWin::ChildSet * * Purpose: * Assigns a child window to this hatch window. * * Parameters: * hWndKid HWND of the child window. * * Return Value: * None */ void CHatchWin::ChildSet(HWND hWndKid) { m_hWndKid = hWndKid; if (NULL != hWndKid) { SetParent(hWndKid, m_hWnd); //Insure this is visible when the hatch window becomes visible. ShowWindow(hWndKid, SW_SHOW); } return; } void CHatchWin::OnLeftDown(INT x, INT y) { m_ptDown.x = x; m_ptDown.y = y; SetCapture(m_hWnd); m_uDragMode = DRAG_PENDING; SetTimer(m_hWnd, IDTIMER_DEBOUNCE, iDragDelay, NULL); } void CHatchWin::OnLeftUp(void) { switch (m_uDragMode) { case DRAG_PENDING: KillTimer(m_hWnd, IDTIMER_DEBOUNCE); ReleaseCapture(); break; case DRAG_ACTIVE: // Erase and release drag region if ( NULL != m_hRgnDrag ) { DrawDragRgn(m_hWndParent, m_hRgnDrag); DeleteObject(m_hRgnDrag); m_hRgnDrag = NULL; } ReleaseCapture(); // Inform associated window of change if ( !EqualRect(&m_rectNew, &m_rcPos) ) { SendMessage(m_hWndAssociate, WM_COMMAND, MAKEWPARAM(m_uID, HWN_RESIZEREQUESTED), (LPARAM)&m_rectNew); } break; } m_uDragMode = DRAG_IDLE; } void CHatchWin::OnMouseMove(INT x, INT y) { INT dx, dy; HRGN hRgnNew, hRgnDiff; UINT uResizeFlags; INT iWidth, iHeight; INT xHit, yHit; static INT xPrev, yPrev; if (x == xPrev && y == yPrev) return; xPrev = x; yPrev = y; switch (m_uDragMode) { case DRAG_IDLE: // Adjust to hatch window coordinates x -= m_ptHatchOrg.x; y -= m_ptHatchOrg.y; iWidth = m_rcPos.right - m_rcPos.left + 2 * m_iBorder; iHeight = m_rcPos.bottom - m_rcPos.top + 2 * m_iBorder; // Determine if x is within a handle if (x <= m_iBorder) xHit = X_LEFT; else if (x >= iWidth - m_iBorder) xHit = X_RIGHT; else if (x >= (iWidth - m_iBorder)/2 && x <= (iWidth + m_iBorder)/2) xHit = X_MIDDLE; else xHit = NO_HIT; // Determine is y within a handle if (y <= m_iBorder) yHit = Y_TOP; else if (y >= iHeight - m_iBorder) yHit = Y_BOTTOM; else if (y > (iHeight - m_iBorder)/2 && y < (iHeight + m_iBorder)/2) yHit = Y_MIDDLE; else yHit = NO_HIT; // Compute handle code // if no handle hit, set to 4 (drag full object) if (xHit != NO_HIT && yHit != NO_HIT) m_uHdlCode = xHit + 3 * yHit; else m_uHdlCode = 4; // Set cursor to match handle SetCursor(hCursTable[uCursIDTable[m_uHdlCode] - IDC_CURS_MIN]); break; case DRAG_PENDING: // Start resize if movement threshold exceeded dx = (x >= m_ptDown.x) ? (x - m_ptDown.x) : (m_ptDown.x - x); dy = (y >= m_ptDown.y) ? (y - m_ptDown.y) : (m_ptDown.y - y); if (dx > iDragMinDist || dy > iDragMinDist) { KillTimer(m_hWnd, IDTIMER_DEBOUNCE); // Create and display initial drag region m_hRgnDrag = CreateDragRgn(&m_rcPos); if ( NULL != m_hRgnDrag ) { DrawDragRgn(m_hWndParent, m_hRgnDrag); // Initialize new rect m_rectNew = m_rcPos; m_uDragMode = DRAG_ACTIVE; } } break; case DRAG_ACTIVE: dx = x - m_ptDown.x; dy = y - m_ptDown.y; // Compute new rect by applying deltas to selected edges // of original position rect uResizeFlags = uSizingTable[m_uHdlCode]; if (uResizeFlags & SIZING_ALL) { m_rectNew.left = m_rcPos.left + dx; m_rectNew.top = m_rcPos.top + dy; m_rectNew.right = m_rcPos.right + dx; m_rectNew.bottom = m_rcPos.bottom + dy; } else { if (uResizeFlags & SIZING_TOP) { m_rectNew.top = m_rcPos.top + dy; if (m_rectNew.bottom - m_rectNew.top < MIN_SIZE) m_rectNew.top = m_rectNew.bottom - MIN_SIZE; } if (uResizeFlags & SIZING_BOTTOM) { m_rectNew.bottom = m_rcPos.bottom + dy; if (m_rectNew.bottom - m_rectNew.top < MIN_SIZE) m_rectNew.bottom = m_rectNew.top + MIN_SIZE; } if (uResizeFlags & SIZING_LEFT) { m_rectNew.left = m_rcPos.left + dx; if (m_rectNew.right - m_rectNew.left < MIN_SIZE) m_rectNew.left = m_rectNew.right - MIN_SIZE; } if (uResizeFlags & SIZING_RIGHT) { m_rectNew.right = m_rcPos.right + dx; if (m_rectNew.right - m_rectNew.left < MIN_SIZE) m_rectNew.right = m_rectNew.left + MIN_SIZE; } } // Compute new drag region hRgnNew = CreateDragRgn(&m_rectNew); if ( NULL != hRgnNew ) { // Repaint difference between old and new regions (No Flicker!) hRgnDiff = CreateRectRgn(0,0,0,0); if ( NULL != m_hRgnDrag && NULL != hRgnDiff ) { CombineRgn(hRgnDiff, m_hRgnDrag, hRgnNew, RGN_XOR); DrawDragRgn(m_hWndParent, hRgnDiff); } else { DrawDragRgn(m_hWndParent, hRgnNew); } if ( NULL != hRgnDiff ) { DeleteObject ( hRgnDiff ); } // Update current region if ( NULL != m_hRgnDrag ) { DeleteObject(m_hRgnDrag); } m_hRgnDrag = hRgnNew; } } } void CHatchWin::OnTimer() { if ( DRAG_PENDING == m_uDragMode ) { KillTimer(m_hWnd, IDTIMER_DEBOUNCE); // Create and display initial drag region m_hRgnDrag = CreateDragRgn(&m_rcPos); if ( NULL != m_hRgnDrag ) { DrawDragRgn(m_hWndParent, m_hRgnDrag); // Initialize new rect m_rectNew = m_rcPos; m_uDragMode = DRAG_ACTIVE; } } } void CHatchWin::OnPaint() { HDC hDC; RECT rc; PAINTSTRUCT ps; INT iWidth, iHeight; hDC = BeginPaint(m_hWnd, &ps); // setup hatch rect in window's coord system iWidth = m_rcPos.right - m_rcPos.left + 2 * m_iBorder; iHeight = m_rcPos.bottom - m_rcPos.top + 2 * m_iBorder; SetRect(&rc, m_ptHatchOrg.x, m_ptHatchOrg.y, m_ptHatchOrg.x + iWidth, m_ptHatchOrg.y + iHeight); DrawShading(hDC, &rc); DrawHandles(hDC, &rc); EndPaint(m_hWnd, &ps); } /* * CHatchWin::ShowHatch * * Purpose: * Turns hatching on and off; turning the hatching off changes * the size of the window to be exactly that of the child, leaving * everything else the same. The result is that we don't have * to turn off drawing because our own WM_PAINT will never be * called. * * Parameters: * fHatch BOOL indicating to show (TRUE) or hide (FALSE) the hatching. * * Return Value: * None */ void CHatchWin::ShowHatch(BOOL fHatch) { /* * All we have to do is set the border to zero and * call SetRects again with the last rectangles the * child sent to us. */ m_iBorder = fHatch ? iBorder : 0; RectsSet(NULL, NULL); return; } /* * CHatchWin::Window * * Purpose: * Returns the window handle associated with this object. * * Return Value: * HWND Window handle for this object */ HWND CHatchWin::Window(void) { return m_hWnd; } /* * HatchWndProc * * Purpose: * Standard window procedure for the Hatch Window */ LRESULT APIENTRY HatchWndProc(HWND hWnd, UINT iMsg , WPARAM wParam, LPARAM lParam) { PCHatchWin phw; phw = (PCHatchWin)GetWindowLongPtr(hWnd, HWWL_STRUCTURE); switch (iMsg) { case WM_CREATE: phw = (PCHatchWin)((LPCREATESTRUCT)lParam)->lpCreateParams; SetWindowLongPtr(hWnd, HWWL_STRUCTURE, (INT_PTR)phw); break; case WM_DESTROY: phw->m_hWnd = NULL; break; case WM_PAINT: phw->OnPaint(); break; case WM_SIZE: // If this resize is not due to RectsSet then forward it // to adjust our internal control window if (!phw->m_bResizeInProgress) { RECT rc; POINT pt; // Get new rect in container coords GetWindowRect(hWnd, &rc); // Convert to parent client coords pt.x = pt.y = 0; ClientToScreen(GetParent(hWnd), &pt); OffsetRect(&rc,-pt.x, -pt.y); // Resize control phw->RectsSet(&rc, NULL); } break; case WM_MOUSEMOVE: phw->OnMouseMove((short)LOWORD(lParam),(short)HIWORD(lParam)); break; case WM_LBUTTONDOWN: phw->OnLeftDown((short)LOWORD(lParam),(short)HIWORD(lParam)); break; case WM_LBUTTONUP: phw->OnLeftUp(); break; case WM_TIMER: phw->OnTimer(); break; case WM_SETFOCUS: //We need this since the container will SetFocus to us. if (NULL != phw->m_hWndKid) SetFocus(phw->m_hWndKid); break; case WM_LBUTTONDBLCLK: /* * If the double click was within m_dBorder of an * edge, send the HWN_BORDERDOUBLECLICKED notification. * * Because we're always sized just larger than our child * window by the border width, we can only *get* this * message when the mouse is on the border. So we can * just send the notification. */ if (NULL!=phw->m_hWndAssociate) { SendMessage(phw->m_hWndAssociate, WM_COMMAND, MAKEWPARAM(phw->m_uID,HWN_BORDERDOUBLECLICKED), (LPARAM)hWnd); } break; default: return DefWindowProc(hWnd, iMsg, wParam, lParam); } return 0L; } HRGN CreateDragRgn(LPRECT pRect) { HRGN hRgnIn; HRGN hRgnOut; HRGN hRgnRet = NULL; if ( NULL != pRect ) { hRgnRet = CreateRectRgn(0,0,0,0); hRgnIn = CreateRectRgn(pRect->left, pRect->top,pRect->right, pRect->bottom); hRgnOut = CreateRectRgn(pRect->left - iBorder, pRect->top - iBorder, pRect->right + iBorder, pRect->bottom + iBorder); if ( NULL != hRgnOut && NULL != hRgnIn && NULL != hRgnRet ) { CombineRgn(hRgnRet, hRgnOut, hRgnIn, RGN_DIFF); } if ( NULL != hRgnIn ) { DeleteObject(hRgnIn); } if ( NULL != hRgnOut ) { DeleteObject(hRgnOut); } } return hRgnRet; } void DrawDragRgn(HWND hWnd, HRGN hRgn) { LONG lWndStyle; INT iMapMode; HDC hDC; RECT rc; HBRUSH hBr; COLORREF crText; // Turn off clipping by children lWndStyle = GetWindowLong(hWnd, GWL_STYLE); SetWindowLong(hWnd, GWL_STYLE, lWndStyle & ~WS_CLIPCHILDREN); // Prepare DC hDC = GetDC(hWnd); if ( NULL != hDC ) { iMapMode = SetMapMode(hDC, MM_TEXT); hBr = (HBRUSH)SelectObject(hDC, hBrGray); crText = SetTextColor(hDC, RGB(255, 255, 255)); SelectClipRgn(hDC, hRgn); GetClipBox(hDC, &rc); PatBlt(hDC, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, PATINVERT); // Restore DC SelectObject(hDC, hBr); SetTextColor(hDC, crText); SetMapMode(hDC, iMapMode); SelectClipRgn(hDC, NULL); ReleaseDC(hWnd, hDC); } SetWindowLong(hWnd, GWL_STYLE, lWndStyle); } /* * DrawShading * * Purpose: * Draw a hatch border ourside the rectable given. * * Parameters: * prc LPRECT containing the rectangle. * hDC HDC on which to draw. * cWidth UINT width of the border to draw. Ignored * if dwFlags has UI_SHADE_FULLRECT. * * Return Value: * None */ void DrawShading(HDC hDC, LPRECT prc) { HBRUSH hBROld; RECT rc; UINT cx, cy; COLORREF crText; COLORREF crBk; const DWORD dwROP = 0x00A000C9L; //DPa if (NULL==prc || NULL==hDC) return; hBROld = (HBRUSH)SelectObject(hDC, hBrHatch); rc = *prc; cx = rc.right - rc.left; cy = rc.bottom - rc.top; crText = SetTextColor(hDC, RGB(255, 255, 255)); crBk = SetBkColor(hDC, RGB(0, 0, 0)); PatBlt(hDC, rc.left, rc.top, cx, iBorder, dwROP); PatBlt(hDC, rc.left, rc.top, iBorder, cy, dwROP); PatBlt(hDC, rc.right-iBorder, rc.top, iBorder, cy, dwROP); PatBlt(hDC, rc.left, rc.bottom-iBorder, cx, iBorder, dwROP); SetTextColor(hDC, crText); SetBkColor(hDC, crBk); SelectObject(hDC, hBROld); return; } void DrawHandles (HDC hDC, LPRECT prc) { HPEN hPenOld; HBRUSH hBROld; INT left,right,top,bottom; #define DrawHandle(x,y) Rectangle(hDC, x, y, (x) + iBorder + 1, (y) + iBorder + 1) hPenOld = (HPEN)SelectObject(hDC, (HPEN)GetStockObject(BLACK_PEN)); hBROld = (HBRUSH)SelectObject(hDC, (HBRUSH)GetStockObject(BLACK_BRUSH)); left = prc->left; right = prc->right - iBorder; top = prc->top; bottom = prc->bottom - iBorder; DrawHandle(left, top); DrawHandle(left, (top + bottom)/2); DrawHandle(left, bottom); DrawHandle(right, top); DrawHandle(right, (top + bottom)/2); DrawHandle(right, bottom); DrawHandle((left + right)/2, top); DrawHandle((left + right)/2, bottom); SelectObject(hDC, hPenOld); SelectObject(hDC, hBROld); }