home *** CD-ROM | disk | FTP | other *** search
/ Tricks of the Windows Gam…ming Gurus (2nd Edition) / Disc2.iso / msdn_vcb / samples / vc98 / sdk / sdktools / winnt / dlgedit / drag.c < prev    next >
C/C++ Source or Header  |  1997-10-05  |  60KB  |  1,973 lines

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