DRAG.C
上传用户:bangxh
上传日期:2007-01-31
资源大小:42235k
文件大小:58k
源码类别:

Windows编程

开发平台:

Visual C++

  1. /******************************************************************************
  2. *       This is a part of the Microsoft Source Code Samples. 
  3. *       Copyright (C) 1993-1997 Microsoft Corporation.
  4. *       All rights reserved. 
  5. *       This source code is only intended as a supplement to 
  6. *       Microsoft Development Tools and/or WinHelp documentation.
  7. *       See these sources for detailed information regarding the 
  8. *       Microsoft samples programs.
  9. ******************************************************************************/
  10. /****************************** Module Header *******************************
  11. * Module Name: drag.c
  12. *
  13. * Contains routines for dragging and sizing controls.
  14. *
  15. * Functions:
  16. *    ShowTrackRect()
  17. *    HideTrackRect()
  18. *    FitRectToBounds()
  19. *    GetOverHang()
  20. *    GridizeRect()
  21. *    SizeDragToControl()
  22. *    DragWndProc()
  23. *    DrawHandles()
  24. *    HandleHitTest()
  25. *    CtrlButtonDown()
  26. *    DragNewBegin()
  27. *    CtrlMouseMove()
  28. *    PreDragTimeout()
  29. *    DragCancel()
  30. *    CtrlButtonUp()
  31. *    DragEnd()
  32. *    CalcCursorOffset()
  33. *    InitTracking()
  34. *    DrawTrackRect()
  35. *    CancelTracking()
  36. *    AtOrAbove()
  37. *    AtOrBelow()
  38. *    PaintUnderDrag()
  39. *    MouseToDragRect()
  40. *    MouseToDU()
  41. *    DragBegin()
  42. *    CtrlHitTest()
  43. *    DragBegin2()
  44. *
  45. * Comments:
  46. *
  47. ****************************************************************************/
  48. #include "dlgedit.h"
  49. #include "dlgfuncs.h"
  50. #include "dlgextrn.h"
  51. #include <stdlib.h>
  52. STATICFN VOID CalcCursorOffset(POINT *ppt);
  53. STATICFN VOID InitTracking(VOID);
  54. STATICFN VOID DrawTrackRect(PRECT prc, BOOL fDialog, BOOL fDraw);
  55. STATICFN VOID CancelTracking(VOID);
  56. STATICFN INT AtOrAbove(INT nStart, INT nGrid);
  57. STATICFN INT AtOrBelow(INT nStart, INT nGrid);
  58. STATICFN VOID PaintUnderDrag(HWND hwndDrag);
  59. STATICFN VOID MouseToDragRect(INT x, INT y, PRECT prc);
  60. STATICFN VOID MouseToDU(PPOINT ppt);
  61. STATICFN VOID DragBegin(HWND hwnd, INT x, INT y, BOOL fHandleWindow);
  62. STATICFN HWND CtrlHitTest(HWND hwnd, PPOINT ppt);
  63. STATICFN VOID DragBegin2(PPOINT ppt);
  64. /*
  65.  * This contains the initial location of the mouse when going into
  66.  * pre-drag mode.  If the mouse pointer is moved too far away from
  67.  * this point, we will start the drag operation, even if the pre-drag
  68.  * timer has not elapsed yet.
  69.  */
  70. static POINT gptPreDragStart;
  71. /************************************************************************
  72. * CalcCursorOffset
  73. *
  74. * This routine updates the gptCursorOffset point.  This is used during
  75. * dragging operations.  It contains the offset from the mouse pointer
  76. * at the time a dragging operation is begun and the upper left corner
  77. * of the dragging rectangle.  This value is needed for determining
  78. * where mouse events are occuring in relation to where the drag was
  79. * initially begun from.
  80. *
  81. * Arguments:
  82. *    POINT - offset where the mouse pointer began drag.
  83. *
  84. ************************************************************************/
  85. STATICFN VOID CalcCursorOffset(
  86.     POINT *ppt)
  87. {
  88.     RECT rc;
  89.     POINT pt;
  90.     if (gfDlgSelected) {
  91.         gptCursorOffset = *ppt;
  92.     }
  93.     else {
  94.         rc = grcSelected;
  95.         pt = *ppt;
  96.         DUToWinRect(&rc);
  97.         ClientToScreen(gnpcSel->hwnd, &pt);
  98.         ScreenToClient(gcd.npc->hwnd, &pt);
  99.         gptCursorOffset.x = pt.x - rc.left;
  100.         gptCursorOffset.y = pt.y - rc.top;
  101.     }
  102. }
  103. /************************************************************************
  104. * InitTracking
  105. *
  106. * This function initializes a tracking operation.  The pointer is
  107. * changed to be the system "move" pointer if we are moving the
  108. * control (not sizing it).
  109. *
  110. ************************************************************************/
  111. STATICFN VOID InitTracking(VOID)
  112. {
  113.     if (gfDlgSelected)
  114.         ghDCTrack = CreateDC(L"DISPLAY", NULL, NULL, NULL);
  115.     else
  116.         ghDCTrack = GetDC(gcd.npc->hwnd);
  117.     SetROP2(ghDCTrack, R2_NOT);
  118. }
  119. /************************************************************************
  120. * DrawTrackRect
  121. *
  122. * This routine draws the drag rectangle.  It is assumed that the window
  123. * has been locked for update appropriately or this could leave garbage
  124. * around.  The rectangle given is in dialog units, and is converted
  125. * to window coordinates using different rules based on the value of
  126. * fDialog.  After this routine has been called to set the rectangle,
  127. * the HideTrackRect and ShowTrackRect functions can be called to
  128. * temporarily hide the track rectangle, but this routine must be called
  129. * again every time that the tracking rectangle is to be changed.
  130. *
  131. * Arguments:
  132. *   PRECT prc     - Drag rectangle to draw (in dialog units).
  133. *   BOOL fDialog  - TRUE if the control being dragged is the dialog.
  134. *   BOOL fDraw    - If TRUE, the rectangle will be drawn.  Having this
  135. *                   FALSE is useful to just initialize the state globals,
  136. *                   but defer the drawing of the rectangle until the mouse
  137. *                   is moved from its starting point.
  138. *
  139. ************************************************************************/
  140. STATICFN VOID DrawTrackRect(
  141.     PRECT prc,
  142.     BOOL fDialog,
  143.     BOOL fDraw)
  144. {
  145.     HideTrackRect();
  146.     grcTrackWin = grcTrackDU = *prc;
  147.     DUToWinRect(&grcTrackWin);
  148.     if (fDialog) {
  149.         AdjustWindowRectEx(&grcTrackWin, gcd.npc->flStyle, FALSE,
  150.                 (gcd.npc->flStyle & DS_MODALFRAME) ?
  151.                 gcd.npc->flExtStyle | WS_EX_DLGMODALFRAME :
  152.                 gcd.npc->flExtStyle);
  153.         ClientToScreenRect(ghwndSubClient, &grcTrackWin);
  154.     }
  155.     if (fDraw)
  156.         ShowTrackRect();
  157. }
  158. /************************************************************************
  159. * ShowTrackRect
  160. *
  161. * This routine shows the current tracking rectangle.
  162. *
  163. ************************************************************************/
  164. VOID ShowTrackRect(VOID)
  165. {
  166.     if (!gfTrackRectShown) {
  167.         MyFrameRect(ghDCTrack, &grcTrackWin, DSTINVERT);
  168.         gfTrackRectShown = TRUE;
  169.     }
  170. }
  171. /************************************************************************
  172. * HideTrackRect
  173. *
  174. * This routine hides the current tracking rectangle.
  175. *
  176. ************************************************************************/
  177. VOID HideTrackRect(VOID)
  178. {
  179.     if (gfTrackRectShown) {
  180.         MyFrameRect(ghDCTrack, &grcTrackWin, DSTINVERT);
  181.         gfTrackRectShown = FALSE;
  182.     }
  183. }
  184. /************************************************************************
  185. * CancelTracking
  186. *
  187. * This routine is used to cancel the display of the tracking rectangle.
  188. * It is basically the opposite of InitTracking.
  189. *
  190. ************************************************************************/
  191. STATICFN VOID CancelTracking(VOID)
  192. {
  193.     if (gfTrackRectShown) {
  194.         HideTrackRect();
  195.         if (gfDlgSelected)
  196.             DeleteDC(ghDCTrack);
  197.         else
  198.             ReleaseDC(gcd.npc->hwnd, ghDCTrack);
  199.     }
  200. }
  201. /************************************************************************
  202. * FitRectToBounds
  203. *
  204. * This routine fits the given rectangle to the appropriate boundary.
  205. * If fDialog is FALSE, the rectangle is a control and it must fall
  206. * entirely within the area of the current dialog being edited.  If the
  207. * rectangle is adjusted to fit, the moved edge(s) will be aligned on
  208. * a grid boundary.  The wHandleHit parameter is used to tell this routine
  209. * what edges are allowed to move, in other words, what edges are
  210. * "anchored" down and what edges are being tracked.
  211. *
  212. * Arguments:
  213. *   PRECT prc     - Rectangle to be adjusted to the allowed size.
  214. *   INT nOverHang - How much the control can hang below the dialog.
  215. *                   This is primarily for the combobox listboxes.
  216. *   INT HandleHit - One of the DRAG_* constants.
  217. *   BOOL fDialog  - TRUE if the rectangle is for a dialog.
  218. *
  219. ************************************************************************/
  220. VOID FitRectToBounds(
  221.     PRECT prc,
  222.     INT nOverHang,
  223.     INT HandleHit,
  224.     BOOL fDialog)
  225. {
  226.     INT cxDlg;
  227.     INT cyDlg;
  228.     INT dx;
  229.     INT dy;
  230.     /*
  231.      * Are we just moving the control (not sizing)?
  232.      */
  233.     if (HandleHit == DRAG_CENTER) {
  234.         /*
  235.          * We only do range checking if it is a control (not on the dialog).
  236.          */
  237.         if (!fDialog) {
  238.             dx = prc->right - prc->left;
  239.             dy = prc->bottom - prc->top;
  240.             cxDlg = gcd.npc->rc.right - gcd.npc->rc.left;
  241.             cyDlg = gcd.npc->rc.bottom - gcd.npc->rc.top + nOverHang;
  242.             if (prc->right > cxDlg) {
  243.                 prc->left = AtOrBelow(cxDlg - dx, gcxGrid);
  244.                 prc->right = prc->left + dx;
  245.             }
  246.             if (prc->left < 0) {
  247.                 prc->left = 0;
  248.                 prc->right = prc->left + dx;
  249.             }
  250.             if (prc->bottom > cyDlg) {
  251.                 prc->top = AtOrBelow(cyDlg - dy, gcyGrid);
  252.                 prc->bottom = prc->top + dy;
  253.             }
  254.             if (prc->top < 0) {
  255.                 prc->top = 0;
  256.                 prc->bottom = prc->top + dy;
  257.             }
  258.         }
  259.         return;
  260.     }
  261.     if (fDialog) {
  262.         /*
  263.          * When dealing with the dialog, we want to take into account
  264.          * the controls so that the dialog is never sized to hide a
  265.          * control.  This routine assumes that grcMinDialog has already
  266.          * been set to enclose the controls.  If the dialog has no
  267.          * controls, this rectangle is not used, but the dialog's size
  268.          * is still limited so that it never goes negative.
  269.          */
  270.         /*
  271.          * First deal with the x coordinates.
  272.          */
  273.         switch (HandleHit) {
  274.             case DRAG_LEFTBOTTOM:
  275.             case DRAG_LEFT:
  276.             case DRAG_LEFTTOP:
  277.                 if (npcHead) {
  278.                     if (prc->left > grcMinDialog.left)
  279.                         prc->left = AtOrBelow(grcMinDialog.left, gcxGrid);
  280.                 }
  281.                 else {
  282.                     if (prc->left > prc->right)
  283.                         prc->left = AtOrBelow(prc->right, gcxGrid);
  284.                 }
  285.                 break;
  286.             case DRAG_RIGHTBOTTOM:
  287.             case DRAG_RIGHT:
  288.             case DRAG_RIGHTTOP:
  289.                 if (npcHead) {
  290.                     if (prc->right < grcMinDialog.right)
  291.                         prc->right = AtOrAbove(grcMinDialog.right, gcxGrid);
  292.                 }
  293.                 else {
  294.                     if (prc->right < prc->left)
  295.                         prc->right = AtOrAbove(prc->left, gcxGrid);
  296.                 }
  297.                 break;
  298.         }
  299.         /*
  300.          * Now deal with the y coordinates.
  301.          */
  302.         switch (HandleHit) {
  303.             case DRAG_LEFTBOTTOM:
  304.             case DRAG_BOTTOM:
  305.             case DRAG_RIGHTBOTTOM:
  306.                 if (npcHead) {
  307.                     if (prc->bottom < grcMinDialog.bottom)
  308.                         prc->bottom = AtOrAbove(grcMinDialog.bottom, gcyGrid);
  309.                 }
  310.                 else {
  311.                     if (prc->bottom < prc->top)
  312.                         prc->bottom = AtOrAbove(prc->top, gcyGrid);
  313.                 }
  314.                 break;
  315.             case DRAG_LEFTTOP:
  316.             case DRAG_TOP:
  317.             case DRAG_RIGHTTOP:
  318.                 if (npcHead) {
  319.                     if (prc->top > grcMinDialog.top)
  320.                         prc->top = AtOrBelow(grcMinDialog.top, gcyGrid);
  321.                 }
  322.                 else {
  323.                     if (prc->top > prc->bottom)
  324.                         prc->top = AtOrBelow(prc->bottom, gcyGrid);
  325.                 }
  326.                 break;
  327.         }
  328.     }
  329.     else {
  330.         /*
  331.          * First deal with the x coordinates.
  332.          */
  333.         switch (HandleHit) {
  334.             case DRAG_LEFTBOTTOM:
  335.             case DRAG_LEFT:
  336.             case DRAG_LEFTTOP:
  337.                 if (prc->left > prc->right)
  338.                     prc->left = AtOrBelow(prc->right, gcxGrid);
  339.                 if (prc->left == prc->right)
  340.                     prc->left -= gcxGrid;
  341.                 if (prc->left < 0)
  342.                     prc->left = 0;
  343.                 break;
  344.             case DRAG_RIGHTBOTTOM:
  345.             case DRAG_RIGHT:
  346.             case DRAG_RIGHTTOP:
  347.                 cxDlg = gcd.npc->rc.right - gcd.npc->rc.left;
  348.                 if (prc->right > cxDlg)
  349.                     prc->right = AtOrBelow(cxDlg, gcxGrid);
  350.                 if (prc->right < prc->left)
  351.                     prc->right = AtOrAbove(prc->left, gcxGrid);
  352.                 if (prc->right == prc->left)
  353.                     prc->right += gcxGrid;
  354.                 break;
  355.         }
  356.         /*
  357.          * Now deal with the y coordinates.
  358.          */
  359.         switch (HandleHit) {
  360.             case DRAG_LEFTTOP:
  361.             case DRAG_TOP:
  362.             case DRAG_RIGHTTOP:
  363.                 if (prc->top > prc->bottom)
  364.                     prc->top = AtOrBelow(prc->bottom, gcyGrid);
  365.                 if (prc->top == prc->bottom)
  366.                     prc->top -= gcyGrid;
  367.                 if (prc->top < 0)
  368.                     prc->top = 0;
  369.                 break;
  370.             case DRAG_LEFTBOTTOM:
  371.             case DRAG_BOTTOM:
  372.             case DRAG_RIGHTBOTTOM:
  373.                 cyDlg = gcd.npc->rc.bottom - gcd.npc->rc.top;
  374.                 /*
  375.                  * Note that if there is an overhang allowed, then
  376.  * we do not limit how far down the bottom of the
  377.                  * control can be.
  378.                  */
  379.                 if (prc->bottom > cyDlg && !nOverHang)
  380.                     prc->bottom = AtOrBelow(cyDlg, gcyGrid);
  381.                 if (prc->bottom < prc->top)
  382.                     prc->bottom = AtOrAbove(prc->top, gcyGrid);
  383.                 if (prc->bottom == prc->top)
  384.                     prc->bottom += gcyGrid;
  385.                 break;
  386.         }
  387.     }
  388. }
  389. /************************************************************************
  390. * AtOrAbove
  391. *
  392. * This routine takes a number, and returns the closest number that
  393. * is equal to or above that number and is an integral of the given
  394. * grid value.
  395. *
  396. * Arguments:
  397. *   INT nStart - Starting number (can be negative).
  398. *   INT nGrid  - Grid value.
  399. *
  400. ************************************************************************/
  401. STATICFN INT AtOrAbove(
  402.     INT nStart,
  403.     INT nGrid)
  404. {
  405.     register INT nAbove;
  406.     nAbove = (nStart / nGrid) * nGrid;
  407.     if (nStart > 0 && nStart != nAbove)
  408.         nAbove += nGrid;
  409.     return nAbove;
  410. }
  411. /************************************************************************
  412. * AtOrBelow
  413. *
  414. * This routine takes a number, and returns the closest number that
  415. * is equal to or below that number and is an integral of the given
  416. * grid value.
  417. *
  418. * Arguments:
  419. *   INT nStart - Starting number (can be negative).
  420. *   INT nGrid  - Grid value.
  421. *
  422. ************************************************************************/
  423. STATICFN INT AtOrBelow(
  424.     INT nStart,
  425.     INT nGrid)
  426. {
  427.     register INT nBelow;
  428.     nBelow = (nStart / nGrid) * nGrid;
  429.     if (nStart < 0 && nStart != nBelow)
  430.         nBelow -= nGrid;
  431.     return nBelow;
  432. }
  433. /************************************************************************
  434. * GetOverHang
  435. *
  436. * This function returns the height that the control can overhang the
  437. * bottom of the dialog.  This is currently only meaningful for comboboxes.
  438. * If the control is not a combobox, zero is returned.
  439. *
  440. * Arguments:
  441. *   INT iType - Type of control (W_* constant).
  442. *   INT cy    - Height of the control (in DU's).
  443. *
  444. ************************************************************************/
  445. INT GetOverHang(
  446.     INT iType,
  447.     INT cy)
  448. {
  449.     if (iType != W_COMBOBOX)
  450.         return 0;
  451.     return max(cy - COMBOEDITHEIGHT, 0);
  452. }
  453. /************************************************************************
  454. * GridizeRect
  455. *
  456. * This function "gridizes" coordinates in a rectangle.  The current
  457. * grid values are used.  The fsGrid flag can contain OR'd together
  458. * GRIDIZE_* values that specify which points to apply the gridding to.
  459. * Upon return, all coordinates specified will have been rounded to the
  460. * nearest grid boundary.
  461. *
  462. * If GRIDIZE_SAMESIZE is specified, the size of the control will be
  463. * kept the same.  This overrides the GRIDIZE_RIGHT and GRIDIZE_BOTTOM
  464. * flags.  In other words, any delta applied to left or top will
  465. * be added to right and bottom to retain the original size of the
  466. * rectangle.
  467. *
  468. * Arguments:
  469. *   PRECT prc       - Rectangle to adjust to the current grid.
  470. *   INT fGridFlags  - GRIDIZE_* flags.  Specifies which points to gridize.
  471. *
  472. ************************************************************************/
  473. VOID GridizeRect(
  474.     PRECT prc,
  475.     INT fGridFlags)
  476. {
  477.     register INT nTemp;
  478.     INT leftOld = prc->left;
  479.     INT topOld = prc->top;
  480.     if (fGridFlags & GRIDIZE_LEFT) {
  481.         nTemp = AtOrBelow(prc->left, gcxGrid);
  482.         if (prc->left - nTemp > gcxGrid / 2)
  483.             nTemp += gcxGrid;
  484.         prc->left = nTemp;
  485.     }
  486.     if (fGridFlags & GRIDIZE_TOP) {
  487.         nTemp = AtOrBelow(prc->top, gcyGrid);
  488.         if (prc->top - nTemp > gcyGrid / 2)
  489.             nTemp += gcyGrid;
  490.         prc->top = nTemp;
  491.     }
  492.     /*
  493.      * Do they want to retain the same size of the rectangle?
  494.      */
  495.     if (fGridFlags & GRIDIZE_SAMESIZE) {
  496.         /*
  497.          * Shift the right coordinate over by the delta that
  498.          * was applied to the left.
  499.          */
  500.         prc->right += prc->left - leftOld;
  501.         prc->bottom += prc->top - topOld;
  502.     }
  503.     else {
  504.         if (fGridFlags & GRIDIZE_RIGHT) {
  505.             nTemp = AtOrBelow(prc->right, gcxGrid);
  506.             if (prc->right - nTemp > gcxGrid / 2)
  507.                 nTemp += gcxGrid;
  508.             prc->right = nTemp;
  509.         }
  510.         if (fGridFlags & GRIDIZE_BOTTOM) {
  511.             nTemp = AtOrBelow(prc->bottom, gcyGrid);
  512.             if (prc->bottom - nTemp > gcyGrid / 2)
  513.                 nTemp += gcyGrid;
  514.             prc->bottom = nTemp;
  515.         }
  516.     }
  517. }
  518. /************************************************************************
  519. * SizeDragToControl
  520. *
  521. * This routine sizes and positions the drag window associated with a
  522. * control, based on the current size and position of the control.
  523. *
  524. * It takes into account the different origin that controls and dialogs
  525. * have, and sizes the drag window to fit around the control properly.
  526. * The Z order of the drag window is NOT changed.
  527. *
  528. * This routine should only be called for controls, not the dialog.
  529. *
  530. * Arguments:
  531. *   NPCTYPE npc - Control whose drag window needs to be sized.
  532. *
  533. ************************************************************************/
  534. VOID SizeDragToControl(
  535.     NPCTYPE npc)
  536. {
  537.     RECT rc;
  538.     rc = npc->rc;
  539.     DUToWinRect(&rc);
  540.     InflateRect(&rc, CHANDLESIZE / 2, CHANDLESIZE / 2);
  541.     SetWindowPos(npc->hwndDrag, NULL, rc.left, rc.top,
  542.             rc.right - rc.left, rc.bottom - rc.top,
  543.             SWP_NOACTIVATE | SWP_NOZORDER);
  544. }
  545. /************************************************************************
  546. * DragWndProc
  547. *
  548. * This is the window procedure for the "drag" class.  This window
  549. * is placed behind a control and is what the user grabs to size a
  550. * window with the mouse.
  551. *
  552. ************************************************************************/
  553. WINDOWPROC DragWndProc(
  554.     HWND hwnd,
  555.     UINT msg,
  556.     WPARAM wParam,
  557.     LPARAM lParam)
  558. {
  559.     POINT pt;
  560.     switch (msg) {
  561.         case WM_PAINT:
  562.             {
  563.                 PAINTSTRUCT ps;
  564.                 HDC hDC;
  565.                 PaintUnderDrag(hwnd);
  566.                 hDC = BeginPaint(hwnd, &ps);
  567.                 DrawHandles(hwnd, hDC,
  568.                         (gnpcSel && hwnd == gnpcSel->hwndDrag) ? TRUE : FALSE);
  569.                 EndPaint(hwnd, &ps);
  570.             }
  571.             break;
  572.         case WM_NCHITTEST:
  573.             ((pt).x = ((*((POINTS *)&(lParam)))).x, (pt).y = ((*((POINTS *)&(lParam)))).y);
  574.             ScreenToClient(hwnd, &pt);
  575.             if (HandleHitTest(hwnd, pt.x, pt.y) == DRAG_CENTER)
  576.                 return HTTRANSPARENT;
  577.             else
  578.                 return HTCLIENT;
  579.         case WM_SETCURSOR:
  580.             /*
  581.              * Defeat the system changing cursors on us.  We do it based
  582.              * on our own hit testing.
  583.              */
  584.             break;
  585.         case WM_LBUTTONDOWN:
  586.             ((pt).x = ((*((POINTS *)&(lParam)))).x, (pt).y = ((*((POINTS *)&(lParam)))).y);
  587.             CtrlButtonDown(hwnd, pt.x, pt.y, TRUE);
  588.             break;
  589.         case WM_MOUSEMOVE:
  590.             ((pt).x = ((*((POINTS *)&(lParam)))).x, (pt).y = ((*((POINTS *)&(lParam)))).y);
  591.             CtrlMouseMove(hwnd, TRUE, pt.x, pt.y);
  592.             break;
  593.         case WM_LBUTTONUP:
  594.             ((pt).x = ((*((POINTS *)&(lParam)))).x, (pt).y = ((*((POINTS *)&(lParam)))).y);
  595.             CtrlButtonUp(pt.x, pt.y);
  596.             break;
  597.         case WM_RBUTTONDOWN:
  598.         case WM_MBUTTONDOWN:
  599.         case WM_LBUTTONDBLCLK:
  600.         case WM_RBUTTONDBLCLK:
  601.         case WM_MBUTTONDBLCLK:
  602.             /*
  603.              * Prevents calling SetFocus when the middle or right
  604.              * mouse buttons are pressed (or doubleclicked).
  605.              */
  606.             break;
  607.         case WM_NCCALCSIZE:
  608.             /*
  609.              * The client area is the entire control.
  610.              */
  611.             break;
  612.         case WM_DESTROY:
  613.             /*
  614.              * When destroying the drag window, we must be sure and
  615.              * remove the properties associated with it.
  616.              */
  617.             UNSETPCINTOHWND(hwnd);
  618.             break;
  619.         default:
  620.             return DefWindowProc(hwnd, msg, wParam, lParam);
  621.     }
  622.     return 0L;
  623. }
  624. /************************************************************************
  625. * DrawHandles
  626. *
  627. * This routine draws the drag handles for a drag window.  The handles
  628. * will be solid (filled) if fCurrentSelection is TRUE, or hollow if it
  629. * is FALSE.
  630. *
  631. * Arguments:
  632. *   HWND hwnd              - Drag window handle.
  633. *   HDC hDC                - DC to use to draw in this window.
  634. *   BOOL fCurrentSelection - TRUE if this control is the "current"
  635. *                            selection.
  636. *
  637. ************************************************************************/
  638. VOID DrawHandles(
  639.     HWND hwnd,
  640.     HDC hDC,
  641.     BOOL fCurrentSelection)
  642. {
  643.     RECT rc;
  644.     INT xMid;
  645.     INT yMid;
  646.     INT x2;
  647.     INT y2;
  648.     HBITMAP hbmOld;
  649.     GetWindowRect(hwnd, &rc);
  650.     OffsetRect(&rc, -rc.left, -rc.top);
  651.     /*
  652.      * Precalculate some points.
  653.      */
  654.     xMid = ((rc.right + 1) / 2) - (CHANDLESIZE / 2);
  655.     yMid = ((rc.bottom + 1) / 2) - (CHANDLESIZE / 2);
  656.     x2 = rc.right - CHANDLESIZE;
  657.     y2 = rc.bottom - CHANDLESIZE;
  658.     /*
  659.      * Draw a solid box if this is the currently selected
  660.      * control, otherwise draw a hollow box.
  661.      */
  662.     if (fCurrentSelection)
  663.         hbmOld = SelectObject(ghDCMem, ghbmDragHandle);
  664.     else
  665.         hbmOld = SelectObject(ghDCMem, ghbmDragHandle2);
  666.     BitBlt(hDC, 0, 0, CHANDLESIZE, CHANDLESIZE,
  667.             ghDCMem, 0, 0, SRCCOPY);
  668.     BitBlt(hDC, xMid, 0, CHANDLESIZE, CHANDLESIZE,
  669.             ghDCMem, 0, 0, SRCCOPY);
  670.     BitBlt(hDC, x2, 0, CHANDLESIZE, CHANDLESIZE,
  671.             ghDCMem, 0, 0, SRCCOPY);
  672.     BitBlt(hDC, x2, yMid, CHANDLESIZE, CHANDLESIZE,
  673.             ghDCMem, 0, 0, SRCCOPY);
  674.     BitBlt(hDC, x2, y2, CHANDLESIZE, CHANDLESIZE,
  675.             ghDCMem, 0, 0, SRCCOPY);
  676.     BitBlt(hDC, xMid, y2, CHANDLESIZE, CHANDLESIZE,
  677.             ghDCMem, 0, 0, SRCCOPY);
  678.     BitBlt(hDC, 0, y2, CHANDLESIZE, CHANDLESIZE,
  679.             ghDCMem, 0, 0, SRCCOPY);
  680.     BitBlt(hDC, 0, yMid, CHANDLESIZE, CHANDLESIZE,
  681.             ghDCMem, 0, 0, SRCCOPY);
  682.     SelectObject(ghDCMem, hbmOld);
  683. }
  684. /************************************************************************
  685. * PaintUnderDrag
  686. *
  687. * This function is used during a paint operation for a visible drag
  688. * window.  It checks underneath the area to update and forces any
  689. * windows down there to paint themselves, in such an order as to cause
  690. * the last one painted to be the visually top window.  This is necessary
  691. * to implement the drag windows as "transparent" windows.  After this
  692. * routine finishes, the caller can paint the "handles".  The caller must
  693. * call this routine before calling BeginPaint, or the update area
  694. * will be null and nothing will get painted.
  695. *
  696. * It is assumed that the drag windows are for controls, not for the
  697. * dialog.
  698. *
  699. * Arguments:
  700. *   HWND hwndDrag - Drag window to paint under.
  701. *
  702. ************************************************************************/
  703. STATICFN VOID PaintUnderDrag(
  704.     HWND hwndDrag)
  705. {
  706.     RECT rc;
  707.     RECT rcInt;
  708.     RECT rcUpdate;
  709.     HWND hwnd;
  710.     HWND hwndControl;
  711.     NPCTYPE npc;
  712.     /*
  713.      * Get our corresponding control window.
  714.      */
  715.     hwndControl = (PCFROMHWND(hwndDrag))->hwnd;
  716.     /*
  717.      * Get the update rectangle and convert to screen coords.
  718.      */
  719.     GetUpdateRect(hwndDrag, &rcUpdate, TRUE);
  720.     ClientToScreenRect(hwndDrag, &rcUpdate);
  721.     /*
  722.      * Start enumerating windows.
  723.      */
  724.     hwnd = hwndDrag;
  725.     while (hwnd = GetWindow(hwnd, GW_HWNDNEXT)) {
  726.         /*
  727.          * Skip invisible drag windows.
  728.          */
  729.         if (IsWindowVisible(hwnd)) {
  730.             /*
  731.              * Does the window rectangle intersect the update rectangle?
  732.              */
  733.             GetWindowRect(hwnd, &rc);
  734.             if (IntersectRect(&rcInt, &rc, &rcUpdate)) {
  735.                 npc = PCFROMHWND(hwnd);
  736.                 if (npc->hwndDrag == hwnd || !npc->fSelected) {
  737.                     ScreenToClientRect(hwnd, &rcInt);
  738.                     InvalidateRect(hwnd, &rcInt, TRUE);
  739.                     UpdateWindow(hwnd);
  740.                 }
  741.                 if (npc->hwndDrag == hwnd)
  742.                     break;
  743.             }
  744.         }
  745.     }
  746.     /*
  747.      * Finally, paint the control associated with this drag window.
  748.      */
  749.     InvalidateRect(hwndControl, NULL, TRUE);
  750.     UpdateWindow(hwndControl);
  751. }
  752. /************************************************************************
  753. * HandleHitTest
  754. *
  755. * This routine takes a point from a mouse button press on a drag window
  756. * and returns which "handle" was hit, if any.
  757. *
  758. * The coordinates are given in zero based coordinates of the drag
  759. * window.
  760. *
  761. * Arguments:
  762. *   HWND hwnd   - Drag window handle the x,y point is relative to.
  763. *   INT x       - Mouse X location (in the drag's client coordinates).
  764. *   INT y       - Mouse Y location (in the drag's client coordinates).
  765. *
  766. * Returns:
  767. *   One of the DRAG_* constants.  If no handle was hit, the
  768. *   return will be DRAG_CENTER.
  769. *
  770. ************************************************************************/
  771. INT HandleHitTest(
  772.     HWND hwnd,
  773.     INT x,
  774.     INT y)
  775. {
  776.     RECT rc;
  777.     INT xMidStart;
  778.     INT yMidStart;
  779.     /*
  780.      * If there are multiple controls selected, or if the control
  781.      * type does not allow sizing, defeat the ability to size
  782.      * with the handles by returning DRAG_CENTER.
  783.      */
  784.     if (gcSelected > 1 || !(PCFROMHWND(hwnd))->pwcd->fSizeable)
  785.         return DRAG_CENTER;
  786.     /*
  787.      * Get the window rectangle and cause it to be zero-origined.
  788.      */
  789.     GetWindowRect(hwnd, &rc);
  790.     OffsetRect(&rc, -rc.left, -rc.top);
  791.     /*
  792.      * Calculate the starting points for the handles
  793.      * that are not on a corner.
  794.      */
  795.     xMidStart = ((rc.right + 1) / 2) - (CHANDLESIZE / 2);
  796.     yMidStart = ((rc.bottom + 1) / 2) - (CHANDLESIZE / 2);
  797.     if (x < CHANDLESIZE) {
  798.         if (y < CHANDLESIZE)
  799.             return DRAG_LEFTTOP;
  800.         else if (y > rc.bottom - CHANDLESIZE)
  801.             return DRAG_LEFTBOTTOM;
  802.         else if (y >= yMidStart && y < yMidStart + CHANDLESIZE)
  803.             return DRAG_LEFT;
  804.     }
  805.     else if (x > rc.right - CHANDLESIZE) {
  806.         if (y < CHANDLESIZE)
  807.             return DRAG_RIGHTTOP;
  808.         else if (y > rc.bottom - CHANDLESIZE)
  809.             return DRAG_RIGHTBOTTOM;
  810.         else if (y >= yMidStart && y < yMidStart + CHANDLESIZE)
  811.             return DRAG_RIGHT;
  812.     }
  813.     else if (x >= xMidStart && x < xMidStart + CHANDLESIZE) {
  814.         if (y < CHANDLESIZE)
  815.             return DRAG_TOP;
  816.         else if (y > rc.bottom - CHANDLESIZE)
  817.             return DRAG_BOTTOM;
  818.     }
  819.     return DRAG_CENTER;
  820. }
  821. /************************************************************************
  822. * MouseToDragRect
  823. *
  824. * This routine takes the mouse pointer coordinates from a mouse message
  825. * and produces a rectangle that contains the coordinates that the drag
  826. * rectangle should be displayed as.  This is for tracking (moving/sizing)
  827. * operations.  It relies on a number of globals to have been set up
  828. * prior to the call with such things as the current control, type of
  829. * drag, old location of the control, offset of the original mouse press
  830. * from the origin of the control, etc.
  831. *
  832. * The returned rectangle is pegged to the boundaries of the dialog (if it
  833. * is for a control), and is aligned to the current grid units.  It is in
  834. * dialog units relative to the appropriate point based on whether it is
  835. * for a control or the dialog, and must be converted to window points
  836. * before the actual drag rectangle can be drawn on the screen.
  837. *
  838. * Arguments:
  839. *   INT x       - Mouse X location (in window coordinates).
  840. *   INT y       - Mouse Y location (in window coordinates).
  841. *   PRECT prc   - Rectangle to return the appropriate drag rectangle
  842. *                 in.
  843. *
  844. ************************************************************************/
  845. STATICFN VOID MouseToDragRect(
  846.     INT x,
  847.     INT y,
  848.     PRECT prc)
  849. {
  850.     POINT pt;
  851.     INT fGridFlags;
  852.     pt.x = x;
  853.     pt.y = y;
  854.     MouseToDU(&pt);
  855.     switch (gHandleHit) {
  856.         case DRAG_LEFTBOTTOM:
  857.             SetRect(prc, pt.x, grcSelected.top, grcSelected.right,
  858.                     (grcSelected.bottom - grcSelected.top) + pt.y);
  859.             fGridFlags = GRIDIZE_LEFT | GRIDIZE_BOTTOM;
  860.             break;
  861.         case DRAG_BOTTOM:
  862.             SetRect(prc, grcSelected.left, grcSelected.top,
  863.                     grcSelected.right,
  864.                     (grcSelected.bottom - grcSelected.top) + pt.y);
  865.             fGridFlags = GRIDIZE_BOTTOM;
  866.             break;
  867.         case DRAG_RIGHTBOTTOM:
  868.             SetRect(prc, grcSelected.left, grcSelected.top,
  869.                     (grcSelected.right - grcSelected.left) + pt.x,
  870.                     (grcSelected.bottom - grcSelected.top) + pt.y);
  871.             fGridFlags = GRIDIZE_BOTTOM | GRIDIZE_RIGHT;
  872.            break;
  873.         case DRAG_RIGHT:
  874.             SetRect(prc, grcSelected.left, grcSelected.top,
  875.                     (grcSelected.right - grcSelected.left) + pt.x,
  876.                     grcSelected.bottom);
  877.             fGridFlags = GRIDIZE_RIGHT;
  878.             break;
  879.         case DRAG_RIGHTTOP:
  880.             SetRect(prc, grcSelected.left, pt.y,
  881.                     (grcSelected.right - grcSelected.left) + pt.x,
  882.                     grcSelected.bottom);
  883.             fGridFlags = GRIDIZE_RIGHT | GRIDIZE_TOP;
  884.             break;
  885.         case DRAG_TOP:
  886.             SetRect(prc, grcSelected.left, pt.y,
  887.                     grcSelected.right, grcSelected.bottom);
  888.             fGridFlags = GRIDIZE_TOP;
  889.             break;
  890.         case DRAG_LEFTTOP:
  891.             SetRect(prc, pt.x, pt.y, grcSelected.right, grcSelected.bottom);
  892.             fGridFlags = GRIDIZE_LEFT | GRIDIZE_TOP;
  893.             break;
  894.         case DRAG_LEFT:
  895.             SetRect(prc, pt.x, grcSelected.top,
  896.                     grcSelected.right, grcSelected.bottom);
  897.             fGridFlags = GRIDIZE_LEFT;
  898.             break;
  899.         case DRAG_CENTER:
  900.             SetRect(prc, pt.x, pt.y,
  901.                     (grcSelected.right - grcSelected.left) + pt.x,
  902.                     (grcSelected.bottom - grcSelected.top) + pt.y);
  903.             fGridFlags = GRIDIZE_LEFT | GRIDIZE_TOP | GRIDIZE_SAMESIZE;
  904.             break;
  905.     }
  906.     GridizeRect(prc, fGridFlags);
  907.     FitRectToBounds(prc, gnOverHang, gHandleHit, gfDlgSelected);
  908. }
  909. /************************************************************************
  910. * MouseToDU
  911. *
  912. * This routine converts the point at ppt from window coordinates
  913. * for the current control into the closest Dialog Unit point.
  914. *
  915. * This routine normally assumes that the point is relative to the selected
  916. * control, and will convert the point into DU's.  The current control
  917. * can either be the current dialog or one of its child controls.
  918. * The DU's returned will be appropriate for the type of control.  If
  919. * it is the dialog, they will be relative to the apps client area.
  920. * If it is a control, they will be relative to the "client" area of
  921. * the current dialog.
  922. *
  923. * If there is no current selection (such as when dropping a new control),
  924. * the point is assumed to already be relative to the dialog and it will
  925. * will not be mapped.  This only applies when called with a point for a
  926. * control, not a dialog.
  927. *
  928. * Arguments:
  929. *   PPOINT ppt - Point to convert.
  930. *
  931. ************************************************************************/
  932. STATICFN VOID MouseToDU(
  933.     PPOINT ppt)
  934. {
  935.     if (gfDlgSelected) {
  936.         /*
  937.          * Map the points from the dialog to the app client.
  938.          */
  939.         ClientToScreen(gcd.npc->hwnd, ppt);
  940.         ScreenToClient(ghwndSubClient, ppt);
  941.         /*
  942.          * Subtract the cursor offset.
  943.          */
  944.         ppt->x -= gptCursorOffset.x;
  945.         ppt->y -= gptCursorOffset.y;
  946.     }
  947.     else {
  948.         /*
  949.          * Map the points from the control to the dialog window,
  950.          * but only if there is a current selection.  There will
  951.          * not be a current selection when dropping a new control,
  952.          * and the point must already be relative to the dialog!
  953.          */
  954.         if (gnpcSel) {
  955.             ClientToScreen(gnpcSel->hwnd, ppt);
  956.             ScreenToClient(gcd.npc->hwnd, ppt);
  957.         }
  958.         /*
  959.          * Subtract the cursor offset, then the dialogs frame
  960.          * controls offset.
  961.          */
  962.         ppt->x -= gptCursorOffset.x;
  963.         ppt->y -= gptCursorOffset.y;
  964.     }
  965.     /*
  966.      * Convert this position to dialog units.
  967.      */
  968.     WinToDUPoint(ppt);
  969. }
  970. /************************************************************************
  971. * CtrlButtonDown
  972. *
  973. * This routine is called by the control and drag window procs when
  974. * the left mouse button is pressed.  It checks for the Ctrl modifier key
  975. * then passes control on to the appropriate routine.
  976. *
  977. * When hwnd is the dialog itself, fHandleWindow must be TRUE, even
  978. * if a handle was not hit, because of the special-case code that must
  979. * be done for the dialog (it doesn't have a separate drag window).
  980. *
  981. * Arguments:
  982. *   HWND hwnd          - Window handle, can be a drag window or control.
  983. *   INT x              - X mouse location (window coordinates).
  984. *   INT y              - Y mouse location (window coordinates).
  985. *   BOOL fHandleWindow - TRUE if a handle was clicked on, or FALSE if the
  986. *                        body of a control was clicked on.
  987. *
  988. ************************************************************************/
  989. VOID CtrlButtonDown(
  990.     HWND hwnd,
  991.     INT x,
  992.     INT y,
  993.     BOOL fHandleWindow)
  994. {
  995.     POINT pt;
  996.     HWND hwndHit;
  997.     INT nOverHang;
  998.     /*
  999.      * Discard all mouse messages during certain operations.
  1000.      */
  1001.     if (gfDisabled)
  1002.         return;
  1003.     /*
  1004.      * Also, be sure any outstanding changes get applied
  1005.      * without errors.
  1006.      */
  1007.     if (!StatusApplyChanges())
  1008.         return;
  1009.     if (gCurTool != W_NOTHING) {
  1010.         nOverHang = GetOverHang(gpwcdCurTool->iType, gpwcdCurTool->cyDefault);
  1011.         DragNewBegin(gpwcdCurTool->cxDefault,
  1012.                 gpwcdCurTool->cyDefault, nOverHang);
  1013.     }
  1014.     else {
  1015.         /*
  1016.          * If the Control key is down, duplicate the control, unless
  1017.          * it is the dialog (mouse duplicate of the dialog is not
  1018.          * supported).
  1019.          */
  1020.         if ((GetKeyState(VK_CONTROL) & 0x8000) &&
  1021.                 (PCFROMHWND(hwnd))->pwcd->iType != W_DIALOG) {
  1022.             /*
  1023.              * First, figure out which control was hit and select it.
  1024.              */
  1025.             if (fHandleWindow) {
  1026.                 hwndHit = hwnd;
  1027.             }
  1028.             else {
  1029.                 pt.x = x;
  1030.                 pt.y = y;
  1031.                 hwndHit = CtrlHitTest(hwnd, &pt);
  1032.             }
  1033.             SelectControl(PCFROMHWND(hwndHit), TRUE);
  1034.             /*
  1035.              * If there is still a selection, begin dragging a copy
  1036.              * of it.  The check here is necessary because the prior
  1037.              * call to SelectControl can unselect the last selected
  1038.              * control if the Shift key was held down.
  1039.              */
  1040.             if (gcSelected)
  1041.                 Duplicate();
  1042.         }
  1043.         else {
  1044.             /*
  1045.              * Start a drag operation.  This can be either moving or sizing
  1046.              * the control, depending on fHandleWindow and where the mouse
  1047.              * click is at.
  1048.              */
  1049.             DragBegin(hwnd, x, y, fHandleWindow);
  1050.         }
  1051.     }
  1052. }
  1053. /************************************************************************
  1054. * DragNewBegin
  1055. *
  1056. * This routine begins a drag operation when dropping a new control, or
  1057. * group of controls.  It is NOT used when dragging existing controls.
  1058. *
  1059. * Arguments:
  1060. *   INT cx        - Width of the new control.
  1061. *   INT cy        - Height of the new control.
  1062. *   INT nOverHang - How much the control can overhang the dialog bottom.
  1063. *
  1064. ************************************************************************/
  1065. VOID DragNewBegin(
  1066.     INT cx,
  1067.     INT cy,
  1068.     INT nOverHang)
  1069. {
  1070.     POINTS mpt;
  1071.     POINT pt;
  1072.     DWORD dwPos;
  1073.     /*
  1074.      * Always be sure the focus is where we want it, not on something
  1075.      * like the status ribbon or "Esc" won't work to cancel the tracking.
  1076.      */
  1077.     SetFocus(ghwndMain);
  1078.     /*
  1079.      * Cancel any current selection, and set some state globals.
  1080.      */
  1081.     CancelSelection(TRUE);
  1082.     gHandleHit = DRAG_CENTER;
  1083.     gState = STATE_DRAGGINGNEW;
  1084.     SetCursor(hcurMove);
  1085.     /*
  1086.      * The cursor offset is set to be located in the middle of the
  1087.      * new control.  This causes the pointer to be initially located
  1088.      * exactly in the center.
  1089.      */
  1090.     gptCursorOffset.x = cx;
  1091.     gptCursorOffset.y = cy;
  1092.     DUToWinPoint(&gptCursorOffset);
  1093.     gptCursorOffset.x /= 2;
  1094.     gptCursorOffset.y /= 2;
  1095.     /*
  1096.      * Set a global with the overhang.  This is used all during the
  1097.      * drag operation we are starting.
  1098.      */
  1099.     gnOverHang = nOverHang;
  1100.     /*
  1101.      * Now we make up a dummy rectangle for the new control.  We start
  1102.      * it at (0,0) with a size of cx and cy.  The point where the mouse
  1103.      * was when the command was done is obtained and mapped to the dialog
  1104.      * (it is assumed that only controls will be done here, not a dialog).
  1105.      * The new control rectangle is then converted to be at this location,
  1106.      * it is gridized, the tracking rectangle is shown and we are off.
  1107.      */
  1108.     SetRect(&grcSelected, 0, 0, cx, cy);
  1109.     dwPos = GetMessagePos();
  1110.     mpt = (*((POINTS *)&(dwPos)));
  1111.     ((pt).x = (mpt).x, (pt).y = (mpt).y);
  1112.     ScreenToClient(gcd.npc->hwnd, &pt);
  1113.     MouseToDragRect(pt.x, pt.y, &grcSelected);
  1114.     InitTracking();
  1115.     DrawTrackRect(&grcSelected, FALSE, TRUE);
  1116.     /*
  1117.      * Display the initial coordinates.
  1118.      */
  1119.     StatusSetCoords(&grcSelected);
  1120.     /*
  1121.      * The mouse messages will come through the dialog subclass proc for
  1122.      * these kinds of operations.
  1123.      */
  1124.     SetCapture(gcd.npc->hwnd);
  1125. }
  1126. /************************************************************************
  1127. * DragBegin
  1128. *
  1129. * This routine begins a drag operation for either moving a control or
  1130. * sizing it.  The tracking rectangle is not actually drawn until the
  1131. * mouse moves by a grid unit, however.
  1132. *
  1133. * To begin a drag on the dialog itself, fHandleWindow must be TRUE, even
  1134. * if a handle was not hit, because of the special-case code that must
  1135. * be done for the dialog (it doesn't have a separate drag window).
  1136. *
  1137. * Arguments:
  1138. *   HWND hwnd          - Window handle, can be a drag window or control.
  1139. *   INT x              - Starting X mouse location (window coordinates).
  1140. *   INT y              - Starting Y mouse location (window coordinates).
  1141. *   BOOL fHandleWindow - TRUE if the drag is happening because a drag
  1142. *                        handle was clicked on, or FALSE if the drag is
  1143. *                        happening because the body of a control is
  1144. *                        clicked on.
  1145. *
  1146. *
  1147. ************************************************************************/
  1148. STATICFN VOID DragBegin(
  1149.     HWND hwnd,
  1150.     INT x,
  1151.     INT y,
  1152.     BOOL fHandleWindow)
  1153. {
  1154.     NPCTYPE npcT;
  1155.     HWND hwndHit;
  1156.     POINT pt;
  1157.     NPCTYPE npc;
  1158.     BOOL fPrevSelect = FALSE;
  1159.     INT nBottom;
  1160.     /*
  1161.      * Always be sure the focus is where we want it, not on something
  1162.      * like the status ribbon or "Esc" won't work to cancel the tracking.
  1163.      */
  1164.     SetFocus(ghwndMain);
  1165.     pt.x = x;
  1166.     pt.y = y;
  1167.     /*
  1168.      * Is this drag happening because a drag handle was clicked on?
  1169.      */
  1170.     if (fHandleWindow) {
  1171.         /*
  1172.          * Find out which handle was clicked on.  It is assumed that
  1173.          * hwnd is a drag window.
  1174.          */
  1175.         gHandleHit = HandleHitTest(hwnd, pt.x, pt.y);
  1176.         hwndHit = hwnd;
  1177.     }
  1178.     else {
  1179.         /*
  1180.          * The body of a control was clicked on.  Set a global to say
  1181.          * that we are moving a control, then find out which control
  1182.          * was hit (with our own hit testing).
  1183.          */
  1184.         gHandleHit = DRAG_CENTER;
  1185.         hwndHit = CtrlHitTest(hwnd, &pt);
  1186.     }
  1187.     /*
  1188.      * Find out if the control clicked on was the currently selected
  1189.      * control already.
  1190.      */
  1191.     npc = PCFROMHWND(hwndHit);
  1192.     if (npc == gnpcSel)
  1193.         fPrevSelect = TRUE;
  1194.     /*
  1195.      * Select the control.  This can return FALSE if the control is
  1196.      * unselected (shift key is down and the control is already selected).
  1197.      */
  1198.     if (!SelectControl(npc, TRUE))
  1199.         return;
  1200.     /*
  1201.      * If the dialog is selected, we make a rectangle that encloses all
  1202.      * the controls.  This will be used to limit the size that the dialog
  1203.      * can be sized to so that it cannot cover any existing controls.
  1204.      */
  1205.     if (gfDlgSelected) {
  1206.         /*
  1207.          * Seed the rectangle with impossible values.
  1208.          */
  1209.         SetRect(&grcMinDialog, 32000, 32000, -32000, -32000);
  1210.         /*
  1211.          * Loop through all the controls, expanding the rectangle to
  1212.          * fit around all the controls.
  1213.          */
  1214.         for (npcT = npcHead; npcT; npcT = npcT->npcNext) {
  1215.             if (npcT->rc.left < grcMinDialog.left)
  1216.                 grcMinDialog.left = npcT->rc.left;
  1217.             if (npcT->rc.right > grcMinDialog.right)
  1218.                 grcMinDialog.right = npcT->rc.right;
  1219.             if (npcT->rc.top < grcMinDialog.top)
  1220.                 grcMinDialog.top = npcT->rc.top;
  1221.             /*
  1222.              * When calculating the bottom boundary of the controls,
  1223.              * make the rectangle shorter by the ovehang amount.  This
  1224.              * allows the dialog to be sized up so that it covers
  1225.              * parts of controls with overhang (comboboxes).
  1226.              */
  1227.             nBottom = npcT->rc.bottom - GetOverHang(npcT->pwcd->iType,
  1228.                     npcT->rc.bottom - npcT->rc.top);
  1229.             if (nBottom > grcMinDialog.bottom)
  1230.                 grcMinDialog.bottom = nBottom;
  1231.         }
  1232.         OffsetRect(&grcMinDialog, gcd.npc->rc.left, gcd.npc->rc.top);
  1233.     }
  1234.     /*
  1235.      * If the control clicked on was already the anchor, go right into
  1236.      * dragging mode.  If it was not, go into pre-drag mode, which will
  1237.      * defer the calculation of offsets, etc., until after a certain
  1238.      * small amount of time so the mouse can be "debounced".
  1239.      */
  1240.     if (fPrevSelect) {
  1241.         DragBegin2(&pt);
  1242.     }
  1243.     else {
  1244.         gState = STATE_PREDRAG;
  1245.         /*
  1246.          * Save the point in a global.  If the mouse pointer is moved
  1247.  * too far away from this point, we will start the drag operation
  1248.          * even if the pre-drag time has not elapsed yet.
  1249.          */
  1250.         gptPreDragStart = pt;
  1251.         /*
  1252.          * Start the pre-drag timer.
  1253.          */
  1254.         SetTimer(hwndHit, TID_PREDRAG, gmsecPreDrag, NULL);
  1255.     }
  1256.     /*
  1257.      * The mouse messages from now on will go to the window clicked on,
  1258.      * either the drag window or the control window.
  1259.      */
  1260.     SetCapture(hwndHit);
  1261. }
  1262. /************************************************************************
  1263. * DragBegin2
  1264. *
  1265. * This routine continues the initiation of a drag operation started
  1266. * by DragBegin.  It is separate because it calculates offsets based
  1267. * on where the mouse pointer is, and these calculations can be deferred
  1268. * until a later time than when DragBegin was called so that the mouse
  1269. * can be "debounced".
  1270. *
  1271. * Arguments:
  1272. *   POINT ppt - Starting mouse location (window coordinates).
  1273. *
  1274. ************************************************************************/
  1275. STATICFN VOID DragBegin2(
  1276.     PPOINT ppt)
  1277. {
  1278.     gState = STATE_DRAGGING;
  1279.     /*
  1280.      * Set the pointer to the "move" pointer if we are moving.
  1281.      * Otherwise, the pointer should already be set to the proper
  1282.      * sizing pointer.
  1283.      */
  1284.     if (gHandleHit == DRAG_CENTER)
  1285.         SetCursor(hcurMove);
  1286.     /*
  1287.      * Save away the initial offset of the cursor.
  1288.      */
  1289.     CalcCursorOffset(ppt);
  1290.     /*
  1291.      * Initialize the track rectangle.  Note we are calling DrawTrackRect
  1292.      * with FALSE.
  1293.      */
  1294.     DrawTrackRect(&grcSelected, gfDlgSelected, FALSE);
  1295. }
  1296. /****************************************************************************
  1297. * CtrlHitTest
  1298. *
  1299. * This routine walks the list of controls and determines which one is
  1300. * "hit" by this point.  If a hit is found, the point is also converted
  1301. * to coordinates for the hit window.
  1302. *
  1303. * There is a special case when hitting controls over a groupbox.
  1304. * Controls within a groupbox will always be hit instead of the
  1305. * groupbox itself.
  1306. *
  1307. * Arguments:
  1308. *   HWND hwnd  - Window handle the coordinates are relative to.
  1309. *   PPOINT ppt - Window point where the click occurred (window coords).
  1310. *
  1311. * Returns:
  1312. *   The hwnd of the "hit" control will be returned.  If no control was hit,
  1313. *   the hwnd that was passed in is returned.
  1314. *
  1315. ****************************************************************************/
  1316. STATICFN HWND CtrlHitTest(
  1317.     HWND hwnd,
  1318.     PPOINT ppt)
  1319. {
  1320.     NPCTYPE npc;
  1321.     RECT rc;
  1322.     HWND hwndHit = (HWND)NULL;
  1323.     BOOL fGroupHit = FALSE;
  1324.     for (npc = npcHead; npc; npc = npc->npcNext) {
  1325.         GetWindowRect(npc->hwnd, &rc);
  1326.         ScreenToClientRect(npc->hwnd, &rc);
  1327.         MyMapWindowRect(npc->hwnd, hwnd, &rc);
  1328.         /*
  1329.          * Is this a hit, and was there either no control hit as
  1330.          * yet, or this control is not a groupbox, or the control
  1331.          * that was previously hit was a groupbox also?
  1332.          */
  1333.         if (PtInRect(&rc, *ppt) &&
  1334.                 (!hwndHit || npc->pwcd->iType != W_GROUPBOX || fGroupHit)) {
  1335.             hwndHit = npc->hwnd;
  1336.             if (npc->pwcd->iType == W_GROUPBOX)
  1337.                 fGroupHit = TRUE;
  1338.             else
  1339.                 fGroupHit = FALSE;
  1340.         }
  1341.     }
  1342.     if (hwndHit) {
  1343.         MapWindowPoint(hwnd, hwndHit, ppt);
  1344.         return hwndHit;
  1345.     }
  1346.     else {
  1347.         return hwnd;
  1348.     }
  1349. }
  1350. /****************************************************************************
  1351. * PreDragTimeout
  1352. *
  1353. * This function handles the WM_TIMER message from the control window
  1354. * proc.  It is used so that the dragging of a newly selected control
  1355. * can be deferred for a small period of time to "debounce" the mouse.
  1356. *
  1357. * This function is also called if the mouse is moved too much during the
  1358. * debounce time, effectively cutting the debounce time short.
  1359. *
  1360. * Arguments:
  1361. *   HWND hwnd      - Window handle the timer came from.
  1362. *   BOOL fTimedOut - TRUE if the predrag is ending because the timer
  1363. *                    expired.  FALSE if the predrag is ending because
  1364. *      the mouse was moved too far.
  1365. *
  1366. ****************************************************************************/
  1367. VOID PreDragTimeout(
  1368.     HWND hwnd,
  1369.     BOOL fTimedOut)
  1370. {
  1371.     POINT pt;
  1372.     /*
  1373.      * The debounce time is over and the mouse button is still
  1374.      * down.  Get the current mouse pointer location and go into
  1375.      * drag mode.
  1376.      */
  1377.     if (gState == STATE_PREDRAG) {
  1378.         /*
  1379.          * If we timed out (the mouse was not moved a large distance),
  1380.          * eat any small movement that may have been done during the
  1381.          * predrag time by setting the mouse cursor back to the location
  1382.          * that it started at.  Note that we do not do this if the mouse
  1383.          * was moved a large distance, because the efect would be
  1384.          * noticeable for that case, and we want the control to be
  1385.          * moved then anyways.
  1386.          */
  1387.         if (fTimedOut) {
  1388.             pt = gptPreDragStart;
  1389.             ClientToScreen(hwnd, &pt);
  1390.             SetCursorPos(pt.x, pt.y);
  1391.         }
  1392.         DragBegin2(&gptPreDragStart);
  1393.     }
  1394.     KillTimer(hwnd, TID_PREDRAG);
  1395. }
  1396. /************************************************************************
  1397. * CtrlMouseMove
  1398. *
  1399. * This routine handles the mouse move messages for controls, the dialog,
  1400. * drag windows and when dropping a new control.
  1401. *
  1402. * During a drag operation, the tracking rectangle will be adjusted.
  1403. * If there is not a drag operation is effect, the mouse cursor will
  1404. * be changed to a sizing pointer if it is over a drag handle.  If not
  1405. * over a drag handle, the pointer will be changed back to the arrow.
  1406. *
  1407. * Arguments:
  1408. *   HWND hwnd        - Window handle the x and y are relative to.
  1409. *   BOOL fDragWindow - TRUE if hwnd is a drag window.
  1410. *   INT x            - X location of the mouse movement (window coords).
  1411. *   INT y            - Y location of the mouse movement (window coords).
  1412. *
  1413. ************************************************************************/
  1414. VOID CtrlMouseMove(
  1415.     HWND hwnd,
  1416.     BOOL fDragWindow,
  1417.     INT x,
  1418.     INT y)
  1419. {
  1420.     RECT rc;
  1421.     RECT rc2;
  1422.     HCURSOR hcur = NULL;
  1423.     /*
  1424.      * Discard all mouse messages during certain operations
  1425.      * (but still set the pointer properly).
  1426.      */
  1427.     if (gfDisabled) {
  1428.         SetCursor(hcurArrow);
  1429.         return;
  1430.     }
  1431.     switch (gState) {
  1432.         case STATE_PREDRAG:
  1433.             /*
  1434.      * If the mouse was moved too far, consider the
  1435.              * pre-drag time elapsed and go into drag mode.
  1436.              */
  1437.             if (abs(gptPreDragStart.x - x) > gcxPreDragMax ||
  1438.                     abs(gptPreDragStart.y - y) > gcyPreDragMax)
  1439.                 PreDragTimeout(hwnd, FALSE);
  1440.             break;
  1441.         case STATE_DRAGGING:
  1442.         case STATE_DRAGGINGNEW:
  1443.             MouseToDragRect(x, y, &rc);
  1444.             if (!EqualRect(&rc, &grcTrackDU)) {
  1445.                 /*
  1446.                  * If the tracking rectangle is not shown, this means that
  1447.                  * this is the first significant mouse move since the start
  1448.                  * of a drag operation, and we need to lock the window, get
  1449.                  * our clip DC, etc.
  1450.                  */
  1451.                 if (!gfTrackRectShown)
  1452.                     InitTracking();
  1453.                 DrawTrackRect(&rc, gfDlgSelected, TRUE);
  1454.                 if (gcSelected > 1) {
  1455.                     /*
  1456.                      * Since there are multiple controls selected,
  1457.                      * rc will be the rectangle that surrounds them
  1458.                      * all.  We really want to just show the anchor
  1459.                      * controls new position, so we have to do a
  1460.                      * little math to calculate and display it.
  1461.                      */
  1462.                     rc2 = gnpcSel->rc;
  1463.                     OffsetRect(&rc2, rc.left - grcSelected.left,
  1464.                             rc.top - grcSelected.top);
  1465.                     StatusSetCoords(&rc2);
  1466.                 }
  1467.                 else {
  1468.                     /*
  1469.                      * Either a single control is being dragged or
  1470.                      * a new control is being dropped.
  1471.                      */
  1472.                     StatusSetCoords(&rc);
  1473.                 }
  1474.             }
  1475.             break;
  1476.         case STATE_SELECTING:
  1477.             OutlineSelectDraw(x, y);
  1478.             break;
  1479.         default:
  1480.             /*
  1481.              * Is there a tool selected?
  1482.              */
  1483.             if (gCurTool != W_NOTHING) {
  1484.                 hcur = hcurDropTool;
  1485.             }
  1486.             else {
  1487.                 /*
  1488.                  * If hwnd is a drag window, see if the pointer is over
  1489.                  * over any of the handles and change it to one of the
  1490.                  * sizing pointers if necessary.  Otherwise set the pointer
  1491.                  * to the default arrow pointer.
  1492.                  */
  1493.                 if (fDragWindow) {
  1494.                     switch (HandleHitTest(hwnd, x, y)) {
  1495.                         case DRAG_LEFTBOTTOM:
  1496.                         case DRAG_RIGHTTOP:
  1497.                             hcur = hcurSizeNESW;
  1498.                             break;
  1499.                         case DRAG_LEFTTOP:
  1500.                         case DRAG_RIGHTBOTTOM:
  1501.                             hcur = hcurSizeNWSE;
  1502.                             break;
  1503.                         case DRAG_BOTTOM:
  1504.                         case DRAG_TOP:
  1505.                             hcur = hcurSizeNS;
  1506.                             break;
  1507.                         case DRAG_RIGHT:
  1508.                         case DRAG_LEFT:
  1509.                             hcur = hcurSizeWE;
  1510.                             break;
  1511.                         case DRAG_CENTER:
  1512.                         default:
  1513.                             hcur = hcurArrow;
  1514.                             break;
  1515.                     }
  1516.                 }
  1517.                 else {
  1518.                     hcur = hcurArrow;
  1519.                 }
  1520.             }
  1521.             break;
  1522.     }
  1523.     if (hcur)
  1524.         SetCursor(hcur);
  1525. }
  1526. /************************************************************************
  1527. * DragCancel
  1528. *
  1529. * This function cancels any drag operation in effect.  It handles
  1530. * such things as erasing any visible tracking rectangle, freeing any
  1531. * "copy" data, setting globals and updating the status display.  It
  1532. * can be used no matter how the drag operation was started.
  1533. *
  1534. ************************************************************************/
  1535. VOID DragCancel(VOID)
  1536. {
  1537.     HWND hwnd;
  1538.     switch (gState) {
  1539.         case STATE_PREDRAG:
  1540.             /*
  1541.              * Stop the timer.  Note that this assumes the timer
  1542.              * was attached to the capture window.  This should
  1543.              * be safe (see the associated SetTimer).
  1544.              */
  1545.             if (hwnd = GetCapture())
  1546.                 KillTimer(hwnd, TID_PREDRAG);
  1547.             break;
  1548.         case STATE_DRAGGING:
  1549.         case STATE_DRAGGINGNEW:
  1550.             CancelTracking();
  1551.             if (gpResCopy) {
  1552.                 MyFree(gpResCopy);
  1553.                 gpResCopy = NULL;
  1554.             }
  1555.             break;
  1556.     }
  1557.     gState = STATE_NORMAL;
  1558.     ReleaseCapture();
  1559.     SetCursor(hcurArrow);
  1560.     StatusUpdate();
  1561.     StatusSetEnable();
  1562. }
  1563. /************************************************************************
  1564. * CtrlButtonUp
  1565. *
  1566. * This function is called when the left mouse button is released.  Depending
  1567. * on the mode, it will complete the operation started when the button
  1568. * was pressed down.
  1569. *
  1570. * Arguments:
  1571. *   INT x - Mouse X location (in window coords).
  1572. *   INT y - Mouse Y location (in window coords).
  1573. *
  1574. ************************************************************************/
  1575. VOID CtrlButtonUp(
  1576.     INT x,
  1577.     INT y)
  1578. {
  1579.     /*
  1580.      * Discard all mouse messages during certain operations.
  1581.      */
  1582.     if (gfDisabled)
  1583.         return;
  1584.     switch (gState) {
  1585.         case STATE_PREDRAG:
  1586.             /*
  1587.              * They released the mouse button during the debounce time,
  1588.              * so cancel the drag.
  1589.              */
  1590.             DragCancel();
  1591.             break;
  1592.         case STATE_DRAGGING:
  1593.         case STATE_DRAGGINGNEW:
  1594.             DragEnd(x, y);
  1595.             break;
  1596.         case STATE_SELECTING:
  1597.             OutlineSelectEnd(x, y);
  1598.             break;
  1599.         default:
  1600.             break;
  1601.     }
  1602. }
  1603. /************************************************************************
  1604. * DragEnd
  1605. *
  1606. * This function completes all kinds of drag operations.  If dragging a
  1607. * new control, it will be dropped at the specified location.  If dragging
  1608. * an existing control, it will be positioned to the given location.
  1609. *
  1610. * Arguments:
  1611. *   INT x - X location the control ended up at (in window coords).
  1612. *   INT y - Y location the control ended up at (in window coords).
  1613. *
  1614. ************************************************************************/
  1615. VOID DragEnd(
  1616.     INT x,
  1617.     INT y)
  1618. {
  1619.     PDIALOGBOXHEADER pdbh;
  1620.     PCONTROLDATA pcd;
  1621.     INT cControls;
  1622.     RECT rc;
  1623.     INT i;
  1624.     INT cx;
  1625.     INT cy;
  1626.     CancelTracking();
  1627.     MouseToDragRect(x, y, &rc);
  1628.     if (gState == STATE_DRAGGING) {
  1629.         PositionControl(&rc);
  1630.     }
  1631.     else {
  1632.         if (gpResCopy) {
  1633.             pdbh = (PDIALOGBOXHEADER)SkipResHeader(gpResCopy);
  1634.             cControls = (INT)pdbh->NumberOfItems;
  1635.             pcd = SkipDialogBoxHeader(pdbh);
  1636.             cx = rc.left - grcCopy.left;
  1637.             cy = rc.top - grcCopy.top;
  1638.             /*
  1639.              * Loop through all the controls, adjusting their position
  1640.              * according to where the drag rectangle ended up.
  1641.              */
  1642.             for (i = 0; i < cControls; i++) {
  1643.                 /*
  1644.                  * Add cx and cy to the resource's x and y fields.
  1645.                  */
  1646.                 pcd->x += (WORD)cx;
  1647.                 pcd->y += (WORD)cy;
  1648.                 pcd = SkipControlData(pcd);
  1649.             }
  1650.             /*
  1651.              * Now we go and create all the controls, adding them to
  1652.              * the current dialog.  It is assumed that the image in
  1653.              * gpResCopy specifies controls to add, and not a dialog
  1654.              * to create!
  1655.              */
  1656.             if (ResToDialog(gpResCopy, FALSE)) {
  1657.                 gfResChged = gfDlgChanged = TRUE;
  1658.                 ShowFileStatus(FALSE);
  1659.             }
  1660.             MyFree(gpResCopy);
  1661.             gpResCopy = NULL;
  1662.             StatusUpdate();
  1663.             StatusSetEnable();
  1664.         }
  1665.         else {
  1666.             /*
  1667.              * Drop the new control.
  1668.              */
  1669.             DropControl(gpwcdCurTool, &rc);
  1670.             if (!gfToolLocked)
  1671.                 ToolboxSelectTool(W_NOTHING, FALSE);
  1672.         }
  1673.     }
  1674.     gState = STATE_NORMAL;
  1675.     ReleaseCapture();
  1676.     SetCursor(hcurArrow);
  1677. }