/******************************************************************************\
* This is a part of the Microsoft Source Code Samples.
* Copyright 1993 - 1998 Microsoft Corporation.
* All rights reserved.
* This source code is only intended as a supplement to
* Microsoft Development Tools and/or WinHelp documentation.
* See these sources for detailed information regarding the
* Microsoft samples programs.
\******************************************************************************/
/****************************** Module Header *******************************
* Module Name: select.c
*
* Contains routines for selecting and positioning controls.
*
* Functions:
*
* SelectControl()
* SelectControl2()
* RedrawSelection()
* SetAnchorToFirstSel()
* SelectNext()
* SelectPrevious()
* UnSelectControl()
* CalcSelectedRect()
* CancelSelection()
* OutlineSelectBegin()
* OutlineSelectDraw()
* OutlineSelectCancel()
* OutlineSelectEnd()
* MyFrameRect()
* MoveControl()
* PositionControl()
* RepositionDialog()
* SaveDlgClientRect()
* SizeToText()
* AlignControls()
* ArrangeSpacing()
* ArrangeSize()
* ArrangePushButtons()
* InvalidateDlgHandles()
* OutlineSelectHide()
* OutlineSelectSetRect()
* PositionControl2()
* SizeCtrlToText()
* QueryTextExtent()
*
* Comments:
*
****************************************************************************/
#include "dlgedit.h"
#include "dlgfuncs.h"
#include "dlgextrn.h"
STATICFN VOID InvalidateDlgHandles(VOID);
STATICFN VOID OutlineSelectHide(VOID);
STATICFN VOID OutlineSelectSetRect(INT x, INT y);
STATICFN HANDLE PositionControl2(NPCTYPE npc, PRECT prc, HANDLE hwpi);
STATICFN BOOL SizeCtrlToText(NPCTYPE npc);
STATICFN INT QueryTextExtent(HWND hwnd, LPTSTR pszText, BOOL fWordBreak);
static POINT gptOutlineSelect;
static RECT grcOutlineSelect;
static RECT grcOutlineSelectLimit;
static BOOL gfOutlineSelectShown = FALSE;
/************************************************************************
* SelectControl
*
* This routine selects a control, showing its drag window and handles.
* If fCheckShift is TRUE and the shift key is down, this routine adds
* the control to the existing selection, unless the control is already
* selected, in which case it is removed from the existing selection.
*
* This routine handles the case where a controls is clicked on to select
* it, and this may cause other controls to be unselected. If it is
* known for sure that a control should be selected or added to the
* existing selection, SelectControl2 can be used instead.
*
* Arguments:
* NPCTYPE npc = The control to select.
* BOOL fCheckShift = TRUE if the state of the shift key should be
* taken into consideration.
*
* Returns:
* The return will be FALSE if the control was just unselected.
*
************************************************************************/
BOOL SelectControl(
NPCTYPE npc,
BOOL fCheckShift)
{
BOOL fShiftDown;
BOOL fSelectDone = TRUE;
if (npc->pwcd->iType == W_DIALOG) {
if (gnpcSel == npc)
return TRUE;
CancelSelection(FALSE);
SelectControl2(npc, FALSE);
}
else {
if (fCheckShift)
fShiftDown = (GetKeyState(VK_SHIFT) & 0x8000) ? TRUE : FALSE;
else
fShiftDown = FALSE;
if (npc->fSelected) {
/*
* If the shift key is down, and they are NOT trying to
* select the dialog, toggle the selection of this control
* to off.
*/
if (fShiftDown && npc->pwcd->iType != W_DIALOG) {
UnSelectControl(npc);
CalcSelectedRect();
fSelectDone = FALSE;
}
else {
if (gnpcSel == npc)
return TRUE;
else
SelectControl2(npc, FALSE);
}
}
else {
/*
* If they are NOT holding the shift key down, or the
* dialog is selected, cancel the selection first.
*/
if (!fShiftDown || gcd.npc->fSelected == TRUE)
CancelSelection(FALSE);
SelectControl2(npc, FALSE);
}
}
StatusUpdate();
StatusSetEnable();
return fSelectDone;
}
/************************************************************************
* SelectControl2
*
* This routine is the worker for SelectControl. It does the actual
* work to "select" a control, updating globals and showing the drag
* windows with handles.
*
* This routine handles the special case where we are selecting a
* control that is already selected. The editor has the concept of
* a control being selected, as well as there being the currently
* selected control (pointed to by gnpcSel). There can be the case
* where there are multiple controls selected, but only one will be
* the current selection (usually the last one clicked on). This
* routine will never unselect other controls. This must be done
* prior to here, if appropriate.
*
* Arguments:
* NPCTYPE npc = The control to make the current selection.
* BOOL fDontUpdate = TRUE if the selection should NOT be redrawn
* after the specified control is added to it.
* This allows painting to be deferred until
* later if a number of controls are being
* selected in a loop. It also does not call
* CalcSelectedRect (this MUST be done later
* for drags to work, however!).
* Comments:
*
* If fDontUpdate is TRUE, the selection will not be redrawn, and it
* is required that CalcSelectedRect be called before doing any drag
* operations.
*
************************************************************************/
VOID SelectControl2(
NPCTYPE npc,
BOOL fDontUpdate)
{
BOOL fUpdate = FALSE;
/*
* Is the control already selected?
*/
if (npc->fSelected) {
/*
* It is already selected (hwndDrag is visible). If it is
* not the current selection, we want all drag windows to
* be redrawn in the proper order to update their appearance.
*/
if (gnpcSel != npc)
fUpdate = TRUE;
}
else {
/*
* The control is not yet selected. If another control is
* currently selected, we want all drag windows to be
* updated so that their handle appearance gets updated.
*/
if (gnpcSel)
fUpdate = TRUE;
/*
* Flip its flag and add to the selected count.
*/
npc->fSelected = TRUE;
gcSelected++;
}
gnpcSel = npc;
if (!fDontUpdate)
CalcSelectedRect();
if (npc->pwcd->iType == W_DIALOG) {
gfDlgSelected = TRUE;
InvalidateDlgHandles();
}
else {
gfDlgSelected = FALSE;
ShowWindow(npc->hwndDrag, SW_SHOW);
if (fUpdate && !fDontUpdate)
RedrawSelection();
}
}
/************************************************************************
* RedrawSelection
*
* This function cause all the selected drag windows to be invalidated.
* This is necessary whenever one of them changes, because of the very
* touchy painting order that has to be maintained. Without invalidating
* all of them as a unit, there are cases where handles do not get
* properly painted.
*
************************************************************************/
VOID RedrawSelection(VOID)
{
NPCTYPE npc;
if (!gcSelected) {
return;
}
else if (gcSelected == 1) {
InvalidateRect(gfDlgSelected ? gnpcSel->hwnd : gnpcSel->hwndDrag,
NULL, TRUE);
}
else {
for (npc = npcHead; npc; npc = npc->npcNext) {
if (npc->fSelected)
InvalidateRect(npc->hwndDrag, NULL, TRUE);
}
}
}
/************************************************************************
* SetAnchorToFirstSel
*
* This function makes the current selection (the anchor) be the
* first selected control. It is used after making a group selection,
* and ensures that the control that ends up being the anchor is
* consistently the first one in Z-order.
*
* Arguments:
* BOOL fDontUpdate = TRUE if the selection should NOT be redrawn.
*
************************************************************************/
VOID SetAnchorToFirstSel(
BOOL fDontUpdate)
{
NPCTYPE npc;
if (gcSelected) {
for (npc = npcHead; npc; npc = npc->npcNext) {
if (npc->fSelected) {
SelectControl2(npc, fDontUpdate);
break;
}
}
}
}
/************************************************************************
* SelectNext
*
* This selects the next control in the dialog box. The enumeration
* includes the dialog box itself, and wraps around.
*
************************************************************************/
VOID SelectNext(VOID)
{
NPCTYPE npcSelect;
/*
* Disable the tabbing functions if there is no dialog
* or if the dialog is already selected and there are
* no controls (the tabs would be a noop in this case).
*/
if (!gfEditingDlg || (gfDlgSelected && !npcHead))
return;
/*
* Is nothing selected?
*/
if (!gnpcSel) {
/*
* Select the first control, unless there are none, in which
* case select the dialog.
*/
if (npcHead)
npcSelect = npcHead;
else
npcSelect = gcd.npc;
}
else {
/*
* Is the dialog selected?
*/
if (gfDlgSelected) {
/*
* Select the first control, unless there are none, in which
* case do nothing.
*/
if (npcHead)
npcSelect = npcHead;
else
npcSelect = NULL;
}
else {
/*
* Find the current control. If there is one after it,
* select it, otherwise wrap around to the dialog and
* select it.
*/
if (gnpcSel->npcNext)
npcSelect = gnpcSel->npcNext;
else
npcSelect = gcd.npc;
}
}
if (npcSelect)
SelectControl(npcSelect, FALSE);
}
/************************************************************************
* SelectPrevious
*
* This selects the previous control in the dialog box. The enumeration
* includes the dialog box itself, and wraps around.
*
************************************************************************/
VOID SelectPrevious(VOID)
{
NPCTYPE npc;
NPCTYPE npcSelect;
/*
* Disable the tabbing functions if there is no dialog
* or if the dialog is already selected and there are
* no controls (the tabs would be a noop in this case).
*/
if (!gfEditingDlg || (gfDlgSelected && !npcHead))
return;
/*
* Is nothing selected?
*/
if (!gnpcSel) {
/*
* Select the last control, unless there are none, in which
* case select the dialog.
*/
if (npcHead) {
npc = npcHead;
while (npc->npcNext)
npc = npc->npcNext;
npcSelect = npc;
}
else {
npcSelect = gcd.npc;
}
}
else {
/*
* Is the dialog selected?
*/
if (gfDlgSelected) {
/*
* Select the last control, unless there are none, in which
* case select nothing.
*/
if (npcHead) {
npc = npcHead;
while (npc->npcNext)
npc = npc->npcNext;
npcSelect = npc;
}
else {
npcSelect = NULL;
}
}
else {
/*
* If the first control is selected, select the dialog.
* Otherwise hunt for and select the previous control.
*/
if (npcHead == gnpcSel) {
npcSelect = gcd.npc;
}
else {
npc = npcHead;
while (npc->npcNext != gnpcSel)
npc = npc->npcNext;
npcSelect = npc;
}
}
}
if (npcSelect)
SelectControl(npcSelect, FALSE);
}
/************************************************************************
* UnSelectControl
*
* This unselects the specified control, hiding its drag window and handles.
*
* Arguments:
* NPCTYPE npc = The control to deselect.
*
************************************************************************/
VOID UnSelectControl(
NPCTYPE npc)
{
npc->fSelected = FALSE;
gcSelected--;
/*
* We don't have a current selection if there are no selected
* windows, or if the control we are unselecting was the current
* selection.
*/
if (!gcSelected || npc == gnpcSel)
gnpcSel = NULL;
if (npc->pwcd->iType == W_DIALOG) {
gfDlgSelected = FALSE;
InvalidateDlgHandles();
}
else {
ShowWindow(npc->hwndDrag, SW_HIDE);
}
/*
* Are there still some selected controls, and was the control
* we just unselected the current selection? If so, we need
* to set the current selection to something.
*/
if (gcSelected && !gnpcSel)
SetAnchorToFirstSel(FALSE);
}
/************************************************************************
* InvalidateDlgHandles
*
* This function invalidates the handles for the dialog. This is
* used as an optimization so that the entire dialog does not need
* to be invalidated just to force the handles to be drawn.
*
************************************************************************/
STATICFN VOID InvalidateDlgHandles(VOID)
{
RECT rc;
RECT rcClient;
RECT rcFrame;
POINT pt;
INT xOffset;
INT yOffset;
/*
* Redraw the dialog border.
*/
SetWindowPos(gcd.npc->hwnd, NULL, 0, 0, 0, 0,
SWP_DRAWFRAME | SWP_NOACTIVATE |
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER);
/*
* Get the frame and client rectangles.
*/
GetWindowRect(gcd.npc->hwnd, &rcFrame);
GetClientRect(gcd.npc->hwnd, &rcClient);
/*
* Determine the offset from the frame origin to the client
* origin.
*/
pt.x = pt.y = 0;
ClientToScreen(gcd.npc->hwnd, &pt);
xOffset = rcFrame.left - pt.x;
yOffset = rcFrame.top - pt.y;
/*
* Make the frame rectangle zero based.
*/
OffsetRect(&rcFrame, -rcFrame.left, -rcFrame.top);
rc.left = 0;
rc.top = 0;
rc.right = CHANDLESIZE;
rc.bottom = CHANDLESIZE;
OffsetRect(&rc, xOffset, yOffset);
InvalidateRect(gcd.npc->hwnd, &rc, TRUE);
rc.left = ((rcFrame.right + 1) / 2) - (CHANDLESIZE / 2);
rc.top = 0;
rc.right = rc.left + CHANDLESIZE;
rc.bottom = CHANDLESIZE;
OffsetRect(&rc, xOffset, yOffset);
InvalidateRect(gcd.npc->hwnd, &rc, TRUE);
rc.left = rcFrame.right - CHANDLESIZE;
rc.top = 0;
rc.right = rcFrame.right;
rc.bottom = CHANDLESIZE;
OffsetRect(&rc, xOffset, yOffset);
InvalidateRect(gcd.npc->hwnd, &rc, TRUE);
rc.left = rcFrame.right - CHANDLESIZE;
rc.top = ((rcFrame.bottom + 1) / 2) - (CHANDLESIZE / 2);
rc.right = rcFrame.right;
rc.bottom = rc.top + CHANDLESIZE;
OffsetRect(&rc, xOffset, yOffset);
InvalidateRect(gcd.npc->hwnd, &rc, TRUE);
rc.left = rcFrame.right - CHANDLESIZE;
rc.top = rcFrame.bottom - CHANDLESIZE;
rc.right = rcFrame.right;
rc.bottom = rcFrame.bottom;
OffsetRect(&rc, xOffset, yOffset);
InvalidateRect(gcd.npc->hwnd, &rc, TRUE);
rc.left = ((rcFrame.right + 1) / 2) - (CHANDLESIZE / 2);
rc.top = rcFrame.bottom - CHANDLESIZE;
rc.right = rc.left + CHANDLESIZE;
rc.bottom = rcFrame.bottom;
OffsetRect(&rc, xOffset, yOffset);
InvalidateRect(gcd.npc->hwnd, &rc, TRUE);
rc.left = 0;
rc.top = rcFrame.bottom - CHANDLESIZE;
rc.right = CHANDLESIZE;
rc.bottom = rcFrame.bottom;
OffsetRect(&rc, xOffset, yOffset);
InvalidateRect(gcd.npc->hwnd, &rc, TRUE);
rc.left = 0;
rc.top = ((rcFrame.bottom + 1) / 2) - (CHANDLESIZE / 2);
rc.right = CHANDLESIZE;
rc.bottom = rc.top + CHANDLESIZE;
OffsetRect(&rc, xOffset, yOffset);
InvalidateRect(gcd.npc->hwnd, &rc, TRUE);
}
/************************************************************************
* CalcSelectedRect
*
* This routine updates the gwrcSelected rectangle. This is used during
* dragging operations. It contains the minimum rectangle that spans
* all the selected controls. If there are no selected controls, the
* contents of this global are not defined. This routine must be called
* every time that a control is selected of unselected to keep this
* rectangle updated, or tracking will not work properly.
*
************************************************************************/
VOID CalcSelectedRect(VOID)
{
NPCTYPE npc;
INT nBottom;
INT nBottomLowest;
/*
* Nothing is selected. The rectangle values are considered
* undefined, so we can quit.
*/
if (!gcSelected)
return;
if (gcSelected == 1) {
/*
* Only one control is selected. Set the rectangle to its
* rectangle. Note that since the dialog cannot be selected
* along with other controls, this handles the case where the
* dialog is selected.
*/
grcSelected = gnpcSel->rc;
gnOverHang = GetOverHang(gnpcSel->pwcd->iType,
gnpcSel->rc.bottom - gnpcSel->rc.top);
}
else {
/*
* Seed the rectangle with impossible values.
*/
SetRect(&grcSelected, 32000, 32000, -32000, -32000);
nBottomLowest = 0;
/*
* Loop through all the controls, expanding the rectangle to
* fit around all the selected controls.
*/
for (npc = npcHead; npc; npc = npc->npcNext) {
if (npc->fSelected) {
if (npc->rc.left < grcSelected.left)
grcSelected.left = npc->rc.left;
if (npc->rc.right > grcSelected.right)
grcSelected.right = npc->rc.right;
if (npc->rc.top < grcSelected.top)
grcSelected.top = npc->rc.top;
nBottom = npc->rc.bottom - GetOverHang(npc->pwcd->iType,
npc->rc.bottom - npc->rc.top);
if (nBottom > nBottomLowest)
nBottomLowest = nBottom;
if (npc->rc.bottom > grcSelected.bottom)
grcSelected.bottom = npc->rc.bottom;
}
}
gnOverHang = grcSelected.bottom - nBottomLowest;
}
}
/************************************************************************
* CancelSelection
*
* This unselects all selected controls.
*
* Arguments:
* BOOL fUpdate - If TRUE, the status ribbon is updated.
*
************************************************************************/
VOID CancelSelection(
BOOL fUpdate)
{
if (gcSelected) {
while (gcSelected)
UnSelectControl(gnpcSel);
if (fUpdate) {
StatusUpdate();
StatusSetEnable();
}
}
}
/************************************************************************
* OutlineSelectBegin
*
* This function begins an Outline Selection operation. This will
* draw a tracking rectangle on the screen that can be used to enclose
* controls. When the selection is completed, all the enclosed controls
* will be selected.
*
* The x and y coordinates are relative to the dialog window, not it's
* client.
*
* Arguments:
* INT x - Starting X location (window coords).
* INT y - Starting Y location (window coords).
*
************************************************************************/
VOID OutlineSelectBegin(
INT x,
INT y)
{
/*
* Always be sure the focus is where we want it, not on something
* like the status ribbon or "Esc" won't work to cancel the tracking.
*/
SetFocus(ghwndMain);
/*
* Remember the starting point. It comes in coords relative to the
* window, and the DC we are getting is one for the dialog's client,
* so we have to map it from window coords to the client's coords.
*/
gptOutlineSelect.x = x;
gptOutlineSelect.y = y;
MapDlgClientPoint(&gptOutlineSelect, FALSE);
gState = STATE_SELECTING;
ghwndTrackOver = gcd.npc->hwnd;
ghDCTrack = GetDC(ghwndTrackOver);
SetROP2(ghDCTrack, R2_NOT);
/*
* Get the rectangle for the client area of the dialog. This is
* used to limit the tracking.
*/
GetClientRect(gcd.npc->hwnd, &grcOutlineSelectLimit);
OutlineSelectDraw(x, y);
/*
* The mouse messages from now on will go to the dialog window,
* so that the following routines can know that the mouse movement
* points are relative to that window.
*/
SetCapture(gcd.npc->hwnd);
SetCursor(hcurOutSel);
}
/************************************************************************
* OutlineSelectDraw
*
* This routine draws the outline selection rectangle. It is assumed
* that the window has been locked for update appropriately or this
* could leave garbage around. The outline selection rectangle is
* drawn from the starting point in gptOutlineSelect to the given
* x,y location.
*
* Arguments:
* INT x - Mouse X location (window coords relative to the dialog).
* INT y - Mouse Y location (window coords relative to the dialog).
*
************************************************************************/
VOID OutlineSelectDraw(
INT x,
INT y)
{
OutlineSelectHide();
OutlineSelectSetRect(x, y);
MyFrameRect(ghDCTrack, &grcOutlineSelect, DSTINVERT);
gfOutlineSelectShown = TRUE;
}
/************************************************************************
* OutlineSelectHide
*
* This routine hides the current outline selection rectangle.
*
************************************************************************/
STATICFN VOID OutlineSelectHide(VOID)
{
if (gfOutlineSelectShown) {
MyFrameRect(ghDCTrack, &grcOutlineSelect, DSTINVERT);
gfOutlineSelectShown = FALSE;
}
}
/************************************************************************
* OutlineSelectSetRect
*
* This function takes an x,y point and makes a rectangle that goes
* from this point to the starting outline selection point. The cases
* are handled where the new point has negative coordinates relative
* to the starting point, and the rectangle produced is limited to
* the rectangle in grcOutlineSelectLimit.
*
* The rectangle is placed into the global grcOutlineSelect. The
* global point gwptOutlineSelect is assumed to have previously been
* set to the starting point of the outline selection.
*
* The x and y coordinates are relative to the dialog window, not it's
* client.
*
* Arguments:
* INT x - Mouse X location (window coords relative to the dialog).
* INT y - Mouse Y location (window coords relative to the dialog).
*
************************************************************************/
STATICFN VOID OutlineSelectSetRect(
INT x,
INT y)
{
POINT pt;
/*
* The point is coming in relative to the upper left of the
* dialog window. It needs to be mapped to points relative
* to the client of the dialog window.
*/
pt.x = x;
pt.y = y;
MapDlgClientPoint(&pt, FALSE);
if (pt.x > gptOutlineSelect.x) {
grcOutlineSelect.left = gptOutlineSelect.x;
grcOutlineSelect.right = pt.x;
}
else {
grcOutlineSelect.left = pt.x;
grcOutlineSelect.right = gptOutlineSelect.x;
}
if (pt.y > gptOutlineSelect.y) {
grcOutlineSelect.top = gptOutlineSelect.y;
grcOutlineSelect.bottom = pt.y;
}
else {
grcOutlineSelect.top = pt.y;
grcOutlineSelect.bottom = gptOutlineSelect.y;
}
if (grcOutlineSelect.left < grcOutlineSelectLimit.left)
grcOutlineSelect.left = grcOutlineSelectLimit.left;
if (grcOutlineSelect.right > grcOutlineSelectLimit.right)
grcOutlineSelect.right = grcOutlineSelectLimit.right;
if (grcOutlineSelect.top < grcOutlineSelectLimit.top)
grcOutlineSelect.top = grcOutlineSelectLimit.top;
if (grcOutlineSelect.bottom > grcOutlineSelectLimit.bottom)
grcOutlineSelect.bottom = grcOutlineSelectLimit.bottom;
}
/************************************************************************
* OutlineSelectCancel
*
* This routine is used to cancel the display of the outline selection
* rectangle.
*
************************************************************************/
VOID OutlineSelectCancel(VOID)
{
OutlineSelectHide();
ReleaseDC(ghwndTrackOver, ghDCTrack);
gState = STATE_NORMAL;
ReleaseCapture();
SetCursor(hcurArrow);
}
/************************************************************************
* OutlineSelectEnd
*
* This function completes an outline selection operation. All the
* enclosed controls will be selected. If the Shift key is down,
* the enclosed controls will be added to the selection, otherwise the
* current selection will be cancelled first.
*
* The current selection will only be cancelled if there is
* at least one new control enclosed. This is so that simply clicking and
* releasing the mouse without enclosing any controls leaves the current
* selection alone.
*
* Arguments:
* INT x - Mouse X location (dialog client coords).
* INT y - Mouse Y location (dialog client coords).
*
************************************************************************/
VOID OutlineSelectEnd(
INT x,
INT y)
{
NPCTYPE npc;
BOOL fFirstOne = TRUE;
OutlineSelectCancel();
OutlineSelectSetRect(x, y);
/*
* If the mouse was not moved at all, consider this a request
* to select the dialog instead of an outline selection operation.
*/
if (grcOutlineSelect.left == grcOutlineSelect.right &&
grcOutlineSelect.top == grcOutlineSelect.bottom) {
SelectControl(gcd.npc, FALSE);
return;
}
/*
* Convert the selected rectangle to dialog units.
*/
WinToDURect(&grcOutlineSelect);
for (npc = npcHead; npc; npc = npc->npcNext) {
/*
* Do the rectangles intersect?
*/
if (npc->rc.left < grcOutlineSelect.right &&
grcOutlineSelect.left < npc->rc.right &&
npc->rc.bottom > grcOutlineSelect.top &&
grcOutlineSelect.bottom > npc->rc.top) {
if (fFirstOne) {
/*
* If the Shift key is not held down, cancel any outstanding
* selections.
*/
if (!(GetKeyState(VK_SHIFT) & 0x8000))
CancelSelection(FALSE);
fFirstOne = FALSE;
}
/*
* If the control is not selected, select it.
*/
if (!npc->fSelected)
SelectControl2(npc, TRUE);
}
}
/*
* Update some things if there was at least one control enclosed.
*/
if (!fFirstOne) {
SetAnchorToFirstSel(TRUE);
StatusUpdate();
StatusSetEnable();
RedrawSelection();
CalcSelectedRect();
}
}
/************************************************************************
* MyFrameRect
*
* This function draws a one-pixel width rectangle using the given
* raster operation.
*
* Arguments:
* HDC hDC - DC to use.
* PRECT prc - Rectangle to draw the frame around.
* DWORD dwRop - RasterOp to use (DSTINVERT, BLACKNESS, etc.).
*
************************************************************************/
VOID MyFrameRect(
HDC hDC,
PRECT prc,
DWORD dwRop)
{
INT x;
INT y;
POINT pt;
x = prc->right - (pt.x = prc->left);
y = prc->bottom - (pt.y = prc->top);
PatBlt(hDC, pt.x, pt.y, x, 1, dwRop);
pt.y = prc->bottom - 1;
PatBlt(hDC, pt.x, pt.y, x, 1, dwRop);
pt.y = prc->top;
PatBlt(hDC, pt.x, pt.y, 1, y, dwRop);
pt.x = prc->right - 1;
PatBlt(hDC, pt.x, pt.y, 1, y, dwRop);
}
/************************************************************************
* MoveControl
*
* This function moves the current control to the next grid boundary in
* the specified direction.
*
* Arguments:
* WPARAM vKey - Virtual key code that was pressed.
*
************************************************************************/
VOID MoveControl(
WPARAM vKey)
{
RECT rc;
INT dx;
INT dy;
/*
* Nothing is selected.
*/
if (!gcSelected)
return;
rc = grcSelected;
switch (vKey) {
case VK_UP:
dx = 0;
if (!(dy = -(rc.top % gcyGrid)))
dy = -gcyGrid;
break;
case VK_DOWN:
dx = 0;
dy = gcyGrid - (rc.top % gcyGrid);
break;
case VK_RIGHT:
dx = gcxGrid - (rc.left % gcxGrid);
dy = 0;
break;
case VK_LEFT:
if (!(dx = -(rc.left % gcxGrid)))
dx = -gcxGrid;
dy = 0;
break;
}
OffsetRect(&rc, dx, dy);
FitRectToBounds(&rc, gnOverHang, DRAG_CENTER, gfDlgSelected);
gHandleHit = DRAG_CENTER;
PositionControl(&rc);
}
/************************************************************************
* PositionControl
*
* This function positions and sizes the current control. Both the control
* window and its associated drag window are moved at the same time. The
* coordinates in the control, and the status window display are updated.
* The given rectangle is in dialog units, and it should have already been
* range limited and grid aligned as appropriate.
*
* Arguments:
* NPWRECT nprc - The rectangle to size/position the control with.
*
************************************************************************/
VOID PositionControl(
PRECT prc)
{
INT cx;
INT cy;
RECT rcT;
NPCTYPE npcT;
HANDLE hwpi;
if (gcSelected == 1) {
/*
* Did nothing change?
*/
if (EqualRect(prc, &gnpcSel->rc))
return;
/*
* Only a single control is selected. Move it.
*/
PositionControl2(gnpcSel, prc, NULL);
/*
* Is the dialog selected, being sized (not just moved), and
* does it have at least one control?
*/
if (gfDlgSelected && gHandleHit != DRAG_CENTER && npcHead) {
cx = prc->left - grcSelected.left;
cy = prc->top - grcSelected.top;
/*
* Did the top or left edge of the dialog move?
*/
if (cx || cy) {
/*
* Loop through all the controls. Move all of them by
* the dialog movement delta.
*/
hwpi = BeginDeferWindowPos(cWindows * 2);
for (npcT = npcHead; npcT; npcT = npcT->npcNext) {
SetRect(&rcT, npcT->rc.left - cx, npcT->rc.top - cy,
npcT->rc.right - cx, npcT->rc.bottom - cy);
hwpi = PositionControl2(npcT, &rcT, hwpi);
}
EndDeferWindowPos(hwpi);
}
}
}
else {
/*
* Did nothing change?
*/
if (EqualRect(prc, &grcSelected))
return;
/*
* There is a group of controls selected.
* Calculate how much the group rectangle was moved.
* It is assumed that a group of controls cannot be
* sized, only moved.
*/
cx = prc->left - grcSelected.left;
cy = prc->top - grcSelected.top;
/*
* Loop through all the controls. Move all the selected
* ones by the group delta.
*/
hwpi = BeginDeferWindowPos(gcSelected * 2);
for (npcT = npcHead; npcT; npcT = npcT->npcNext) {
if (npcT->fSelected) {
SetRect(&rcT,
npcT->rc.left + cx, npcT->rc.top + cy,
npcT->rc.right + cx, npcT->rc.bottom + cy);
GridizeRect(&rcT,
GRIDIZE_LEFT | GRIDIZE_TOP | GRIDIZE_SAMESIZE);
FitRectToBounds(&rcT,
GetOverHang(npcT->pwcd->iType,
npcT->rc.bottom - npcT->rc.top),
DRAG_CENTER, gfDlgSelected);
hwpi = PositionControl2(npcT, &rcT, hwpi);
}
}
EndDeferWindowPos(hwpi);
}
CalcSelectedRect();
StatusSetCoords(&gnpcSel->rc);
gfResChged = gfDlgChanged = TRUE;
ShowFileStatus(FALSE);
}
/************************************************************************
* PositionControl2
*
* This function positions and sizes a single control. Both the control
* window and its associated drag window are moved at the same time. The
* coordinates in the control are updated.
*
* The given rectangle is in dialog units, and it should have already been
* range limited and grid aligned as appropriate.
*
* Arguments:
* NPCTYPE npc - The control to position.
* PRECT prc - The rectangle to size/position the control with.
* HANDLE hwpi - Handle that has been returned from a BeginDeferWindowPos
* call. If this parameter is not NULL, the calls to
* position the control and drag window will use this
* handle.
*
* Returns:
*
* The return will be the hwpi handle that is currently being used.
* The variable that the caller is using must be updated with the
* return value each call, because each call to DeferWindowPos can
* possibly change this value.
*
************************************************************************/
STATICFN HANDLE PositionControl2(
NPCTYPE npc,
PRECT prc,
HANDLE hwpi)
{
RECT rc;
RECT rcDrag;
HANDLE hwpi2;
/*
* Make a local copy of the rectangle.
*/
rc = *prc;
/*
* Start calculating the new position. Begin by mapping the dialog
* points to window coordinates.
*/
DUToWinRect(&rc);
if (npc->pwcd->iType == W_DIALOG) {
InvalidateDlgHandles();
AdjustWindowRectEx(&rc, npc->flStyle, FALSE,
(npc->flStyle & DS_MODALFRAME) ?
npc->flExtStyle | WS_EX_DLGMODALFRAME : npc->flExtStyle);
ClientToScreenRect(ghwndSubClient, &rc);
MoveWindow(npc->hwnd, rc.left, rc.top,
rc.right - rc.left, rc.bottom - rc.top, TRUE);
/*
* Update the control structure with the new rectangle.
*/
npc->rc = *prc;
/*
* Since this was the dialog that was just positioned, we need to
* recalculate and save the size of its "client" area.
*/
SaveDlgClientRect(npc->hwnd);
}
else {
rcDrag = rc;
InflateRect(&rcDrag, CHANDLESIZE / 2, CHANDLESIZE / 2);
if (hwpi)
hwpi2 = hwpi;
else
hwpi2 = BeginDeferWindowPos(2);
hwpi2 = DeferWindowPos(hwpi2, npc->hwndDrag, NULL,
rcDrag.left, rcDrag.top,
rcDrag.right - rcDrag.left, rcDrag.bottom - rcDrag.top,
SWP_NOACTIVATE | SWP_NOZORDER);
hwpi2 = DeferWindowPos(hwpi2, npc->hwnd, NULL, rc.left, rc.top,
rc.right - rc.left, rc.bottom - rc.top,
SWP_NOACTIVATE | SWP_NOZORDER);
if (!hwpi)
EndDeferWindowPos(hwpi2);
/*
* Update the control structure with the new rectangle.
*/
npc->rc = *prc;
InvalidateRect(npc->hwndDrag, NULL, TRUE);
}
return hwpi2;
}
/************************************************************************
* RepositionDialog
*
* This routine forces the dialog to be moved to the location that
* is stored in it's npc rectangle. This is necessary after the
* main application has been moved, because the dialog is relative
* to the app's window and does not automatically move with it.
*
************************************************************************/
VOID RepositionDialog(VOID)
{
PositionControl2(gcd.npc, &gcd.npc->rc, NULL);
}
/************************************************************************
* SaveDlgClientRect
*
* This routine saves away a global that will contain the offset, in window
* coordinates, of the origin of the "client" area for the current dialog.
*
* Arguments:
* HWND hwndDlg - The dialog window.
*
************************************************************************/
VOID SaveDlgClientRect(
HWND hwndDlg)
{
RECT rcFrame;
POINT pt;
GetWindowRect(hwndDlg, &rcFrame);
GetClientRect(hwndDlg, &grcDlgClient);
pt.x = pt.y = 0;
ClientToScreen(hwndDlg, &pt);
OffsetRect(&grcDlgClient, pt.x - rcFrame.left, pt.y - rcFrame.top);
}
/************************************************************************
* SizeToText
*
* This function will size all the selected controls to fit their text.
* This is to support the "Size to text" command of the "Edit" menu.
*
* Globals are updated appropriately if anything actually had to change.
*
************************************************************************/
VOID SizeToText(VOID)
{
NPCTYPE npc;
BOOL fSized = FALSE;
/*
* Loop through all the controls.
*/
for (npc = npcHead; npc; npc = npc->npcNext) {
/*
* Is the control selected, and can it be sized to its text?
*/
if (npc->fSelected && npc->pwcd->fSizeToText)
fSized |= SizeCtrlToText(npc);
}
/*
* Was anything modified?
*/
if (fSized) {
CalcSelectedRect();
StatusSetCoords(&gnpcSel->rc);
gfResChged = gfDlgChanged = TRUE;
ShowFileStatus(FALSE);
}
}
/************************************************************************
* SizeCtrlToText
*
* This function sizes a single control so that it just fits its text.
* This does not make sense for all controls (see the fSizeToText flag
* in the awcd array), and there are different rules for the different
* types of controls.
*
* Arguments:
* NPCTYPE npc - The control to size.
*
* Returns:
*
* The return value is TRUE if the control was modified (sized) or
* FALSE if it was not.
*
************************************************************************/
STATICFN BOOL SizeCtrlToText(
NPCTYPE npc)
{
RECT rc;
INT x;
INT cxLowern;
switch (npc->pwcd->iType) {
case W_CHECKBOX:
/*
* Take the width of the text, plus some pixels for the square.
*/
x = QueryTextExtent(npc->hwnd, npc->text, TRUE) + 20;
x = MulDiv(x, 4, gcd.cxChar) + 1;
break;
case W_PUSHBUTTON:
/*
* The UITF definition of the size of a pushbutton says
* that the left and right margins should be approximately
* the width of a lowercase "n". In any event, the width
* cannot be less than the default size.
*/
cxLowern = QueryTextExtent(npc->hwnd, L"n", FALSE);
x = QueryTextExtent(npc->hwnd, npc->text, FALSE) + (2 * cxLowern);
x = MulDiv(x, 4, gcd.cxChar);
if (x < awcd[W_PUSHBUTTON].cxDefault)
x = awcd[W_PUSHBUTTON].cxDefault;
break;
case W_RADIOBUTTON:
/*
* Take the width of the text, plus some for the circle.
*/
x = QueryTextExtent(npc->hwnd, npc->text, TRUE) + 20;
x = MulDiv(x, 4, gcd.cxChar) + 1;
break;
case W_TEXT:
/*
* Take the width of the text.
*/
x = QueryTextExtent(npc->hwnd, npc->text, TRUE);
x = MulDiv(x, 4, gcd.cxChar) + 1;
break;
case W_CUSTOM:
/*
* Call out to the custom control and let it decide
* how wide the text should be.
*/
x = CallCustomSizeToText(npc);
break;
default:
x = -1;
break;
}
/*
* Does it need to be sized?
*/
if (x != -1 && x != npc->rc.right - npc->rc.left) {
/*
* Now that we know the size we want the control, position
* it to change that size. Note that we do NOT gridize
* the left edge here. The user probably just wants the
* right edge of the control to be adjusted to fit the new
* text, and probably does not want the left edge shifting
* on them.
*/
rc = npc->rc;
rc.right = rc.left + x;
FitRectToBounds(&rc,
GetOverHang(npc->pwcd->iType, npc->rc.bottom - npc->rc.top),
DRAG_CENTER, gfDlgSelected);
PositionControl2(npc, &rc, NULL);
return TRUE;
}
return FALSE;
}
/************************************************************************
* QueryTextExtent
*
* This function takes a window handle and text, and will return the
* number of pixels that the specified text is wide in that window.
* It is used to determine how wide a control needs to be to display
* its text.
*
* The font set into the current dialog is taken into consideration
* when calculating the size.
*
* Arguments:
* HWND hwnd - The control window handle.
* LPTSTR pszText - The text of the control.
* BOOL fWordBreak - TRUE if this text will be drawn with DT_WORDBREAK.
*
* Returns:
*
* The number of pixels wide the selected text is.
*
************************************************************************/
STATICFN INT QueryTextExtent(
HWND hwnd,
LPTSTR pszText,
BOOL fWordBreak)
{
HDC hDC;
INT iHeight;
RECT rc;
INT nLen;
HFONT hfontOld;
if (!pszText || *pszText == CHAR_NULL)
return 0;
hDC = GetDC(hwnd);
/*
* If there is a valid font, select it into the DC. Note that
* we look at gcd.hFont instead of gcd.fFontSpecified, because
* it is possible to specify a font for the dialog but not have
* been able to create it.
*/
if (gcd.hFont)
hfontOld = SelectObject(hDC, gcd.hFont);
/*
* First, calculate the length of the text.
*/
rc.left = rc.top = 0;
rc.right = 10000;
rc.bottom = 10000;
nLen = lstrlen(pszText);
DrawText(hDC, pszText, nLen, &rc,
DT_CALCRECT | DT_NOCLIP | DT_EXPANDTABS);
/*
* First save the height of the line. This works because the
* DrawText call above with DT_CALCRECT will always draw on
* a single line. Then we move the upwards the rectangle to draw
* in a large amount, so that it is outside the dimensions of
* the control. Finally, we do a real draw of the text,
* increasing the width a little each time until we are able
* to draw entirely on one line. This is inefficient, but it does
* ensure that the returned width will be enough to actually
* draw the string.
*/
if (fWordBreak) {
iHeight = rc.bottom - rc.top;
rc.top -= 10000;
rc.bottom -= 10000;
while (TRUE) {
/*
* Determine if we have enough width to draw on a single
* line yet.
*/
if (DrawText(hDC, pszText, nLen, &rc,
DT_NOCLIP | DT_EXPANDTABS | DT_WORDBREAK) == iHeight)
break;
/*
* Nope, push the right margin out and try again...
*/
rc.right++;
}
}
if (gcd.hFont)
SelectObject(hDC, hfontOld);
ReleaseDC(hwnd, hDC);
return rc.right - rc.left;
}
/************************************************************************
* AlignControls
*
* This function will align all the selected controls. The point to
* align to is always taken from the currently selected control.
*
* In all cases, the resulting desired position of the control will be
* gridized and limited to the dialogs "client" area. The size of the
* controls will not be changed.
*
* Arguments:
* INT cmd - The alignment menu command.
*
* The following are valid values for cmd:
*
* MENU_ALIGNLEFT - Align to the left edge.
* MENU_ALIGNVERT - Align to the center vertically.
* MENU_ALIGNRIGHT - Align to the right edge.
* MENU_ALIGNTOP - Align to the top edge.
* MENU_ALIGNHORZ - Align to the center horizontally.
* MENU_ALIGNBOTTOM - Align to the bottom edge.
*
************************************************************************/
VOID AlignControls(
INT cmd)
{
register INT sDelta;
NPCTYPE npc;
RECT rc;
BOOL fMove;
BOOL fModified = FALSE;
/*
* Loop through all the controls. Align all the selected ones.
*/
for (npc = npcHead; npc; npc = npc->npcNext) {
if (npc->fSelected && npc != gnpcSel) {
rc = npc->rc;
fMove = FALSE;
switch (cmd) {
case MENU_ALIGNLEFT:
if (sDelta = gnpcSel->rc.left - rc.left) {
fMove = TRUE;
rc.left += sDelta;
rc.right += sDelta;
}
break;
case MENU_ALIGNVERT:
if (sDelta =
(((gnpcSel->rc.right - gnpcSel->rc.left) / 2)
+ gnpcSel->rc.left) -
(((rc.right - rc.left) / 2) +
rc.left)) {
fMove = TRUE;
rc.left += sDelta;
rc.right += sDelta;
}
break;
case MENU_ALIGNRIGHT:
if (sDelta = gnpcSel->rc.right - rc.right) {
fMove = TRUE;
rc.left += sDelta;
rc.right += sDelta;
}
break;
case MENU_ALIGNTOP:
if (sDelta = gnpcSel->rc.top - rc.top) {
fMove = TRUE;
rc.top += sDelta;
rc.bottom += sDelta;
}
break;
case MENU_ALIGNHORZ:
if (sDelta =
(((gnpcSel->rc.bottom - gnpcSel->rc.top) / 2)
+ gnpcSel->rc.top) -
(((rc.bottom - rc.top) / 2) +
rc.top)) {
fMove = TRUE;
rc.top += sDelta;
rc.bottom += sDelta;
}
break;
case MENU_ALIGNBOTTOM:
if (sDelta = gnpcSel->rc.bottom - rc.bottom) {
fMove = TRUE;
rc.top += sDelta;
rc.bottom += sDelta;
}
break;
}
if (fMove) {
GridizeRect(&rc,
GRIDIZE_LEFT | GRIDIZE_TOP | GRIDIZE_SAMESIZE);
FitRectToBounds(&rc, GetOverHang(npc->pwcd->iType,
npc->rc.bottom - npc->rc.top),
DRAG_CENTER, FALSE);
if (!EqualRect(&rc, &npc->rc)) {
PositionControl2(npc, &rc, NULL);
fModified = TRUE;
}
}
}
}
if (fModified) {
RedrawSelection();
CalcSelectedRect();
gfResChged = gfDlgChanged = TRUE;
ShowFileStatus(FALSE);
StatusUpdate();
}
}
/************************************************************************
* ArrangeSpacing
*
* This function will evenly space all the selected controls. The
* currently selected control is not moved (unless it has to be gridized)
* and any previous controls in Z order will be evenly spaced to the
* left or above the anchor, and all controls following in Z order will
* be evenly spaced below or to the right of the anchor.
*
* The spacing values used are gxSpace and gySpace.
*
* In all cases, the resulting desired position of the control will be
* gridized and limited to the dialogs "client" area. The size of the
* controls is not changed.
*
* Arguments:
* INT cmd - The Arrange/Even spacing menu command.
*
* The following are valid values for cmd:
*
* MENU_SPACEHORZ - Space the controls left and right.
* MENU_SPACEVERT - Space all the controls up and down.
*
************************************************************************/
VOID ArrangeSpacing(
INT cmd)
{
NPCTYPE npc;
RECT rc;
BOOL fModified = FALSE;
INT x;
INT y;
INT cPreceding;
INT xTotalWidth;
INT yTotalWidth;
cPreceding = 0;
xTotalWidth = 0;
yTotalWidth = 0;
for (npc = npcHead; npc; npc = npc->npcNext) {
if (npc->fSelected) {
if (npc == gnpcSel)
break;
cPreceding++;
xTotalWidth += npc->rc.right - npc->rc.left;
yTotalWidth += npc->rc.bottom - npc->rc.top;
}
}
x = gnpcSel->rc.left;
y = gnpcSel->rc.top;
if (cPreceding) {
x -= xTotalWidth + (gxSpace * cPreceding);
y -= yTotalWidth + (gySpace * cPreceding);
}
/*
* Loop through all the controls. Space all the selected ones.
*/
for (npc = npcHead; npc; npc = npc->npcNext) {
if (npc->fSelected) {
rc = npc->rc;
switch (cmd) {
case MENU_SPACEVERT:
rc.top = y;
rc.bottom = y + (npc->rc.bottom - npc->rc.top);
y = rc.bottom + gySpace;
break;
case MENU_SPACEHORZ:
rc.left = x;
rc.right = x + (npc->rc.right - npc->rc.left);
x = rc.right + gxSpace;
break;
}
GridizeRect(&rc, GRIDIZE_LEFT | GRIDIZE_TOP | GRIDIZE_SAMESIZE);
FitRectToBounds(&rc, GetOverHang(npc->pwcd->iType,
npc->rc.bottom - npc->rc.top),
DRAG_CENTER, FALSE);
if (!EqualRect(&rc, &npc->rc)) {
PositionControl2(npc, &rc, NULL);
fModified = TRUE;
}
}
}
if (fModified) {
RedrawSelection();
CalcSelectedRect();
gfResChged = gfDlgChanged = TRUE;
ShowFileStatus(FALSE);
StatusUpdate();
}
}
/************************************************************************
* ArrangeSize
*
* This function will evenly size all the selected controls. The
* currently selected control determines the size that the other
* controls will be set to in the given dimension.
*
* In all cases, the resulting size of the control will be gridized and
* limited to the dialogs "client" area.
*
* Arguments:
* INT cmd - The Arrange/Same size menu command.
*
* The following are valid values for cmd:
*
* MENU_ARRSIZEWIDTH - Size the widths of the controls.
* MENU_ARRSIZEHEIGHT - Size the heights of the controls.
*
************************************************************************/
VOID ArrangeSize(
INT cmd)
{
NPCTYPE npc;
RECT rc;
BOOL fModified = FALSE;
INT cx;
INT cy;
cx = gnpcSel->rc.right - gnpcSel->rc.left;
cy = gnpcSel->rc.bottom - gnpcSel->rc.top;
/*
* Loop through all the controls, operating on the selected ones.
*/
for (npc = npcHead; npc; npc = npc->npcNext) {
/*
* Is the control selected, and is it sizeable?
*/
if (npc->fSelected && npc->pwcd->fSizeable) {
rc = npc->rc;
switch (cmd) {
case MENU_ARRSIZEWIDTH:
rc.right = npc->rc.left + cx;
break;
case MENU_ARRSIZEHEIGHT:
rc.top = npc->rc.bottom - cy;
break;
}
GridizeRect(&rc, GRIDIZE_LEFT | GRIDIZE_TOP |
GRIDIZE_RIGHT | GRIDIZE_BOTTOM);
FitRectToBounds(&rc, GetOverHang(npc->pwcd->iType,
npc->rc.bottom - npc->rc.top),
DRAG_CENTER, FALSE);
if (!EqualRect(&rc, &npc->rc)) {
PositionControl2(npc, &rc, NULL);
fModified = TRUE;
}
}
}
if (fModified) {
RedrawSelection();
CalcSelectedRect();
gfResChged = gfDlgChanged = TRUE;
ShowFileStatus(FALSE);
StatusUpdate();
}
}
/************************************************************************
* ArrangePushButtons
*
* This function will arrange push buttons along either the bottom of
* the dialog or along the right side of the dialog. It will operate
* on the selected buttons (which button is currently selected does not
* matter) but this function can also be used if buttons are not selected
* in a couple of special cases. If either the dialog or nothing is
* selected, ALL the push buttons in the dialog will be arranged.
*
* The margin values used are gxMargin and gyMargin, and the spacing
* between buttons is gxMinPushSpace, gxMaxPushSpace and gyPushSpace.
*
* In all cases, the resulting desired position of the buttons will be
* gridized and limited to the dialogs "client" area. The size of the
* push buttons is not changed.
*
* Arguments:
* INT cmd - The Arrange/Push buttons menu command.
*
* The following are valid values for cmd:
*
* MENU_ARRPUSHBOTTOM - Arrange push buttons along the bottom.
* MENU_ARRPUSHRIGHT - Arrange push buttons along the right side.
*
************************************************************************/
VOID ArrangePushButtons(
INT cmd)
{
NPCTYPE npc;
RECT rc;
BOOL fModified = FALSE;
INT x; // Note: These values must be signed.
INT y;
INT cxDlg;
INT cButtons;
INT xTotal;
INT xInterSpace;
switch (cmd) {
case MENU_ARRPUSHBOTTOM:
cxDlg = gcd.npc->rc.right - gcd.npc->rc.left;
y = (gcd.npc->rc.bottom - gcd.npc->rc.top) - gyMargin;
for (cButtons = 0, xTotal = 0, npc = npcHead; npc;
npc = npc->npcNext) {
if (npc->pwcd->iType == W_PUSHBUTTON &&
(!gcSelected || gfDlgSelected || npc->fSelected)) {
cButtons++;
xTotal += npc->rc.right - npc->rc.left;
}
}
if (cButtons == 1) {
x = (cxDlg - xTotal) / 2;
xInterSpace = 0;
}
else {
xInterSpace = (cxDlg - xTotal) / (cButtons + 1);
if (xInterSpace > gxMaxPushSpace)
xInterSpace = gxMaxPushSpace;
else if (xInterSpace < gxMinPushSpace)
xInterSpace = gxMinPushSpace;
x = (cxDlg - ((cButtons - 1) * xInterSpace) - xTotal) / 2;
if (x < 0)
x = 0;
if (x < gxMargin && xInterSpace > gxMinPushSpace) {
xInterSpace = (cxDlg - xTotal - (2 * gxMargin))
/ (cButtons - 1);
if (xInterSpace < gxMinPushSpace)
xInterSpace = gxMinPushSpace;
x = (cxDlg - ((cButtons - 1) * xInterSpace) - xTotal)
/ 2;
if (x < 0)
x = 0;
}
}
break;
case MENU_ARRPUSHRIGHT:
x = (gcd.npc->rc.right - gcd.npc->rc.left) - gxMargin;
y = gyMargin;
break;
}
/*
* Loop through all the controls.
*/
for (npc = npcHead; npc; npc = npc->npcNext) {
/*
* We will arrange this control only if it is a pushbutton,
* and only if one of the following is true: there are no
* controls selected, or the dialog itself is selected, or
* there are some controls selected and this pushbutton is
* one of them.
*/
if (npc->pwcd->iType == W_PUSHBUTTON &&
(!gcSelected || gfDlgSelected || npc->fSelected)) {
rc = npc->rc;
switch (cmd) {
case MENU_ARRPUSHBOTTOM:
rc.left = x;
rc.top = y - (npc->rc.bottom - npc->rc.top);
rc.bottom = y;
rc.right = rc.left + (npc->rc.right - npc->rc.left);
x = rc.right + xInterSpace;
break;
case MENU_ARRPUSHRIGHT:
rc.left = x - (npc->rc.right - npc->rc.left);
rc.bottom = y + (npc->rc.bottom - npc->rc.top);
rc.right = x;
rc.top = y;
y = rc.bottom + gyPushSpace;
break;
}
GridizeRect(&rc, GRIDIZE_LEFT | GRIDIZE_TOP | GRIDIZE_SAMESIZE);
FitRectToBounds(&rc, GetOverHang(npc->pwcd->iType,
npc->rc.bottom - npc->rc.top),
DRAG_CENTER, FALSE);
if (!EqualRect(&rc, &npc->rc)) {
PositionControl2(npc, &rc, NULL);
fModified = TRUE;
}
}
}
if (fModified) {
if (gfDlgSelected || !gcSelected)
InvalidateRect(gcd.npc->hwnd, NULL, TRUE);
CalcSelectedRect();
gfResChged = gfDlgChanged = TRUE;
ShowFileStatus(FALSE);
StatusUpdate();
}
}