/****************************************************************************/ /* */ /* Microsoft Confidential */ /* */ /* Copyright (c) Microsoft Corp. 1987, 1990 */ /* All Rights Reserved */ /* */ /****************************************************************************/ /****************************** Module Header ******************************* * Module Name: select.c * * Contains routines for selecting and positioning controls. * * History: * ****************************************************************************/ #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. * * The return will be FALSE if the control was just unselected. * * Arguments: * NPCTYPE npc = The control to select. * BOOL fCheckShift = TRUE if the state of the shift key should be * taken into consideration. * * History: * ************************************************************************/ 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. * * If fDontUpdate is TRUE, the selection will not be redrawn, and it * is required that CalcSelectedRect be called before doing any drag * operations!! * * 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!). * * History: * ************************************************************************/ 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. * * History: * ************************************************************************/ 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. * * History: * ************************************************************************/ 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. * * History: * ************************************************************************/ 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 * anyways). */ 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. * * History: * ************************************************************************/ 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 * anyways). */ 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. * * History: * ************************************************************************/ 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. * * History: * ************************************************************************/ 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. * * History: * ************************************************************************/ 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. * * History: * ************************************************************************/ 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). * * History: * ************************************************************************/ 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). * * History: * ************************************************************************/ 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. * * History: * ************************************************************************/ 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). * * History: * ************************************************************************/ 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. * * History: * ************************************************************************/ 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. * * Actually, 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). * * History: * ************************************************************************/ 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 or if the dialog is * selected, then cancel any outstanding selections. */ if (!(GetKeyState(VK_SHIFT) & 0x8000) || gcd.npc->fSelected == TRUE) 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.). * * History: * ************************************************************************/ 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. * * History: * ************************************************************************/ 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. * * History: * ************************************************************************/ 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. * * 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. * * 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. * * History: * ************************************************************************/ 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. * * History: * ************************************************************************/ 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. * * History: * ************************************************************************/ 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. * * History: * ************************************************************************/ 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. * * The return value is TRUE if the control was modified (sized) or * FALSE if it was not. * * Arguments: * NPCTYPE npc - The control to size. * * History: * ************************************************************************/ 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. * * History: * ************************************************************************/ 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); /* * Unfortunately, there is a bug in Win 3.0 that causes text * to be word-broken before it should. Because of this, we * 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 a hack, 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. * * 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. * * 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. * * History: * ************************************************************************/ 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 following are valid values for cmd: * * MENU_SPACEHORZ - Space the controls left and right. * MENU_SPACEVERT - Space all the controls up and down. * * 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. * * History: * ************************************************************************/ 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. * * The following are valid values for cmd: * * MENU_ARRSIZEWIDTH - Size the widths of the controls. * MENU_ARRSIZEHEIGHT - Size the heights of the controls. * * 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. * * History: * ************************************************************************/ 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 following are valid values for cmd: * * MENU_ARRPUSHBOTTOM - Arrange push buttons along the bottom. * MENU_ARRPUSHRIGHT - Arrange push buttons along the right side. * * 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. * * History: * ************************************************************************/ 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(); } }