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

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: select.c
  12. *
  13. * Contains routines for selecting and positioning controls.
  14. *
  15. * Functions:
  16. *
  17. *    SelectControl()
  18. *    SelectControl2()
  19. *    RedrawSelection()
  20. *    SetAnchorToFirstSel()
  21. *    SelectNext()
  22. *    SelectPrevious()
  23. *    UnSelectControl()
  24. *    CalcSelectedRect()
  25. *    CancelSelection()
  26. *    OutlineSelectBegin()
  27. *    OutlineSelectDraw()
  28. *    OutlineSelectCancel()
  29. *    OutlineSelectEnd()
  30. *    MyFrameRect()
  31. *    MoveControl()
  32. *    PositionControl()
  33. *    RepositionDialog()
  34. *    SaveDlgClientRect()
  35. *    SizeToText()
  36. *    AlignControls()
  37. *    ArrangeSpacing()
  38. *    ArrangeSize()
  39. *    ArrangePushButtons()
  40. *    InvalidateDlgHandles()
  41. *    OutlineSelectHide()
  42. *    OutlineSelectSetRect()
  43. *    PositionControl2()
  44. *    SizeCtrlToText()
  45. *    QueryTextExtent()
  46. *
  47. * Comments:
  48. *
  49. ****************************************************************************/
  50. #include "dlgedit.h"
  51. #include "dlgfuncs.h"
  52. #include "dlgextrn.h"
  53. STATICFN VOID InvalidateDlgHandles(VOID);
  54. STATICFN VOID OutlineSelectHide(VOID);
  55. STATICFN VOID OutlineSelectSetRect(INT x, INT y);
  56. STATICFN HANDLE PositionControl2(NPCTYPE npc, PRECT prc, HANDLE hwpi);
  57. STATICFN BOOL SizeCtrlToText(NPCTYPE npc);
  58. STATICFN INT QueryTextExtent(HWND hwnd, LPTSTR pszText, BOOL fWordBreak);
  59. static POINT gptOutlineSelect;
  60. static RECT grcOutlineSelect;
  61. static RECT grcOutlineSelectLimit;
  62. static BOOL gfOutlineSelectShown = FALSE;
  63. /************************************************************************
  64. * SelectControl
  65. *
  66. * This routine selects a control, showing its drag window and handles.
  67. * If fCheckShift is TRUE and the shift key is down, this routine adds
  68. * the control to the existing selection, unless the control is already
  69. * selected, in which case it is removed from the existing selection.
  70. *
  71. * This routine handles the case where a controls is clicked on to select
  72. * it, and this may cause other controls to be unselected.  If it is
  73. * known for sure that a control should be selected or added to the
  74. * existing selection, SelectControl2 can be used instead.
  75. *
  76. * Arguments:
  77. *   NPCTYPE npc      = The control to select.
  78. *   BOOL fCheckShift = TRUE if the state of the shift key should be
  79. *                      taken into consideration.
  80. *
  81. * Returns:
  82. *   The return will be FALSE if the control was just unselected.
  83. *
  84. ************************************************************************/
  85. BOOL SelectControl(
  86.     NPCTYPE npc,
  87.     BOOL fCheckShift)
  88. {
  89.     BOOL fShiftDown;
  90.     BOOL fSelectDone = TRUE;
  91.     if (npc->pwcd->iType == W_DIALOG) {
  92.         if (gnpcSel == npc)
  93.             return TRUE;
  94.         CancelSelection(FALSE);
  95.         SelectControl2(npc, FALSE);
  96.     }
  97.     else {
  98.         if (fCheckShift)
  99.             fShiftDown = (GetKeyState(VK_SHIFT) & 0x8000) ? TRUE : FALSE;
  100.         else
  101.             fShiftDown = FALSE;
  102.         if (npc->fSelected) {
  103.             /*
  104.              * If the shift key is down, and they are NOT trying to
  105.              * select the dialog, toggle the selection of this control
  106.              * to off.
  107.              */
  108.             if (fShiftDown && npc->pwcd->iType != W_DIALOG) {
  109.                 UnSelectControl(npc);
  110.                 CalcSelectedRect();
  111.                 fSelectDone = FALSE;
  112.             }
  113.             else {
  114.                 if (gnpcSel == npc)
  115.                     return TRUE;
  116.                 else
  117.                     SelectControl2(npc, FALSE);
  118.             }
  119.         }
  120.         else {
  121.             /*
  122.              * If they are NOT holding the shift key down, or the
  123.              * dialog is selected, cancel the selection first.
  124.              */
  125.             if (!fShiftDown || gcd.npc->fSelected == TRUE)
  126.                 CancelSelection(FALSE);
  127.             SelectControl2(npc, FALSE);
  128.         }
  129.     }
  130.     StatusUpdate();
  131.     StatusSetEnable();
  132.     return fSelectDone;
  133. }
  134. /************************************************************************
  135. * SelectControl2
  136. *
  137. * This routine is the worker for SelectControl.  It does the actual
  138. * work to "select" a control, updating globals and showing the drag
  139. * windows with handles.
  140. *
  141. * This routine handles the special case where we are selecting a
  142. * control that is already selected.  The editor has the concept of
  143. * a control being selected, as well as there being the currently
  144. * selected control (pointed to by gnpcSel).  There can be the case
  145. * where there are multiple controls selected, but only one will be
  146. * the current selection (usually the last one clicked on).  This
  147. * routine will never unselect other controls.  This must be done
  148. * prior to here, if appropriate.
  149. *
  150. * Arguments:
  151. *   NPCTYPE npc      = The control to make the current selection.
  152. *   BOOL fDontUpdate = TRUE if the selection should NOT be redrawn
  153. *                      after the specified control is added to it.
  154. *                      This allows painting to be deferred until
  155. *                      later if a number of controls are being
  156. *                      selected in a loop.  It also does not call
  157. *                      CalcSelectedRect (this MUST be done later
  158. *                      for drags to work, however!).
  159. * Comments:
  160. *
  161. * If fDontUpdate is TRUE, the selection will not be redrawn, and it
  162. * is required that CalcSelectedRect be called before doing any drag
  163. * operations.
  164. *
  165. ************************************************************************/
  166. VOID SelectControl2(
  167.     NPCTYPE npc,
  168.     BOOL fDontUpdate)
  169. {
  170.     BOOL fUpdate = FALSE;
  171.     /*
  172.      * Is the control already selected?
  173.      */
  174.     if (npc->fSelected) {
  175.         /*
  176.          * It is already selected (hwndDrag is visible).  If it is
  177.          * not the current selection, we want all drag windows to
  178.          * be redrawn in the proper order to update their appearance.
  179.          */
  180.         if (gnpcSel != npc)
  181.             fUpdate = TRUE;
  182.     }
  183.     else {
  184.         /*
  185.          * The control is not yet selected.  If another control is
  186.          * currently selected, we want all drag windows to be
  187.          * updated so that their handle appearance gets updated.
  188.          */
  189.         if (gnpcSel)
  190.             fUpdate = TRUE;
  191.         /*
  192.          * Flip its flag and add to the selected count.
  193.          */
  194.         npc->fSelected = TRUE;
  195.         gcSelected++;
  196.     }
  197.     gnpcSel = npc;
  198.     if (!fDontUpdate)
  199.         CalcSelectedRect();
  200.     if (npc->pwcd->iType == W_DIALOG) {
  201.         gfDlgSelected = TRUE;
  202.         InvalidateDlgHandles();
  203.     }
  204.     else {
  205.         gfDlgSelected = FALSE;
  206.         ShowWindow(npc->hwndDrag, SW_SHOW);
  207.         if (fUpdate && !fDontUpdate)
  208.             RedrawSelection();
  209.     }
  210. }
  211. /************************************************************************
  212. * RedrawSelection
  213. *
  214. * This function cause all the selected drag windows to be invalidated.
  215. * This is necessary whenever one of them changes, because of the very
  216. * touchy painting order that has to be maintained.  Without invalidating
  217. * all of them as a unit, there are cases where handles do not get
  218. * properly painted.
  219. *
  220. ************************************************************************/
  221. VOID RedrawSelection(VOID)
  222. {
  223.     NPCTYPE npc;
  224.     if (!gcSelected) {
  225.         return;
  226.     }
  227.     else if (gcSelected == 1) {
  228.         InvalidateRect(gfDlgSelected ? gnpcSel->hwnd : gnpcSel->hwndDrag,
  229.                 NULL, TRUE);
  230.     }
  231.     else {
  232.         for (npc = npcHead; npc; npc = npc->npcNext) {
  233.             if (npc->fSelected)
  234.                 InvalidateRect(npc->hwndDrag, NULL, TRUE);
  235.         }
  236.     }
  237. }
  238. /************************************************************************
  239. * SetAnchorToFirstSel
  240. *
  241. * This function makes the current selection (the anchor) be the
  242. * first selected control.  It is used after making a group selection,
  243. * and ensures that the control that ends up being the anchor is
  244. * consistently the first one in Z-order.
  245. *
  246. * Arguments:
  247. *   BOOL fDontUpdate = TRUE if the selection should NOT be redrawn.
  248. *
  249. ************************************************************************/
  250. VOID SetAnchorToFirstSel(
  251.     BOOL fDontUpdate)
  252. {
  253.     NPCTYPE npc;
  254.     if (gcSelected) {
  255.         for (npc = npcHead; npc; npc = npc->npcNext) {
  256.             if (npc->fSelected) {
  257.                 SelectControl2(npc, fDontUpdate);
  258.                 break;
  259.             }
  260.         }
  261.     }
  262. }
  263. /************************************************************************
  264. * SelectNext
  265. *
  266. * This selects the next control in the dialog box.  The enumeration
  267. * includes the dialog box itself, and wraps around.
  268. *
  269. ************************************************************************/
  270. VOID SelectNext(VOID)
  271. {
  272.     NPCTYPE npcSelect;
  273.     /*
  274.      * Disable the tabbing functions if there is no dialog
  275.      * or if the dialog is already selected and there are
  276.      * no controls (the tabs would be a noop in this case).
  277.      */
  278.     if (!gfEditingDlg || (gfDlgSelected && !npcHead))
  279.         return;
  280.     /*
  281.      * Is nothing selected?
  282.      */
  283.     if (!gnpcSel) {
  284.         /*
  285.          * Select the first control, unless there are none, in which
  286.          * case select the dialog.
  287.          */
  288.         if (npcHead)
  289.             npcSelect = npcHead;
  290.         else
  291.             npcSelect = gcd.npc;
  292.     }
  293.     else {
  294.         /*
  295.          * Is the dialog selected?
  296.          */
  297.         if (gfDlgSelected) {
  298.             /*
  299.              * Select the first control, unless there are none, in which
  300.              * case do nothing.
  301.              */
  302.             if (npcHead)
  303.                 npcSelect = npcHead;
  304.             else
  305.                 npcSelect = NULL;
  306.         }
  307.         else {
  308.             /*
  309.              * Find the current control.  If there is one after it,
  310.              * select it, otherwise wrap around to the dialog and
  311.              * select it.
  312.              */
  313.             if (gnpcSel->npcNext)
  314.                 npcSelect = gnpcSel->npcNext;
  315.             else
  316.                 npcSelect = gcd.npc;
  317.         }
  318.     }
  319.     if (npcSelect)
  320.         SelectControl(npcSelect, FALSE);
  321. }
  322. /************************************************************************
  323. * SelectPrevious
  324. *
  325. * This selects the previous control in the dialog box.  The enumeration
  326. * includes the dialog box itself, and wraps around.
  327. *
  328. ************************************************************************/
  329. VOID SelectPrevious(VOID)
  330. {
  331.     NPCTYPE npc;
  332.     NPCTYPE npcSelect;
  333.     /*
  334.      * Disable the tabbing functions if there is no dialog
  335.      * or if the dialog is already selected and there are
  336.      * no controls (the tabs would be a noop in this case).
  337.      */
  338.     if (!gfEditingDlg || (gfDlgSelected && !npcHead))
  339.         return;
  340.     /*
  341.      * Is nothing selected?
  342.      */
  343.     if (!gnpcSel) {
  344.         /*
  345.          * Select the last control, unless there are none, in which
  346.          * case select the dialog.
  347.          */
  348.         if (npcHead) {
  349.             npc = npcHead;
  350.             while (npc->npcNext)
  351.                 npc = npc->npcNext;
  352.             npcSelect = npc;
  353.         }
  354.         else {
  355.             npcSelect = gcd.npc;
  356.         }
  357.     }
  358.     else {
  359.         /*
  360.          * Is the dialog selected?
  361.          */
  362.         if (gfDlgSelected) {
  363.             /*
  364.              * Select the last control, unless there are none, in which
  365.              * case select nothing.
  366.              */
  367.             if (npcHead) {
  368.                 npc = npcHead;
  369.                 while (npc->npcNext)
  370.                     npc = npc->npcNext;
  371.                 npcSelect = npc;
  372.             }
  373.             else {
  374.                 npcSelect = NULL;
  375.             }
  376.         }
  377.         else {
  378.             /*
  379.              * If the first control is selected, select the dialog.
  380.              * Otherwise hunt for and select the previous control.
  381.              */
  382.             if (npcHead == gnpcSel) {
  383.                 npcSelect = gcd.npc;
  384.             }
  385.             else {
  386.                 npc = npcHead;
  387.                 while (npc->npcNext != gnpcSel)
  388.                     npc = npc->npcNext;
  389.                 npcSelect = npc;
  390.             }
  391.         }
  392.     }
  393.     if (npcSelect)
  394.         SelectControl(npcSelect, FALSE);
  395. }
  396. /************************************************************************
  397. * UnSelectControl
  398. *
  399. * This unselects the specified control, hiding its drag window and handles.
  400. *
  401. * Arguments:
  402. *     NPCTYPE npc = The control to deselect.
  403. *
  404. ************************************************************************/
  405. VOID UnSelectControl(
  406.     NPCTYPE npc)
  407. {
  408.     npc->fSelected = FALSE;
  409.     gcSelected--;
  410.     /*
  411.      * We don't have a current selection if there are no selected
  412.      * windows, or if the control we are unselecting was the current
  413.      * selection.
  414.      */
  415.     if (!gcSelected || npc == gnpcSel)
  416.         gnpcSel = NULL;
  417.     if (npc->pwcd->iType == W_DIALOG) {
  418.         gfDlgSelected = FALSE;
  419.         InvalidateDlgHandles();
  420.     }
  421.     else {
  422.         ShowWindow(npc->hwndDrag, SW_HIDE);
  423.     }
  424.     /*
  425.      * Are there still some selected controls, and was the control
  426.      * we just unselected the current selection?  If so, we need
  427.      * to set the current selection to something.
  428.      */
  429.     if (gcSelected && !gnpcSel)
  430.         SetAnchorToFirstSel(FALSE);
  431. }
  432. /************************************************************************
  433. * InvalidateDlgHandles
  434. *
  435. * This function invalidates the handles for the dialog.  This is
  436. * used as an optimization so that the entire dialog does not need
  437. * to be invalidated just to force the handles to be drawn.
  438. *
  439. ************************************************************************/
  440. STATICFN VOID InvalidateDlgHandles(VOID)
  441. {
  442.     RECT rc;
  443.     RECT rcClient;
  444.     RECT rcFrame;
  445.     POINT pt;
  446.     INT xOffset;
  447.     INT yOffset;
  448.     /*
  449.      * Redraw the dialog border.
  450.      */
  451.     SetWindowPos(gcd.npc->hwnd, NULL, 0, 0, 0, 0,
  452.             SWP_DRAWFRAME | SWP_NOACTIVATE |
  453.             SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER);
  454.     /*
  455.      * Get the frame and client rectangles.
  456.      */
  457.     GetWindowRect(gcd.npc->hwnd, &rcFrame);
  458.     GetClientRect(gcd.npc->hwnd, &rcClient);
  459.     /*
  460.      * Determine the offset from the frame origin to the client
  461.      * origin.
  462.      */
  463.     pt.x = pt.y = 0;
  464.     ClientToScreen(gcd.npc->hwnd, &pt);
  465.     xOffset = rcFrame.left - pt.x;
  466.     yOffset = rcFrame.top - pt.y;
  467.     /*
  468.      * Make the frame rectangle zero based.
  469.      */
  470.     OffsetRect(&rcFrame, -rcFrame.left, -rcFrame.top);
  471.     rc.left = 0;
  472.     rc.top = 0;
  473.     rc.right = CHANDLESIZE;
  474.     rc.bottom = CHANDLESIZE;
  475.     OffsetRect(&rc, xOffset, yOffset);
  476.     InvalidateRect(gcd.npc->hwnd, &rc, TRUE);
  477.     rc.left = ((rcFrame.right + 1) / 2) - (CHANDLESIZE / 2);
  478.     rc.top = 0;
  479.     rc.right = rc.left + CHANDLESIZE;
  480.     rc.bottom = CHANDLESIZE;
  481.     OffsetRect(&rc, xOffset, yOffset);
  482.     InvalidateRect(gcd.npc->hwnd, &rc, TRUE);
  483.     rc.left = rcFrame.right - CHANDLESIZE;
  484.     rc.top = 0;
  485.     rc.right = rcFrame.right;
  486.     rc.bottom = CHANDLESIZE;
  487.     OffsetRect(&rc, xOffset, yOffset);
  488.     InvalidateRect(gcd.npc->hwnd, &rc, TRUE);
  489.     rc.left = rcFrame.right - CHANDLESIZE;
  490.     rc.top = ((rcFrame.bottom + 1) / 2) - (CHANDLESIZE / 2);
  491.     rc.right = rcFrame.right;
  492.     rc.bottom = rc.top + CHANDLESIZE;
  493.     OffsetRect(&rc, xOffset, yOffset);
  494.     InvalidateRect(gcd.npc->hwnd, &rc, TRUE);
  495.     rc.left = rcFrame.right - CHANDLESIZE;
  496.     rc.top = rcFrame.bottom - CHANDLESIZE;
  497.     rc.right = rcFrame.right;
  498.     rc.bottom = rcFrame.bottom;
  499.     OffsetRect(&rc, xOffset, yOffset);
  500.     InvalidateRect(gcd.npc->hwnd, &rc, TRUE);
  501.     rc.left = ((rcFrame.right + 1) / 2) - (CHANDLESIZE / 2);
  502.     rc.top = rcFrame.bottom - CHANDLESIZE;
  503.     rc.right = rc.left + CHANDLESIZE;
  504.     rc.bottom = rcFrame.bottom;
  505.     OffsetRect(&rc, xOffset, yOffset);
  506.     InvalidateRect(gcd.npc->hwnd, &rc, TRUE);
  507.     rc.left = 0;
  508.     rc.top = rcFrame.bottom - CHANDLESIZE;
  509.     rc.right = CHANDLESIZE;
  510.     rc.bottom = rcFrame.bottom;
  511.     OffsetRect(&rc, xOffset, yOffset);
  512.     InvalidateRect(gcd.npc->hwnd, &rc, TRUE);
  513.     rc.left = 0;
  514.     rc.top = ((rcFrame.bottom + 1) / 2) - (CHANDLESIZE / 2);
  515.     rc.right = CHANDLESIZE;
  516.     rc.bottom = rc.top + CHANDLESIZE;
  517.     OffsetRect(&rc, xOffset, yOffset);
  518.     InvalidateRect(gcd.npc->hwnd, &rc, TRUE);
  519. }
  520. /************************************************************************
  521. * CalcSelectedRect
  522. *
  523. * This routine updates the gwrcSelected rectangle.  This is used during
  524. * dragging operations.  It contains the minimum rectangle that spans
  525. * all the selected controls.  If there are no selected controls, the
  526. * contents of this global are not defined.  This routine must be called
  527. * every time that a control is selected of unselected to keep this
  528. * rectangle updated, or tracking will not work properly.
  529. *
  530. ************************************************************************/
  531. VOID CalcSelectedRect(VOID)
  532. {
  533.     NPCTYPE npc;
  534.     INT nBottom;
  535.     INT nBottomLowest;
  536.     /*
  537.      * Nothing is selected.  The rectangle values are considered
  538.      * undefined, so we can quit.
  539.      */
  540.     if (!gcSelected)
  541.         return;
  542.     if (gcSelected == 1) {
  543.         /*
  544.          * Only one control is selected.  Set the rectangle to its
  545.          * rectangle.  Note that since the dialog cannot be selected
  546.          * along with other controls, this handles the case where the
  547.          * dialog is selected.
  548.          */
  549.         grcSelected = gnpcSel->rc;
  550.         gnOverHang = GetOverHang(gnpcSel->pwcd->iType,
  551.                 gnpcSel->rc.bottom - gnpcSel->rc.top);
  552.     }
  553.     else {
  554.         /*
  555.          * Seed the rectangle with impossible values.
  556.          */
  557.         SetRect(&grcSelected, 32000, 32000, -32000, -32000);
  558.         nBottomLowest = 0;
  559.         /*
  560.          * Loop through all the controls, expanding the rectangle to
  561.          * fit around all the selected controls.
  562.          */
  563.         for (npc = npcHead; npc; npc = npc->npcNext) {
  564.             if (npc->fSelected) {
  565.                 if (npc->rc.left < grcSelected.left)
  566.                     grcSelected.left = npc->rc.left;
  567.                 if (npc->rc.right > grcSelected.right)
  568.                     grcSelected.right = npc->rc.right;
  569.                 if (npc->rc.top < grcSelected.top)
  570.                     grcSelected.top = npc->rc.top;
  571.                 nBottom = npc->rc.bottom - GetOverHang(npc->pwcd->iType,
  572.                         npc->rc.bottom - npc->rc.top);
  573.                 if (nBottom > nBottomLowest)
  574.                     nBottomLowest = nBottom;
  575.                 if (npc->rc.bottom > grcSelected.bottom)
  576.                     grcSelected.bottom = npc->rc.bottom;
  577.             }
  578.         }
  579.         gnOverHang = grcSelected.bottom - nBottomLowest;
  580.     }
  581. }
  582. /************************************************************************
  583. * CancelSelection
  584. *
  585. * This unselects all selected controls.
  586. *
  587. * Arguments:
  588. *   BOOL fUpdate - If TRUE, the status ribbon is updated.
  589. *
  590. ************************************************************************/
  591. VOID CancelSelection(
  592.     BOOL fUpdate)
  593. {
  594.     if (gcSelected) {
  595.         while (gcSelected)
  596.             UnSelectControl(gnpcSel);
  597.         if (fUpdate) {
  598.             StatusUpdate();
  599.             StatusSetEnable();
  600.         }
  601.     }
  602. }
  603. /************************************************************************
  604. * OutlineSelectBegin
  605. *
  606. * This function begins an Outline Selection operation.  This will
  607. * draw a tracking rectangle on the screen that can be used to enclose
  608. * controls.  When the selection is completed, all the enclosed controls
  609. * will be selected.
  610. *
  611. * The x and y coordinates are relative to the dialog window, not it's
  612. * client.
  613. *
  614. * Arguments:
  615. *   INT x   - Starting X location (window coords).
  616. *   INT y   - Starting Y location (window coords).
  617. *
  618. ************************************************************************/
  619. VOID OutlineSelectBegin(
  620.     INT x,
  621.     INT y)
  622. {
  623.     /*
  624.      * Always be sure the focus is where we want it, not on something
  625.      * like the status ribbon or "Esc" won't work to cancel the tracking.
  626.      */
  627.     SetFocus(ghwndMain);
  628.     /*
  629.      * Remember the starting point.  It comes in coords relative to the
  630.      * window, and the DC we are getting is one for the dialog's client,
  631.      * so we have to map it from window coords to the client's coords.
  632.      */
  633.     gptOutlineSelect.x = x;
  634.     gptOutlineSelect.y = y;
  635.     MapDlgClientPoint(&gptOutlineSelect, FALSE);
  636.     gState = STATE_SELECTING;
  637.     ghwndTrackOver = gcd.npc->hwnd;
  638.     ghDCTrack = GetDC(ghwndTrackOver);
  639.     SetROP2(ghDCTrack, R2_NOT);
  640.     /*
  641.      * Get the rectangle for the client area of the dialog.  This is
  642.      * used to limit the tracking.
  643.      */
  644.     GetClientRect(gcd.npc->hwnd, &grcOutlineSelectLimit);
  645.     OutlineSelectDraw(x, y);
  646.     /*
  647.      * The mouse messages from now on will go to the dialog window,
  648.      * so that the following routines can know that the mouse movement
  649.      * points are relative to that window.
  650.      */
  651.     SetCapture(gcd.npc->hwnd);
  652.     SetCursor(hcurOutSel);
  653. }
  654. /************************************************************************
  655. * OutlineSelectDraw
  656. *
  657. * This routine draws the outline selection rectangle.  It is assumed
  658. * that the window has been locked for update appropriately or this
  659. * could leave garbage around.  The outline selection rectangle is
  660. * drawn from the starting point in gptOutlineSelect to the given
  661. * x,y location.
  662. *
  663. * Arguments:
  664. *   INT x   - Mouse X location (window coords relative to the dialog).
  665. *   INT y   - Mouse Y location (window coords relative to the dialog).
  666. *
  667. ************************************************************************/
  668. VOID OutlineSelectDraw(
  669.     INT x,
  670.     INT y)
  671. {
  672.     OutlineSelectHide();
  673.     OutlineSelectSetRect(x, y);
  674.     MyFrameRect(ghDCTrack, &grcOutlineSelect, DSTINVERT);
  675.     gfOutlineSelectShown = TRUE;
  676. }
  677. /************************************************************************
  678. * OutlineSelectHide
  679. *
  680. * This routine hides the current outline selection rectangle.
  681. *
  682. ************************************************************************/
  683. STATICFN VOID OutlineSelectHide(VOID)
  684. {
  685.     if (gfOutlineSelectShown) {
  686.         MyFrameRect(ghDCTrack, &grcOutlineSelect, DSTINVERT);
  687.         gfOutlineSelectShown = FALSE;
  688.     }
  689. }
  690. /************************************************************************
  691. * OutlineSelectSetRect
  692. *
  693. * This function takes an x,y point and makes a rectangle that goes
  694. * from this point to the starting outline selection point.  The cases
  695. * are handled where the new point has negative coordinates relative
  696. * to the starting point, and the rectangle produced is limited to
  697. * the rectangle in grcOutlineSelectLimit.
  698. *
  699. * The rectangle is placed into the global grcOutlineSelect.  The
  700. * global point gwptOutlineSelect is assumed to have previously been
  701. * set to the starting point of the outline selection.
  702. *
  703. * The x and y coordinates are relative to the dialog window, not it's
  704. * client.
  705. *
  706. * Arguments:
  707. *   INT x   - Mouse X location (window coords relative to the dialog).
  708. *   INT y   - Mouse Y location (window coords relative to the dialog).
  709. *
  710. ************************************************************************/
  711. STATICFN VOID OutlineSelectSetRect(
  712.     INT x,
  713.     INT y)
  714. {
  715.     POINT pt;
  716.     /*
  717.      * The point is coming in relative to the upper left of the
  718.      * dialog window.  It needs to be mapped to points relative
  719.      * to the client of the dialog window.
  720.      */
  721.     pt.x = x;
  722.     pt.y = y;
  723.     MapDlgClientPoint(&pt, FALSE);
  724.     if (pt.x > gptOutlineSelect.x) {
  725.         grcOutlineSelect.left = gptOutlineSelect.x;
  726.         grcOutlineSelect.right = pt.x;
  727.     }
  728.     else {
  729.         grcOutlineSelect.left = pt.x;
  730.         grcOutlineSelect.right = gptOutlineSelect.x;
  731.     }
  732.     if (pt.y > gptOutlineSelect.y) {
  733.         grcOutlineSelect.top = gptOutlineSelect.y;
  734.         grcOutlineSelect.bottom = pt.y;
  735.     }
  736.     else {
  737.         grcOutlineSelect.top = pt.y;
  738.         grcOutlineSelect.bottom = gptOutlineSelect.y;
  739.     }
  740.     if (grcOutlineSelect.left < grcOutlineSelectLimit.left)
  741.         grcOutlineSelect.left = grcOutlineSelectLimit.left;
  742.     if (grcOutlineSelect.right > grcOutlineSelectLimit.right)
  743.         grcOutlineSelect.right = grcOutlineSelectLimit.right;
  744.     if (grcOutlineSelect.top < grcOutlineSelectLimit.top)
  745.         grcOutlineSelect.top = grcOutlineSelectLimit.top;
  746.     if (grcOutlineSelect.bottom > grcOutlineSelectLimit.bottom)
  747.         grcOutlineSelect.bottom = grcOutlineSelectLimit.bottom;
  748. }
  749. /************************************************************************
  750. * OutlineSelectCancel
  751. *
  752. * This routine is used to cancel the display of the outline selection
  753. * rectangle.
  754. *
  755. ************************************************************************/
  756. VOID OutlineSelectCancel(VOID)
  757. {
  758.     OutlineSelectHide();
  759.     ReleaseDC(ghwndTrackOver, ghDCTrack);
  760.     gState = STATE_NORMAL;
  761.     ReleaseCapture();
  762.     SetCursor(hcurArrow);
  763. }
  764. /************************************************************************
  765. * OutlineSelectEnd
  766. *
  767. * This function completes an outline selection operation.  All the
  768. * enclosed controls will be selected.  If the Shift key is down,
  769. * the enclosed controls will be added to the selection, otherwise the
  770. * current selection will be cancelled first.
  771. *
  772. * The current selection will only be cancelled if there is
  773. * at least one new control enclosed.  This is so that simply clicking and
  774. * releasing the mouse without enclosing any controls leaves the current
  775. * selection alone.
  776. *
  777. * Arguments:
  778. *   INT x   - Mouse X location (dialog client coords).
  779. *   INT y   - Mouse Y location (dialog client coords).
  780. *
  781. ************************************************************************/
  782. VOID OutlineSelectEnd(
  783.     INT x,
  784.     INT y)
  785. {
  786.     NPCTYPE npc;
  787.     BOOL fFirstOne = TRUE;
  788.     OutlineSelectCancel();
  789.     OutlineSelectSetRect(x, y);
  790.     /*
  791.      * If the mouse was not moved at all, consider this a request
  792.      * to select the dialog instead of an outline selection operation.
  793.      */
  794.     if (grcOutlineSelect.left == grcOutlineSelect.right &&
  795.             grcOutlineSelect.top == grcOutlineSelect.bottom) {
  796.         SelectControl(gcd.npc, FALSE);
  797.         return;
  798.     }
  799.     /*
  800.      * Convert the selected rectangle to dialog units.
  801.      */
  802.     WinToDURect(&grcOutlineSelect);
  803.     for (npc = npcHead; npc; npc = npc->npcNext) {
  804.         /*
  805.          * Do the rectangles intersect?
  806.          */
  807.         if (npc->rc.left < grcOutlineSelect.right &&
  808.                 grcOutlineSelect.left < npc->rc.right &&
  809.                 npc->rc.bottom > grcOutlineSelect.top &&
  810.                 grcOutlineSelect.bottom > npc->rc.top) {
  811.             if (fFirstOne) {
  812.                 /*
  813.                  * If the Shift key is not held down, cancel any outstanding
  814.                  * selections.
  815.                  */
  816.                 if (!(GetKeyState(VK_SHIFT) & 0x8000))
  817.                     CancelSelection(FALSE);
  818.                 fFirstOne = FALSE;
  819.             }
  820.             /*
  821.              * If the control is not selected, select it.
  822.              */
  823.             if (!npc->fSelected)
  824.                 SelectControl2(npc, TRUE);
  825.         }
  826.     }
  827.     /*
  828.      * Update some things if there was at least one control enclosed.
  829.      */
  830.     if (!fFirstOne) {
  831.         SetAnchorToFirstSel(TRUE);
  832.         StatusUpdate();
  833.         StatusSetEnable();
  834.         RedrawSelection();
  835.         CalcSelectedRect();
  836.     }
  837. }
  838. /************************************************************************
  839. * MyFrameRect
  840. *
  841. * This function draws a one-pixel width rectangle using the given
  842. * raster operation.
  843. *
  844. * Arguments:
  845. *   HDC hDC     - DC to use.
  846. *   PRECT prc   - Rectangle to draw the frame around.
  847. *   DWORD dwRop - RasterOp to use (DSTINVERT, BLACKNESS, etc.).
  848. *
  849. ************************************************************************/
  850. VOID MyFrameRect(
  851.     HDC hDC,
  852.     PRECT prc,
  853.     DWORD dwRop)
  854. {
  855.     INT x;
  856.     INT y;
  857.     POINT pt;
  858.     x = prc->right  - (pt.x = prc->left);
  859.     y = prc->bottom - (pt.y = prc->top);
  860.     PatBlt(hDC, pt.x, pt.y, x, 1, dwRop);
  861.     pt.y = prc->bottom - 1;
  862.     PatBlt(hDC, pt.x, pt.y, x, 1, dwRop);
  863.     pt.y = prc->top;
  864.     PatBlt(hDC, pt.x, pt.y, 1, y, dwRop);
  865.     pt.x = prc->right - 1;
  866.     PatBlt(hDC, pt.x, pt.y, 1, y, dwRop);
  867. }
  868. /************************************************************************
  869. * MoveControl
  870. *
  871. * This function moves the current control to the next grid boundary in
  872. * the specified direction.
  873. *
  874. * Arguments:
  875. *   WPARAM vKey - Virtual key code that was pressed.
  876. *
  877. ************************************************************************/
  878. VOID MoveControl(
  879.     WPARAM vKey)
  880. {
  881.     RECT rc;
  882.     INT dx;
  883.     INT dy;
  884.     /*
  885.      * Nothing is selected.
  886.      */
  887.     if (!gcSelected)
  888.         return;
  889.     rc = grcSelected;
  890.     switch (vKey) {
  891.         case VK_UP:
  892.             dx = 0;
  893.             if (!(dy = -(rc.top % gcyGrid)))
  894.                 dy = -gcyGrid;
  895.             break;
  896.         case VK_DOWN:
  897.             dx = 0;
  898.             dy = gcyGrid - (rc.top % gcyGrid);
  899.             break;
  900.         case VK_RIGHT:
  901.             dx = gcxGrid - (rc.left % gcxGrid);
  902.             dy = 0;
  903.             break;
  904.         case VK_LEFT:
  905.             if (!(dx = -(rc.left % gcxGrid)))
  906.                 dx = -gcxGrid;
  907.             dy = 0;
  908.             break;
  909.     }
  910.     OffsetRect(&rc, dx, dy);
  911.     FitRectToBounds(&rc, gnOverHang, DRAG_CENTER, gfDlgSelected);
  912.     gHandleHit = DRAG_CENTER;
  913.     PositionControl(&rc);
  914. }
  915. /************************************************************************
  916. * PositionControl
  917. *
  918. * This function positions and sizes the current control.  Both the control
  919. * window and its associated drag window are moved at the same time.  The
  920. * coordinates in the control, and the status window display are updated.
  921. * The given rectangle is in dialog units, and it should have already been
  922. * range limited and grid aligned as appropriate.
  923. *
  924. * Arguments:
  925. *   NPWRECT nprc - The rectangle to size/position the control with.
  926. *
  927. ************************************************************************/
  928. VOID PositionControl(
  929.     PRECT prc)
  930. {
  931.     INT cx;
  932.     INT cy;
  933.     RECT rcT;
  934.     NPCTYPE npcT;
  935.     HANDLE hwpi;
  936.     if (gcSelected == 1) {
  937.         /*
  938.          * Did nothing change?
  939.          */
  940.         if (EqualRect(prc, &gnpcSel->rc))
  941.             return;
  942.         /*
  943.          * Only a single control is selected.  Move it.
  944.          */
  945.         PositionControl2(gnpcSel, prc, NULL);
  946.         /*
  947.          * Is the dialog selected, being sized (not just moved), and
  948.          * does it have at least one control?
  949.          */
  950.         if (gfDlgSelected && gHandleHit != DRAG_CENTER && npcHead) {
  951.             cx = prc->left - grcSelected.left;
  952.             cy = prc->top - grcSelected.top;
  953.             /*
  954.              * Did the top or left edge of the dialog move?
  955.              */
  956.             if (cx || cy) {
  957.                 /*
  958.                  * Loop through all the controls.  Move all of them by
  959.                  * the dialog movement delta.
  960.                  */
  961.                 hwpi = BeginDeferWindowPos(cWindows * 2);
  962.                 for (npcT = npcHead; npcT; npcT = npcT->npcNext) {
  963.                     SetRect(&rcT, npcT->rc.left - cx, npcT->rc.top - cy,
  964.                             npcT->rc.right - cx, npcT->rc.bottom - cy);
  965.                     hwpi = PositionControl2(npcT, &rcT, hwpi);
  966.                 }
  967.                 EndDeferWindowPos(hwpi);
  968.             }
  969.         }
  970.     }
  971.     else {
  972.         /*
  973.          * Did nothing change?
  974.          */
  975.         if (EqualRect(prc, &grcSelected))
  976.             return;
  977.         /*
  978.          * There is a group of controls selected.
  979.          * Calculate how much the group rectangle was moved.
  980.          * It is assumed that a group of controls cannot be
  981.          * sized, only moved.
  982.          */
  983.         cx = prc->left - grcSelected.left;
  984.         cy = prc->top - grcSelected.top;
  985.         /*
  986.          * Loop through all the controls.  Move all the selected
  987.          * ones by the group delta.
  988.          */
  989.         hwpi = BeginDeferWindowPos(gcSelected * 2);
  990.         for (npcT = npcHead; npcT; npcT = npcT->npcNext) {
  991.             if (npcT->fSelected) {
  992.                 SetRect(&rcT,
  993.                         npcT->rc.left + cx, npcT->rc.top + cy,
  994.                         npcT->rc.right + cx, npcT->rc.bottom + cy);
  995.                 GridizeRect(&rcT,
  996.                         GRIDIZE_LEFT | GRIDIZE_TOP | GRIDIZE_SAMESIZE);
  997.                 FitRectToBounds(&rcT,
  998.                         GetOverHang(npcT->pwcd->iType,
  999.                         npcT->rc.bottom - npcT->rc.top),
  1000.                         DRAG_CENTER, gfDlgSelected);
  1001.                 hwpi = PositionControl2(npcT, &rcT, hwpi);
  1002.             }
  1003.         }
  1004.         EndDeferWindowPos(hwpi);
  1005.     }
  1006.     CalcSelectedRect();
  1007.     StatusSetCoords(&gnpcSel->rc);
  1008.     gfResChged = gfDlgChanged = TRUE;
  1009.     ShowFileStatus(FALSE);
  1010. }
  1011. /************************************************************************
  1012. * PositionControl2
  1013. *
  1014. * This function positions and sizes a single control.  Both the control
  1015. * window and its associated drag window are moved at the same time.  The
  1016. * coordinates in the control are updated.
  1017. *
  1018. * The given rectangle is in dialog units, and it should have already been
  1019. * range limited and grid aligned as appropriate.
  1020. *
  1021. * Arguments:
  1022. *   NPCTYPE npc - The control to position.
  1023. *   PRECT prc   - The rectangle to size/position the control with.
  1024. *   HANDLE hwpi - Handle that has been returned from a BeginDeferWindowPos
  1025. *                 call.  If this parameter is not NULL, the calls to
  1026. *                 position the control and drag window will use this
  1027. *                 handle.
  1028. *
  1029. * Returns:
  1030. *
  1031. * The return will be the hwpi handle that is currently being used.
  1032. * The variable that the caller is using must be updated with the
  1033. * return value each call, because each call to DeferWindowPos can
  1034. * possibly change this value.
  1035. *
  1036. ************************************************************************/
  1037. STATICFN HANDLE PositionControl2(
  1038.     NPCTYPE npc,
  1039.     PRECT prc,
  1040.     HANDLE hwpi)
  1041. {
  1042.     RECT rc;
  1043.     RECT rcDrag;
  1044.     HANDLE hwpi2;
  1045.     /*
  1046.      * Make a local copy of the rectangle.
  1047.      */
  1048.     rc = *prc;
  1049.     /*
  1050.      * Start calculating the new position.  Begin by mapping the dialog
  1051.      * points to window coordinates.
  1052.      */
  1053.     DUToWinRect(&rc);
  1054.     if (npc->pwcd->iType == W_DIALOG) {
  1055.         InvalidateDlgHandles();
  1056.         AdjustWindowRectEx(&rc, npc->flStyle, FALSE,
  1057.                 (npc->flStyle & DS_MODALFRAME) ?
  1058.                 npc->flExtStyle | WS_EX_DLGMODALFRAME : npc->flExtStyle);
  1059.         ClientToScreenRect(ghwndSubClient, &rc);
  1060.         MoveWindow(npc->hwnd, rc.left, rc.top,
  1061.                 rc.right - rc.left, rc.bottom - rc.top, TRUE);
  1062.         /*
  1063.          * Update the control structure with the new rectangle.
  1064.          */
  1065.         npc->rc = *prc;
  1066.         /*
  1067.          * Since this was the dialog that was just positioned, we need to
  1068.          * recalculate and save the size of its "client" area.
  1069.          */
  1070.         SaveDlgClientRect(npc->hwnd);
  1071.     }
  1072.     else {
  1073.         rcDrag = rc;
  1074.         InflateRect(&rcDrag, CHANDLESIZE / 2, CHANDLESIZE / 2);
  1075.         if (hwpi)
  1076.             hwpi2 = hwpi;
  1077.         else
  1078.             hwpi2 = BeginDeferWindowPos(2);
  1079.         hwpi2 = DeferWindowPos(hwpi2, npc->hwndDrag, NULL,
  1080.                 rcDrag.left, rcDrag.top,
  1081.                 rcDrag.right - rcDrag.left, rcDrag.bottom - rcDrag.top,
  1082.                 SWP_NOACTIVATE | SWP_NOZORDER);
  1083.         hwpi2 = DeferWindowPos(hwpi2, npc->hwnd, NULL, rc.left, rc.top,
  1084.                 rc.right - rc.left, rc.bottom - rc.top,
  1085.                 SWP_NOACTIVATE | SWP_NOZORDER);
  1086.         if (!hwpi)
  1087.             EndDeferWindowPos(hwpi2);
  1088.         /*
  1089.          * Update the control structure with the new rectangle.
  1090.          */
  1091.         npc->rc = *prc;
  1092.         InvalidateRect(npc->hwndDrag, NULL, TRUE);
  1093.     }
  1094.     return hwpi2;
  1095. }
  1096. /************************************************************************
  1097. * RepositionDialog
  1098. *
  1099. * This routine forces the dialog to be moved to the location that
  1100. * is stored in it's npc rectangle.  This is necessary after the
  1101. * main application has been moved, because the dialog is relative
  1102. * to the app's window and does not automatically move with it.
  1103. *
  1104. ************************************************************************/
  1105. VOID RepositionDialog(VOID)
  1106. {
  1107.     PositionControl2(gcd.npc, &gcd.npc->rc, NULL);
  1108. }
  1109. /************************************************************************
  1110. * SaveDlgClientRect
  1111. *
  1112. * This routine saves away a global that will contain the offset, in window
  1113. * coordinates, of the origin of the "client" area for the current dialog.
  1114. *
  1115. * Arguments:
  1116. *   HWND hwndDlg - The dialog window.
  1117. *
  1118. ************************************************************************/
  1119. VOID SaveDlgClientRect(
  1120.     HWND hwndDlg)
  1121. {
  1122.     RECT rcFrame;
  1123.     POINT pt;
  1124.     GetWindowRect(hwndDlg, &rcFrame);
  1125.     GetClientRect(hwndDlg, &grcDlgClient);
  1126.     pt.x = pt.y = 0;
  1127.     ClientToScreen(hwndDlg, &pt);
  1128.     OffsetRect(&grcDlgClient, pt.x - rcFrame.left, pt.y - rcFrame.top);
  1129. }
  1130. /************************************************************************
  1131. * SizeToText
  1132. *
  1133. * This function will size all the selected controls to fit their text.
  1134. * This is to support the "Size to text" command of the "Edit" menu.
  1135. *
  1136. * Globals are updated appropriately if anything actually had to change.
  1137. *
  1138. ************************************************************************/
  1139. VOID SizeToText(VOID)
  1140. {
  1141.     NPCTYPE npc;
  1142.     BOOL fSized = FALSE;
  1143.     /*
  1144.      * Loop through all the controls.
  1145.      */
  1146.     for (npc = npcHead; npc; npc = npc->npcNext) {
  1147.         /*
  1148.          * Is the control selected, and can it be sized to its text?
  1149.          */
  1150.         if (npc->fSelected && npc->pwcd->fSizeToText)
  1151.             fSized |= SizeCtrlToText(npc);
  1152.     }
  1153.     /*
  1154.      * Was anything modified?
  1155.      */
  1156.     if (fSized) {
  1157.         CalcSelectedRect();
  1158.         StatusSetCoords(&gnpcSel->rc);
  1159.         gfResChged = gfDlgChanged = TRUE;
  1160.         ShowFileStatus(FALSE);
  1161.     }
  1162. }
  1163. /************************************************************************
  1164. * SizeCtrlToText
  1165. *
  1166. * This function sizes a single control so that it just fits its text.
  1167. * This does not make sense for all controls (see the fSizeToText flag
  1168. * in the awcd array), and there are different rules for the different
  1169. * types of controls.
  1170. *
  1171. * Arguments:
  1172. *   NPCTYPE npc - The control to size.
  1173. *
  1174. * Returns:
  1175. *
  1176. * The return value is TRUE if the control was modified (sized) or
  1177. * FALSE if it was not.
  1178. *
  1179. ************************************************************************/
  1180. STATICFN BOOL SizeCtrlToText(
  1181.     NPCTYPE npc)
  1182. {
  1183.     RECT rc;
  1184.     INT x;
  1185.     INT cxLowern;
  1186.     switch (npc->pwcd->iType) {
  1187.         case W_CHECKBOX:
  1188.             /*
  1189.              * Take the width of the text, plus some pixels for the square.
  1190.              */
  1191.             x = QueryTextExtent(npc->hwnd, npc->text, TRUE) + 20;
  1192.             x = MulDiv(x, 4, gcd.cxChar) + 1;
  1193.             break;
  1194.         case W_PUSHBUTTON:
  1195.             /*
  1196.              * The UITF definition of the size of a pushbutton says
  1197.              * that the left and right margins should be approximately
  1198.              * the width of a lowercase "n".  In any event, the width
  1199.              * cannot be less than the default size.
  1200.              */
  1201.             cxLowern = QueryTextExtent(npc->hwnd, L"n", FALSE);
  1202.             x = QueryTextExtent(npc->hwnd, npc->text, FALSE) + (2 * cxLowern);
  1203.             x = MulDiv(x, 4, gcd.cxChar);
  1204.             if (x < awcd[W_PUSHBUTTON].cxDefault)
  1205.                 x = awcd[W_PUSHBUTTON].cxDefault;
  1206.             break;
  1207.         case W_RADIOBUTTON:
  1208.             /*
  1209.              * Take the width of the text, plus some for the circle.
  1210.              */
  1211.             x = QueryTextExtent(npc->hwnd, npc->text, TRUE) + 20;
  1212.             x = MulDiv(x, 4, gcd.cxChar) + 1;
  1213.             break;
  1214.         case W_TEXT:
  1215.             /*
  1216.              * Take the width of the text.
  1217.              */
  1218.             x = QueryTextExtent(npc->hwnd, npc->text, TRUE);
  1219.             x = MulDiv(x, 4, gcd.cxChar) + 1;
  1220.             break;
  1221.         case W_CUSTOM:
  1222.             /*
  1223.              * Call out to the custom control and let it decide
  1224.              * how wide the text should be.
  1225.              */
  1226.             x = CallCustomSizeToText(npc);
  1227.             break;
  1228.         default:
  1229.             x = -1;
  1230.             break;
  1231.     }
  1232.     /*
  1233.      * Does it need to be sized?
  1234.      */
  1235.     if (x != -1 && x != npc->rc.right - npc->rc.left) {
  1236.         /*
  1237.          * Now that we know the size we want the control, position
  1238.          * it to change that size.  Note that we do NOT gridize
  1239.          * the left edge here.  The user probably just wants the
  1240.          * right edge of the control to be adjusted to fit the new
  1241.          * text, and probably does not want the left edge shifting
  1242.          * on them.
  1243.          */
  1244.         rc = npc->rc;
  1245.         rc.right = rc.left + x;
  1246.         FitRectToBounds(&rc,
  1247.                 GetOverHang(npc->pwcd->iType, npc->rc.bottom - npc->rc.top),
  1248.                 DRAG_CENTER, gfDlgSelected);
  1249.         PositionControl2(npc, &rc, NULL);
  1250.         return TRUE;
  1251.     }
  1252.     return FALSE;
  1253. }
  1254. /************************************************************************
  1255. * QueryTextExtent
  1256. *
  1257. * This function takes a window handle and text, and will return the
  1258. * number of pixels that the specified text is wide in that window.
  1259. * It is used to determine how wide a control needs to be to display
  1260. * its text.
  1261. *
  1262. * The font set into the current dialog is taken into consideration
  1263. * when calculating the size.
  1264. *
  1265. * Arguments:
  1266. *   HWND hwnd       - The control window handle.
  1267. *   LPTSTR pszText  - The text of the control.
  1268. *   BOOL fWordBreak - TRUE if this text will be drawn with DT_WORDBREAK.
  1269. *
  1270. * Returns:
  1271. *
  1272. * The number of pixels wide the selected text is.
  1273. *
  1274. ************************************************************************/
  1275. STATICFN INT QueryTextExtent(
  1276.     HWND hwnd,
  1277.     LPTSTR pszText,
  1278.     BOOL fWordBreak)
  1279. {
  1280.     HDC hDC;
  1281.     INT iHeight;
  1282.     RECT rc;
  1283.     INT nLen;
  1284.     HFONT hfontOld;
  1285.     if (!pszText || *pszText == CHAR_NULL)
  1286.         return 0;
  1287.     hDC = GetDC(hwnd);
  1288.     /*
  1289.      * If there is a valid font, select it into the DC.  Note that
  1290.      * we look at gcd.hFont instead of gcd.fFontSpecified, because
  1291.      * it is possible to specify a font for the dialog but not have
  1292.      * been able to create it.
  1293.      */
  1294.     if (gcd.hFont)
  1295.         hfontOld = SelectObject(hDC, gcd.hFont);
  1296.     /*
  1297.      * First, calculate the length of the text.
  1298.      */
  1299.     rc.left = rc.top = 0;
  1300.     rc.right = 10000;
  1301.     rc.bottom = 10000;
  1302.     nLen = lstrlen(pszText);
  1303.     DrawText(hDC, pszText, nLen, &rc,
  1304.             DT_CALCRECT | DT_NOCLIP | DT_EXPANDTABS);
  1305.     /*
  1306.      * First save the height of the line.  This works because the
  1307.      * DrawText call above with DT_CALCRECT will always draw on
  1308.      * a single line.  Then we move the upwards the rectangle to draw
  1309.      * in a large amount, so that it is outside the dimensions of
  1310.      * the control.  Finally, we do a real draw of the text,
  1311.      * increasing the width a little each time until we are able
  1312.      * to draw entirely on one line.  This is inefficient, but it does
  1313.      * ensure that the returned width will be enough to actually
  1314.      * draw the string.
  1315.      */
  1316.     if (fWordBreak) {
  1317.         iHeight = rc.bottom - rc.top;
  1318.         rc.top -= 10000;
  1319.         rc.bottom -= 10000;
  1320.         while (TRUE) {
  1321.             /*
  1322.              * Determine if we have enough width to draw on a single
  1323.              * line yet.
  1324.              */
  1325.             if (DrawText(hDC, pszText, nLen, &rc,
  1326.                     DT_NOCLIP | DT_EXPANDTABS | DT_WORDBREAK) == iHeight)
  1327.                 break;
  1328.             /*
  1329.              * Nope, push the right margin out and try again...
  1330.              */
  1331.             rc.right++;
  1332.         }
  1333.     }
  1334.     if (gcd.hFont)
  1335.         SelectObject(hDC, hfontOld);
  1336.     ReleaseDC(hwnd, hDC);
  1337.     return rc.right - rc.left;
  1338. }
  1339. /************************************************************************
  1340. * AlignControls
  1341. *
  1342. * This function will align all the selected controls.  The point to
  1343. * align to is always taken from the currently selected control.
  1344. *
  1345. * In all cases, the resulting desired position of the control will be
  1346. * gridized and limited to the dialogs "client" area.  The size of the
  1347. * controls will not be changed.
  1348. *
  1349. * Arguments:
  1350. *   INT cmd - The alignment menu command.
  1351. *
  1352. *   The following are valid values for cmd:
  1353. *
  1354. *   MENU_ALIGNLEFT      - Align to the left edge.
  1355. *   MENU_ALIGNVERT      - Align to the center vertically.
  1356. *   MENU_ALIGNRIGHT     - Align to the right edge.
  1357. *   MENU_ALIGNTOP       - Align to the top edge.
  1358. *   MENU_ALIGNHORZ      - Align to the center horizontally.
  1359. *   MENU_ALIGNBOTTOM    - Align to the bottom edge.
  1360. *
  1361. ************************************************************************/
  1362. VOID AlignControls(
  1363.     INT cmd)
  1364. {
  1365.     register INT sDelta;
  1366.     NPCTYPE npc;
  1367.     RECT rc;
  1368.     BOOL fMove;
  1369.     BOOL fModified = FALSE;
  1370.     /*
  1371.      * Loop through all the controls.  Align all the selected ones.
  1372.      */
  1373.     for (npc = npcHead; npc; npc = npc->npcNext) {
  1374.         if (npc->fSelected && npc != gnpcSel) {
  1375.             rc = npc->rc;
  1376.             fMove = FALSE;
  1377.             switch (cmd) {
  1378.                 case MENU_ALIGNLEFT:
  1379.                     if (sDelta = gnpcSel->rc.left - rc.left) {
  1380.                         fMove = TRUE;
  1381.                         rc.left += sDelta;
  1382.                         rc.right += sDelta;
  1383.                     }
  1384.                     break;
  1385.                 case MENU_ALIGNVERT:
  1386.                     if (sDelta =
  1387.                             (((gnpcSel->rc.right - gnpcSel->rc.left) / 2)
  1388.                             + gnpcSel->rc.left) -
  1389.                             (((rc.right - rc.left) / 2) +
  1390.                             rc.left)) {
  1391.                         fMove = TRUE;
  1392.                         rc.left += sDelta;
  1393.                         rc.right += sDelta;
  1394.                     }
  1395.                     break;
  1396.                 case MENU_ALIGNRIGHT:
  1397.                     if (sDelta = gnpcSel->rc.right - rc.right) {
  1398.                         fMove = TRUE;
  1399.                         rc.left += sDelta;
  1400.                         rc.right += sDelta;
  1401.                     }
  1402.                     break;
  1403.                 case MENU_ALIGNTOP:
  1404.                     if (sDelta = gnpcSel->rc.top - rc.top) {
  1405.                         fMove = TRUE;
  1406.                         rc.top += sDelta;
  1407.                         rc.bottom += sDelta;
  1408.                     }
  1409.                     break;
  1410.                 case MENU_ALIGNHORZ:
  1411.                     if (sDelta =
  1412.                             (((gnpcSel->rc.bottom - gnpcSel->rc.top) / 2)
  1413.                             + gnpcSel->rc.top) -
  1414.                             (((rc.bottom - rc.top) / 2) +
  1415.                             rc.top)) {
  1416.                         fMove = TRUE;
  1417.                         rc.top += sDelta;
  1418.                         rc.bottom += sDelta;
  1419.                     }
  1420.                     break;
  1421.                 case MENU_ALIGNBOTTOM:
  1422.                     if (sDelta = gnpcSel->rc.bottom - rc.bottom) {
  1423.                         fMove = TRUE;
  1424.                         rc.top += sDelta;
  1425.                         rc.bottom += sDelta;
  1426.                     }
  1427.                     break;
  1428.             }
  1429.             if (fMove) {
  1430.                 GridizeRect(&rc,
  1431.                         GRIDIZE_LEFT | GRIDIZE_TOP | GRIDIZE_SAMESIZE);
  1432.                 FitRectToBounds(&rc, GetOverHang(npc->pwcd->iType,
  1433.                         npc->rc.bottom - npc->rc.top),
  1434.                         DRAG_CENTER, FALSE);
  1435.                 if (!EqualRect(&rc, &npc->rc)) {
  1436.                     PositionControl2(npc, &rc, NULL);
  1437.                     fModified = TRUE;
  1438.                 }
  1439.             }
  1440.         }
  1441.     }
  1442.     if (fModified) {
  1443.         RedrawSelection();
  1444.         CalcSelectedRect();
  1445.         gfResChged = gfDlgChanged = TRUE;
  1446.         ShowFileStatus(FALSE);
  1447.         StatusUpdate();
  1448.     }
  1449. }
  1450. /************************************************************************
  1451. * ArrangeSpacing
  1452. *
  1453. * This function will evenly space all the selected controls.  The
  1454. * currently selected control is not moved (unless it has to be gridized)
  1455. * and any previous controls in Z order will be evenly spaced to the
  1456. * left or above the anchor, and all controls following in Z order will
  1457. * be evenly spaced below or to the right of the anchor.
  1458. *
  1459. * The spacing values used are gxSpace and gySpace.
  1460. *
  1461. * In all cases, the resulting desired position of the control will be
  1462. * gridized and limited to the dialogs "client" area.  The size of the
  1463. * controls is not changed.
  1464. *
  1465. * Arguments:
  1466. *   INT cmd - The Arrange/Even spacing menu command.
  1467. *
  1468. *   The following are valid values for cmd:
  1469. *
  1470. *   MENU_SPACEHORZ - Space the controls left and right.
  1471. *   MENU_SPACEVERT - Space all the controls up and down.
  1472. *
  1473. ************************************************************************/
  1474. VOID ArrangeSpacing(
  1475.     INT cmd)
  1476. {
  1477.     NPCTYPE npc;
  1478.     RECT rc;
  1479.     BOOL fModified = FALSE;
  1480.     INT x;
  1481.     INT y;
  1482.     INT cPreceding;
  1483.     INT xTotalWidth;
  1484.     INT yTotalWidth;
  1485.     cPreceding = 0;
  1486.     xTotalWidth = 0;
  1487.     yTotalWidth = 0;
  1488.     for (npc = npcHead; npc; npc = npc->npcNext) {
  1489.         if (npc->fSelected) {
  1490.             if (npc == gnpcSel)
  1491.                 break;
  1492.             cPreceding++;
  1493.             xTotalWidth += npc->rc.right - npc->rc.left;
  1494.             yTotalWidth += npc->rc.bottom - npc->rc.top;
  1495.         }
  1496.     }
  1497.     x = gnpcSel->rc.left;
  1498.     y = gnpcSel->rc.top;
  1499.     if (cPreceding) {
  1500.         x -= xTotalWidth + (gxSpace * cPreceding);
  1501.         y -= yTotalWidth + (gySpace * cPreceding);
  1502.     }
  1503.     /*
  1504.      * Loop through all the controls.  Space all the selected ones.
  1505.      */
  1506.     for (npc = npcHead; npc; npc = npc->npcNext) {
  1507.         if (npc->fSelected) {
  1508.             rc = npc->rc;
  1509.             switch (cmd) {
  1510.                 case MENU_SPACEVERT:
  1511.                     rc.top = y;
  1512.                     rc.bottom = y + (npc->rc.bottom - npc->rc.top);
  1513.                     y = rc.bottom + gySpace;
  1514.                     break;
  1515.                 case MENU_SPACEHORZ:
  1516.                     rc.left = x;
  1517.                     rc.right = x + (npc->rc.right - npc->rc.left);
  1518.                     x = rc.right + gxSpace;
  1519.                     break;
  1520.             }
  1521.             GridizeRect(&rc, GRIDIZE_LEFT | GRIDIZE_TOP | GRIDIZE_SAMESIZE);
  1522.             FitRectToBounds(&rc, GetOverHang(npc->pwcd->iType,
  1523.                     npc->rc.bottom - npc->rc.top),
  1524.                     DRAG_CENTER, FALSE);
  1525.             if (!EqualRect(&rc, &npc->rc)) {
  1526.                 PositionControl2(npc, &rc, NULL);
  1527.                 fModified = TRUE;
  1528.             }
  1529.         }
  1530.     }
  1531.     if (fModified) {
  1532.         RedrawSelection();
  1533.         CalcSelectedRect();
  1534.         gfResChged = gfDlgChanged = TRUE;
  1535.         ShowFileStatus(FALSE);
  1536.         StatusUpdate();
  1537.     }
  1538. }
  1539. /************************************************************************
  1540. * ArrangeSize
  1541. *
  1542. * This function will evenly size all the selected controls.  The
  1543. * currently selected control determines the size that the other
  1544. * controls will be set to in the given dimension.
  1545. *
  1546. * In all cases, the resulting size of the control will be gridized and
  1547. * limited to the dialogs "client" area.
  1548. *
  1549. * Arguments:
  1550. *   INT cmd - The Arrange/Same size menu command.
  1551. *
  1552. * The following are valid values for cmd:
  1553. *
  1554. *   MENU_ARRSIZEWIDTH  - Size the widths of the controls.
  1555. *   MENU_ARRSIZEHEIGHT - Size the heights of the controls.
  1556. *
  1557. ************************************************************************/
  1558. VOID ArrangeSize(
  1559.     INT cmd)
  1560. {
  1561.     NPCTYPE npc;
  1562.     RECT rc;
  1563.     BOOL fModified = FALSE;
  1564.     INT cx;
  1565.     INT cy;
  1566.     cx = gnpcSel->rc.right - gnpcSel->rc.left;
  1567.     cy = gnpcSel->rc.bottom - gnpcSel->rc.top;
  1568.     /*
  1569.      * Loop through all the controls, operating on the selected ones.
  1570.      */
  1571.     for (npc = npcHead; npc; npc = npc->npcNext) {
  1572.         /*
  1573.          * Is the control selected, and is it sizeable?
  1574.          */
  1575.         if (npc->fSelected && npc->pwcd->fSizeable) {
  1576.             rc = npc->rc;
  1577.             switch (cmd) {
  1578.                 case MENU_ARRSIZEWIDTH:
  1579.                     rc.right = npc->rc.left + cx;
  1580.                     break;
  1581.                 case MENU_ARRSIZEHEIGHT:
  1582.                     rc.top = npc->rc.bottom - cy;
  1583.                     break;
  1584.             }
  1585.             GridizeRect(&rc, GRIDIZE_LEFT | GRIDIZE_TOP |
  1586.                     GRIDIZE_RIGHT | GRIDIZE_BOTTOM);
  1587.             FitRectToBounds(&rc, GetOverHang(npc->pwcd->iType,
  1588.                     npc->rc.bottom - npc->rc.top),
  1589.                     DRAG_CENTER, FALSE);
  1590.             if (!EqualRect(&rc, &npc->rc)) {
  1591.                 PositionControl2(npc, &rc, NULL);
  1592.                 fModified = TRUE;
  1593.             }
  1594.         }
  1595.     }
  1596.     if (fModified) {
  1597.         RedrawSelection();
  1598.         CalcSelectedRect();
  1599.         gfResChged = gfDlgChanged = TRUE;
  1600.         ShowFileStatus(FALSE);
  1601.         StatusUpdate();
  1602.     }
  1603. }
  1604. /************************************************************************
  1605. * ArrangePushButtons
  1606. *
  1607. * This function will arrange push buttons along either the bottom of
  1608. * the dialog or along the right side of the dialog.  It will operate
  1609. * on the selected buttons (which button is currently selected does not
  1610. * matter) but this function can also be used if buttons are not selected
  1611. * in a couple of special cases.  If either the dialog or nothing is
  1612. * selected, ALL the push buttons in the dialog will be arranged.
  1613. *
  1614. * The margin values used are gxMargin and gyMargin, and the spacing
  1615. * between buttons is gxMinPushSpace, gxMaxPushSpace and gyPushSpace.
  1616. *
  1617. * In all cases, the resulting desired position of the buttons will be
  1618. * gridized and limited to the dialogs "client" area.  The size of the
  1619. * push buttons is not changed.
  1620. *
  1621. * Arguments:
  1622. *   INT cmd - The Arrange/Push buttons menu command.
  1623. *
  1624. *   The following are valid values for cmd:
  1625. *
  1626. *   MENU_ARRPUSHBOTTOM - Arrange push buttons along the bottom.
  1627. *   MENU_ARRPUSHRIGHT  - Arrange push buttons along the right side.
  1628. *
  1629. ************************************************************************/
  1630. VOID ArrangePushButtons(
  1631.     INT cmd)
  1632. {
  1633.     NPCTYPE npc;
  1634.     RECT rc;
  1635.     BOOL fModified = FALSE;
  1636.     INT x;                          // Note: These values must be signed.
  1637.     INT y;
  1638.     INT cxDlg;
  1639.     INT cButtons;
  1640.     INT xTotal;
  1641.     INT xInterSpace;
  1642.     switch (cmd) {
  1643.         case MENU_ARRPUSHBOTTOM:
  1644.             cxDlg = gcd.npc->rc.right - gcd.npc->rc.left;
  1645.             y = (gcd.npc->rc.bottom - gcd.npc->rc.top) - gyMargin;
  1646.             for (cButtons = 0, xTotal = 0, npc = npcHead; npc;
  1647.                     npc = npc->npcNext) {
  1648.                 if (npc->pwcd->iType == W_PUSHBUTTON &&
  1649.                         (!gcSelected || gfDlgSelected || npc->fSelected)) {
  1650.                     cButtons++;
  1651.                     xTotal += npc->rc.right - npc->rc.left;
  1652.                 }
  1653.             }
  1654.             if (cButtons == 1) {
  1655.                 x = (cxDlg - xTotal) / 2;
  1656.                 xInterSpace = 0;
  1657.             }
  1658.             else {
  1659.                 xInterSpace = (cxDlg - xTotal) / (cButtons + 1);
  1660.                 if (xInterSpace > gxMaxPushSpace)
  1661.                     xInterSpace = gxMaxPushSpace;
  1662.                 else if (xInterSpace < gxMinPushSpace)
  1663.                     xInterSpace = gxMinPushSpace;
  1664.                 x = (cxDlg - ((cButtons - 1) * xInterSpace) - xTotal) / 2;
  1665.                 if (x < 0)
  1666.                     x = 0;
  1667.                 if (x < gxMargin && xInterSpace > gxMinPushSpace) {
  1668.                     xInterSpace = (cxDlg - xTotal - (2 * gxMargin))
  1669.                             / (cButtons - 1);
  1670.                     if (xInterSpace < gxMinPushSpace)
  1671.                         xInterSpace = gxMinPushSpace;
  1672.                     x = (cxDlg - ((cButtons - 1) * xInterSpace) - xTotal)
  1673.                             / 2;
  1674.                     if (x < 0)
  1675.                         x = 0;
  1676.                 }
  1677.             }
  1678.             break;
  1679.         case MENU_ARRPUSHRIGHT:
  1680.             x = (gcd.npc->rc.right - gcd.npc->rc.left) - gxMargin;
  1681.             y = gyMargin;
  1682.             break;
  1683.     }
  1684.     /*
  1685.      * Loop through all the controls.
  1686.      */
  1687.     for (npc = npcHead; npc; npc = npc->npcNext) {
  1688.         /*
  1689.          * We will arrange this control only if it is a pushbutton,
  1690.          * and only if one of the following is true: there are no
  1691.          * controls selected, or the dialog itself is selected, or
  1692.          * there are some controls selected and this pushbutton is
  1693.          * one of them.
  1694.          */
  1695.         if (npc->pwcd->iType == W_PUSHBUTTON &&
  1696.                 (!gcSelected || gfDlgSelected || npc->fSelected)) {
  1697.             rc = npc->rc;
  1698.             switch (cmd) {
  1699.                 case MENU_ARRPUSHBOTTOM:
  1700.                     rc.left = x;
  1701.                     rc.top = y - (npc->rc.bottom - npc->rc.top);
  1702.                     rc.bottom = y;
  1703.                     rc.right = rc.left + (npc->rc.right - npc->rc.left);
  1704.                     x = rc.right + xInterSpace;
  1705.                     break;
  1706.                 case MENU_ARRPUSHRIGHT:
  1707.                     rc.left = x - (npc->rc.right - npc->rc.left);
  1708.                     rc.bottom = y + (npc->rc.bottom - npc->rc.top);
  1709.                     rc.right = x;
  1710.                     rc.top = y;
  1711.                     y = rc.bottom + gyPushSpace;
  1712.                     break;
  1713.             }
  1714.             GridizeRect(&rc, GRIDIZE_LEFT | GRIDIZE_TOP | GRIDIZE_SAMESIZE);
  1715.             FitRectToBounds(&rc, GetOverHang(npc->pwcd->iType,
  1716.                     npc->rc.bottom - npc->rc.top),
  1717.                     DRAG_CENTER, FALSE);
  1718.             if (!EqualRect(&rc, &npc->rc)) {
  1719.                 PositionControl2(npc, &rc, NULL);
  1720.                 fModified = TRUE;
  1721.             }
  1722.         }
  1723.     }
  1724.     if (fModified) {
  1725.         if (gfDlgSelected || !gcSelected)
  1726.             InvalidateRect(gcd.npc->hwnd, NULL, TRUE);
  1727.         CalcSelectedRect();
  1728.         gfResChged = gfDlgChanged = TRUE;
  1729.         ShowFileStatus(FALSE);
  1730.         StatusUpdate();
  1731.     }
  1732. }