/******************************************************************************\
* 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: drag.c
*
* Contains routines for dragging and sizing controls.
*
* Functions:
* ShowTrackRect()
* HideTrackRect()
* FitRectToBounds()
* GetOverHang()
* GridizeRect()
* SizeDragToControl()
* DragWndProc()
* DrawHandles()
* HandleHitTest()
* CtrlButtonDown()
* DragNewBegin()
* CtrlMouseMove()
* PreDragTimeout()
* DragCancel()
* CtrlButtonUp()
* DragEnd()
* CalcCursorOffset()
* InitTracking()
* DrawTrackRect()
* CancelTracking()
* AtOrAbove()
* AtOrBelow()
* PaintUnderDrag()
* MouseToDragRect()
* MouseToDU()
* DragBegin()
* CtrlHitTest()
* DragBegin2()
*
* Comments:
*
****************************************************************************/
#include "dlgedit.h"
#include "dlgfuncs.h"
#include "dlgextrn.h"
#include <stdlib.h>
STATICFN VOID CalcCursorOffset(POINT *ppt);
STATICFN VOID InitTracking(VOID);
STATICFN VOID DrawTrackRect(PRECT prc, BOOL fDialog, BOOL fDraw);
STATICFN VOID CancelTracking(VOID);
STATICFN INT AtOrAbove(INT nStart, INT nGrid);
STATICFN INT AtOrBelow(INT nStart, INT nGrid);
STATICFN VOID PaintUnderDrag(HWND hwndDrag);
STATICFN VOID MouseToDragRect(INT x, INT y, PRECT prc);
STATICFN VOID MouseToDU(PPOINT ppt);
STATICFN VOID DragBegin(HWND hwnd, INT x, INT y, BOOL fHandleWindow);
STATICFN HWND CtrlHitTest(HWND hwnd, PPOINT ppt);
STATICFN VOID DragBegin2(PPOINT ppt);
/*
* This contains the initial location of the mouse when going into
* pre-drag mode. If the mouse pointer is moved too far away from
* this point, we will start the drag operation, even if the pre-drag
* timer has not elapsed yet.
*/
static POINT gptPreDragStart;
/************************************************************************
* CalcCursorOffset
*
* This routine updates the gptCursorOffset point. This is used during
* dragging operations. It contains the offset from the mouse pointer
* at the time a dragging operation is begun and the upper left corner
* of the dragging rectangle. This value is needed for determining
* where mouse events are occuring in relation to where the drag was
* initially begun from.
*
* Arguments:
* POINT - offset where the mouse pointer began drag.
*
************************************************************************/
STATICFN VOID CalcCursorOffset(
POINT *ppt)
{
RECT rc;
POINT pt;
if (gfDlgSelected) {
gptCursorOffset = *ppt;
}
else {
rc = grcSelected;
pt = *ppt;
DUToWinRect(&rc);
ClientToScreen(gnpcSel->hwnd, &pt);
ScreenToClient(gcd.npc->hwnd, &pt);
gptCursorOffset.x = pt.x - rc.left;
gptCursorOffset.y = pt.y - rc.top;
}
}
/************************************************************************
* InitTracking
*
* This function initializes a tracking operation. The pointer is
* changed to be the system "move" pointer if we are moving the
* control (not sizing it).
*
************************************************************************/
STATICFN VOID InitTracking(VOID)
{
if (gfDlgSelected)
ghDCTrack = CreateDC(L"DISPLAY", NULL, NULL, NULL);
else
ghDCTrack = GetDC(gcd.npc->hwnd);
SetROP2(ghDCTrack, R2_NOT);
}
/************************************************************************
* DrawTrackRect
*
* This routine draws the drag rectangle. It is assumed that the window
* has been locked for update appropriately or this could leave garbage
* around. The rectangle given is in dialog units, and is converted
* to window coordinates using different rules based on the value of
* fDialog. After this routine has been called to set the rectangle,
* the HideTrackRect and ShowTrackRect functions can be called to
* temporarily hide the track rectangle, but this routine must be called
* again every time that the tracking rectangle is to be changed.
*
* Arguments:
* PRECT prc - Drag rectangle to draw (in dialog units).
* BOOL fDialog - TRUE if the control being dragged is the dialog.
* BOOL fDraw - If TRUE, the rectangle will be drawn. Having this
* FALSE is useful to just initialize the state globals,
* but defer the drawing of the rectangle until the mouse
* is moved from its starting point.
*
************************************************************************/
STATICFN VOID DrawTrackRect(
PRECT prc,
BOOL fDialog,
BOOL fDraw)
{
HideTrackRect();
grcTrackWin = grcTrackDU = *prc;
DUToWinRect(&grcTrackWin);
if (fDialog) {
AdjustWindowRectEx(&grcTrackWin, gcd.npc->flStyle, FALSE,
(gcd.npc->flStyle & DS_MODALFRAME) ?
gcd.npc->flExtStyle | WS_EX_DLGMODALFRAME :
gcd.npc->flExtStyle);
ClientToScreenRect(ghwndSubClient, &grcTrackWin);
}
if (fDraw)
ShowTrackRect();
}
/************************************************************************
* ShowTrackRect
*
* This routine shows the current tracking rectangle.
*
************************************************************************/
VOID ShowTrackRect(VOID)
{
if (!gfTrackRectShown) {
MyFrameRect(ghDCTrack, &grcTrackWin, DSTINVERT);
gfTrackRectShown = TRUE;
}
}
/************************************************************************
* HideTrackRect
*
* This routine hides the current tracking rectangle.
*
************************************************************************/
VOID HideTrackRect(VOID)
{
if (gfTrackRectShown) {
MyFrameRect(ghDCTrack, &grcTrackWin, DSTINVERT);
gfTrackRectShown = FALSE;
}
}
/************************************************************************
* CancelTracking
*
* This routine is used to cancel the display of the tracking rectangle.
* It is basically the opposite of InitTracking.
*
************************************************************************/
STATICFN VOID CancelTracking(VOID)
{
if (gfTrackRectShown) {
HideTrackRect();
if (gfDlgSelected)
DeleteDC(ghDCTrack);
else
ReleaseDC(gcd.npc->hwnd, ghDCTrack);
}
}
/************************************************************************
* FitRectToBounds
*
* This routine fits the given rectangle to the appropriate boundary.
* If fDialog is FALSE, the rectangle is a control and it must fall
* entirely within the area of the current dialog being edited. If the
* rectangle is adjusted to fit, the moved edge(s) will be aligned on
* a grid boundary. The wHandleHit parameter is used to tell this routine
* what edges are allowed to move, in other words, what edges are
* "anchored" down and what edges are being tracked.
*
* Arguments:
* PRECT prc - Rectangle to be adjusted to the allowed size.
* INT nOverHang - How much the control can hang below the dialog.
* This is primarily for the combobox listboxes.
* INT HandleHit - One of the DRAG_* constants.
* BOOL fDialog - TRUE if the rectangle is for a dialog.
*
************************************************************************/
VOID FitRectToBounds(
PRECT prc,
INT nOverHang,
INT HandleHit,
BOOL fDialog)
{
INT cxDlg;
INT cyDlg;
INT dx;
INT dy;
/*
* Are we just moving the control (not sizing)?
*/
if (HandleHit == DRAG_CENTER) {
/*
* We only do range checking if it is a control (not on the dialog).
*/
if (!fDialog) {
dx = prc->right - prc->left;
dy = prc->bottom - prc->top;
cxDlg = gcd.npc->rc.right - gcd.npc->rc.left;
cyDlg = gcd.npc->rc.bottom - gcd.npc->rc.top + nOverHang;
if (prc->right > cxDlg) {
prc->left = AtOrBelow(cxDlg - dx, gcxGrid);
prc->right = prc->left + dx;
}
if (prc->left < 0) {
prc->left = 0;
prc->right = prc->left + dx;
}
if (prc->bottom > cyDlg) {
prc->top = AtOrBelow(cyDlg - dy, gcyGrid);
prc->bottom = prc->top + dy;
}
if (prc->top < 0) {
prc->top = 0;
prc->bottom = prc->top + dy;
}
}
return;
}
if (fDialog) {
/*
* When dealing with the dialog, we want to take into account
* the controls so that the dialog is never sized to hide a
* control. This routine assumes that grcMinDialog has already
* been set to enclose the controls. If the dialog has no
* controls, this rectangle is not used, but the dialog's size
* is still limited so that it never goes negative.
*/
/*
* First deal with the x coordinates.
*/
switch (HandleHit) {
case DRAG_LEFTBOTTOM:
case DRAG_LEFT:
case DRAG_LEFTTOP:
if (npcHead) {
if (prc->left > grcMinDialog.left)
prc->left = AtOrBelow(grcMinDialog.left, gcxGrid);
}
else {
if (prc->left > prc->right)
prc->left = AtOrBelow(prc->right, gcxGrid);
}
break;
case DRAG_RIGHTBOTTOM:
case DRAG_RIGHT:
case DRAG_RIGHTTOP:
if (npcHead) {
if (prc->right < grcMinDialog.right)
prc->right = AtOrAbove(grcMinDialog.right, gcxGrid);
}
else {
if (prc->right < prc->left)
prc->right = AtOrAbove(prc->left, gcxGrid);
}
break;
}
/*
* Now deal with the y coordinates.
*/
switch (HandleHit) {
case DRAG_LEFTBOTTOM:
case DRAG_BOTTOM:
case DRAG_RIGHTBOTTOM:
if (npcHead) {
if (prc->bottom < grcMinDialog.bottom)
prc->bottom = AtOrAbove(grcMinDialog.bottom, gcyGrid);
}
else {
if (prc->bottom < prc->top)
prc->bottom = AtOrAbove(prc->top, gcyGrid);
}
break;
case DRAG_LEFTTOP:
case DRAG_TOP:
case DRAG_RIGHTTOP:
if (npcHead) {
if (prc->top > grcMinDialog.top)
prc->top = AtOrBelow(grcMinDialog.top, gcyGrid);
}
else {
if (prc->top > prc->bottom)
prc->top = AtOrBelow(prc->bottom, gcyGrid);
}
break;
}
}
else {
/*
* First deal with the x coordinates.
*/
switch (HandleHit) {
case DRAG_LEFTBOTTOM:
case DRAG_LEFT:
case DRAG_LEFTTOP:
if (prc->left > prc->right)
prc->left = AtOrBelow(prc->right, gcxGrid);
if (prc->left == prc->right)
prc->left -= gcxGrid;
if (prc->left < 0)
prc->left = 0;
break;
case DRAG_RIGHTBOTTOM:
case DRAG_RIGHT:
case DRAG_RIGHTTOP:
cxDlg = gcd.npc->rc.right - gcd.npc->rc.left;
if (prc->right > cxDlg)
prc->right = AtOrBelow(cxDlg, gcxGrid);
if (prc->right < prc->left)
prc->right = AtOrAbove(prc->left, gcxGrid);
if (prc->right == prc->left)
prc->right += gcxGrid;
break;
}
/*
* Now deal with the y coordinates.
*/
switch (HandleHit) {
case DRAG_LEFTTOP:
case DRAG_TOP:
case DRAG_RIGHTTOP:
if (prc->top > prc->bottom)
prc->top = AtOrBelow(prc->bottom, gcyGrid);
if (prc->top == prc->bottom)
prc->top -= gcyGrid;
if (prc->top < 0)
prc->top = 0;
break;
case DRAG_LEFTBOTTOM:
case DRAG_BOTTOM:
case DRAG_RIGHTBOTTOM:
cyDlg = gcd.npc->rc.bottom - gcd.npc->rc.top;
/*
* Note that if there is an overhang allowed, then
* we do not limit how far down the bottom of the
* control can be.
*/
if (prc->bottom > cyDlg && !nOverHang)
prc->bottom = AtOrBelow(cyDlg, gcyGrid);
if (prc->bottom < prc->top)
prc->bottom = AtOrAbove(prc->top, gcyGrid);
if (prc->bottom == prc->top)
prc->bottom += gcyGrid;
break;
}
}
}
/************************************************************************
* AtOrAbove
*
* This routine takes a number, and returns the closest number that
* is equal to or above that number and is an integral of the given
* grid value.
*
* Arguments:
* INT nStart - Starting number (can be negative).
* INT nGrid - Grid value.
*
************************************************************************/
STATICFN INT AtOrAbove(
INT nStart,
INT nGrid)
{
register INT nAbove;
nAbove = (nStart / nGrid) * nGrid;
if (nStart > 0 && nStart != nAbove)
nAbove += nGrid;
return nAbove;
}
/************************************************************************
* AtOrBelow
*
* This routine takes a number, and returns the closest number that
* is equal to or below that number and is an integral of the given
* grid value.
*
* Arguments:
* INT nStart - Starting number (can be negative).
* INT nGrid - Grid value.
*
************************************************************************/
STATICFN INT AtOrBelow(
INT nStart,
INT nGrid)
{
register INT nBelow;
nBelow = (nStart / nGrid) * nGrid;
if (nStart < 0 && nStart != nBelow)
nBelow -= nGrid;
return nBelow;
}
/************************************************************************
* GetOverHang
*
* This function returns the height that the control can overhang the
* bottom of the dialog. This is currently only meaningful for comboboxes.
* If the control is not a combobox, zero is returned.
*
* Arguments:
* INT iType - Type of control (W_* constant).
* INT cy - Height of the control (in DU's).
*
************************************************************************/
INT GetOverHang(
INT iType,
INT cy)
{
if (iType != W_COMBOBOX)
return 0;
return max(cy - COMBOEDITHEIGHT, 0);
}
/************************************************************************
* GridizeRect
*
* This function "gridizes" coordinates in a rectangle. The current
* grid values are used. The fsGrid flag can contain OR'd together
* GRIDIZE_* values that specify which points to apply the gridding to.
* Upon return, all coordinates specified will have been rounded to the
* nearest grid boundary.
*
* If GRIDIZE_SAMESIZE is specified, the size of the control will be
* kept the same. This overrides the GRIDIZE_RIGHT and GRIDIZE_BOTTOM
* flags. In other words, any delta applied to left or top will
* be added to right and bottom to retain the original size of the
* rectangle.
*
* Arguments:
* PRECT prc - Rectangle to adjust to the current grid.
* INT fGridFlags - GRIDIZE_* flags. Specifies which points to gridize.
*
************************************************************************/
VOID GridizeRect(
PRECT prc,
INT fGridFlags)
{
register INT nTemp;
INT leftOld = prc->left;
INT topOld = prc->top;
if (fGridFlags & GRIDIZE_LEFT) {
nTemp = AtOrBelow(prc->left, gcxGrid);
if (prc->left - nTemp > gcxGrid / 2)
nTemp += gcxGrid;
prc->left = nTemp;
}
if (fGridFlags & GRIDIZE_TOP) {
nTemp = AtOrBelow(prc->top, gcyGrid);
if (prc->top - nTemp > gcyGrid / 2)
nTemp += gcyGrid;
prc->top = nTemp;
}
/*
* Do they want to retain the same size of the rectangle?
*/
if (fGridFlags & GRIDIZE_SAMESIZE) {
/*
* Shift the right coordinate over by the delta that
* was applied to the left.
*/
prc->right += prc->left - leftOld;
prc->bottom += prc->top - topOld;
}
else {
if (fGridFlags & GRIDIZE_RIGHT) {
nTemp = AtOrBelow(prc->right, gcxGrid);
if (prc->right - nTemp > gcxGrid / 2)
nTemp += gcxGrid;
prc->right = nTemp;
}
if (fGridFlags & GRIDIZE_BOTTOM) {
nTemp = AtOrBelow(prc->bottom, gcyGrid);
if (prc->bottom - nTemp > gcyGrid / 2)
nTemp += gcyGrid;
prc->bottom = nTemp;
}
}
}
/************************************************************************
* SizeDragToControl
*
* This routine sizes and positions the drag window associated with a
* control, based on the current size and position of the control.
*
* It takes into account the different origin that controls and dialogs
* have, and sizes the drag window to fit around the control properly.
* The Z order of the drag window is NOT changed.
*
* This routine should only be called for controls, not the dialog.
*
* Arguments:
* NPCTYPE npc - Control whose drag window needs to be sized.
*
************************************************************************/
VOID SizeDragToControl(
NPCTYPE npc)
{
RECT rc;
rc = npc->rc;
DUToWinRect(&rc);
InflateRect(&rc, CHANDLESIZE / 2, CHANDLESIZE / 2);
SetWindowPos(npc->hwndDrag, NULL, rc.left, rc.top,
rc.right - rc.left, rc.bottom - rc.top,
SWP_NOACTIVATE | SWP_NOZORDER);
}
/************************************************************************
* DragWndProc
*
* This is the window procedure for the "drag" class. This window
* is placed behind a control and is what the user grabs to size a
* window with the mouse.
*
************************************************************************/
WINDOWPROC DragWndProc(
HWND hwnd,
UINT msg,
WPARAM wParam,
LPARAM lParam)
{
POINT pt;
switch (msg) {
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hDC;
PaintUnderDrag(hwnd);
hDC = BeginPaint(hwnd, &ps);
DrawHandles(hwnd, hDC,
(gnpcSel && hwnd == gnpcSel->hwndDrag) ? TRUE : FALSE);
EndPaint(hwnd, &ps);
}
break;
case WM_NCHITTEST:
((pt).x = ((*((POINTS *)&(lParam)))).x, (pt).y = ((*((POINTS *)&(lParam)))).y);
ScreenToClient(hwnd, &pt);
if (HandleHitTest(hwnd, pt.x, pt.y) == DRAG_CENTER)
return HTTRANSPARENT;
else
return HTCLIENT;
case WM_SETCURSOR:
/*
* Defeat the system changing cursors on us. We do it based
* on our own hit testing.
*/
break;
case WM_LBUTTONDOWN:
((pt).x = ((*((POINTS *)&(lParam)))).x, (pt).y = ((*((POINTS *)&(lParam)))).y);
CtrlButtonDown(hwnd, pt.x, pt.y, TRUE);
break;
case WM_MOUSEMOVE:
((pt).x = ((*((POINTS *)&(lParam)))).x, (pt).y = ((*((POINTS *)&(lParam)))).y);
CtrlMouseMove(hwnd, TRUE, pt.x, pt.y);
break;
case WM_LBUTTONUP:
((pt).x = ((*((POINTS *)&(lParam)))).x, (pt).y = ((*((POINTS *)&(lParam)))).y);
CtrlButtonUp(pt.x, pt.y);
break;
case WM_RBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_LBUTTONDBLCLK:
case WM_RBUTTONDBLCLK:
case WM_MBUTTONDBLCLK:
/*
* Prevents calling SetFocus when the middle or right
* mouse buttons are pressed (or doubleclicked).
*/
break;
case WM_NCCALCSIZE:
/*
* The client area is the entire control.
*/
break;
case WM_DESTROY:
/*
* When destroying the drag window, we must be sure and
* remove the properties associated with it.
*/
UNSETPCINTOHWND(hwnd);
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0L;
}
/************************************************************************
* DrawHandles
*
* This routine draws the drag handles for a drag window. The handles
* will be solid (filled) if fCurrentSelection is TRUE, or hollow if it
* is FALSE.
*
* Arguments:
* HWND hwnd - Drag window handle.
* HDC hDC - DC to use to draw in this window.
* BOOL fCurrentSelection - TRUE if this control is the "current"
* selection.
*
************************************************************************/
VOID DrawHandles(
HWND hwnd,
HDC hDC,
BOOL fCurrentSelection)
{
RECT rc;
INT xMid;
INT yMid;
INT x2;
INT y2;
HBITMAP hbmOld;
GetWindowRect(hwnd, &rc);
OffsetRect(&rc, -rc.left, -rc.top);
/*
* Precalculate some points.
*/
xMid = ((rc.right + 1) / 2) - (CHANDLESIZE / 2);
yMid = ((rc.bottom + 1) / 2) - (CHANDLESIZE / 2);
x2 = rc.right - CHANDLESIZE;
y2 = rc.bottom - CHANDLESIZE;
/*
* Draw a solid box if this is the currently selected
* control, otherwise draw a hollow box.
*/
if (fCurrentSelection)
hbmOld = SelectObject(ghDCMem, ghbmDragHandle);
else
hbmOld = SelectObject(ghDCMem, ghbmDragHandle2);
BitBlt(hDC, 0, 0, CHANDLESIZE, CHANDLESIZE,
ghDCMem, 0, 0, SRCCOPY);
BitBlt(hDC, xMid, 0, CHANDLESIZE, CHANDLESIZE,
ghDCMem, 0, 0, SRCCOPY);
BitBlt(hDC, x2, 0, CHANDLESIZE, CHANDLESIZE,
ghDCMem, 0, 0, SRCCOPY);
BitBlt(hDC, x2, yMid, CHANDLESIZE, CHANDLESIZE,
ghDCMem, 0, 0, SRCCOPY);
BitBlt(hDC, x2, y2, CHANDLESIZE, CHANDLESIZE,
ghDCMem, 0, 0, SRCCOPY);
BitBlt(hDC, xMid, y2, CHANDLESIZE, CHANDLESIZE,
ghDCMem, 0, 0, SRCCOPY);
BitBlt(hDC, 0, y2, CHANDLESIZE, CHANDLESIZE,
ghDCMem, 0, 0, SRCCOPY);
BitBlt(hDC, 0, yMid, CHANDLESIZE, CHANDLESIZE,
ghDCMem, 0, 0, SRCCOPY);
SelectObject(ghDCMem, hbmOld);
}
/************************************************************************
* PaintUnderDrag
*
* This function is used during a paint operation for a visible drag
* window. It checks underneath the area to update and forces any
* windows down there to paint themselves, in such an order as to cause
* the last one painted to be the visually top window. This is necessary
* to implement the drag windows as "transparent" windows. After this
* routine finishes, the caller can paint the "handles". The caller must
* call this routine before calling BeginPaint, or the update area
* will be null and nothing will get painted.
*
* It is assumed that the drag windows are for controls, not for the
* dialog.
*
* Arguments:
* HWND hwndDrag - Drag window to paint under.
*
************************************************************************/
STATICFN VOID PaintUnderDrag(
HWND hwndDrag)
{
RECT rc;
RECT rcInt;
RECT rcUpdate;
HWND hwnd;
HWND hwndControl;
NPCTYPE npc;
/*
* Get our corresponding control window.
*/
hwndControl = (PCFROMHWND(hwndDrag))->hwnd;
/*
* Get the update rectangle and convert to screen coords.
*/
GetUpdateRect(hwndDrag, &rcUpdate, TRUE);
ClientToScreenRect(hwndDrag, &rcUpdate);
/*
* Start enumerating windows.
*/
hwnd = hwndDrag;
while (hwnd = GetWindow(hwnd, GW_HWNDNEXT)) {
/*
* Skip invisible drag windows.
*/
if (IsWindowVisible(hwnd)) {
/*
* Does the window rectangle intersect the update rectangle?
*/
GetWindowRect(hwnd, &rc);
if (IntersectRect(&rcInt, &rc, &rcUpdate)) {
npc = PCFROMHWND(hwnd);
if (npc->hwndDrag == hwnd || !npc->fSelected) {
ScreenToClientRect(hwnd, &rcInt);
InvalidateRect(hwnd, &rcInt, TRUE);
UpdateWindow(hwnd);
}
if (npc->hwndDrag == hwnd)
break;
}
}
}
/*
* Finally, paint the control associated with this drag window.
*/
InvalidateRect(hwndControl, NULL, TRUE);
UpdateWindow(hwndControl);
}
/************************************************************************
* HandleHitTest
*
* This routine takes a point from a mouse button press on a drag window
* and returns which "handle" was hit, if any.
*
* The coordinates are given in zero based coordinates of the drag
* window.
*
* Arguments:
* HWND hwnd - Drag window handle the x,y point is relative to.
* INT x - Mouse X location (in the drag's client coordinates).
* INT y - Mouse Y location (in the drag's client coordinates).
*
* Returns:
* One of the DRAG_* constants. If no handle was hit, the
* return will be DRAG_CENTER.
*
************************************************************************/
INT HandleHitTest(
HWND hwnd,
INT x,
INT y)
{
RECT rc;
INT xMidStart;
INT yMidStart;
/*
* If there are multiple controls selected, or if the control
* type does not allow sizing, defeat the ability to size
* with the handles by returning DRAG_CENTER.
*/
if (gcSelected > 1 || !(PCFROMHWND(hwnd))->pwcd->fSizeable)
return DRAG_CENTER;
/*
* Get the window rectangle and cause it to be zero-origined.
*/
GetWindowRect(hwnd, &rc);
OffsetRect(&rc, -rc.left, -rc.top);
/*
* Calculate the starting points for the handles
* that are not on a corner.
*/
xMidStart = ((rc.right + 1) / 2) - (CHANDLESIZE / 2);
yMidStart = ((rc.bottom + 1) / 2) - (CHANDLESIZE / 2);
if (x < CHANDLESIZE) {
if (y < CHANDLESIZE)
return DRAG_LEFTTOP;
else if (y > rc.bottom - CHANDLESIZE)
return DRAG_LEFTBOTTOM;
else if (y >= yMidStart && y < yMidStart + CHANDLESIZE)
return DRAG_LEFT;
}
else if (x > rc.right - CHANDLESIZE) {
if (y < CHANDLESIZE)
return DRAG_RIGHTTOP;
else if (y > rc.bottom - CHANDLESIZE)
return DRAG_RIGHTBOTTOM;
else if (y >= yMidStart && y < yMidStart + CHANDLESIZE)
return DRAG_RIGHT;
}
else if (x >= xMidStart && x < xMidStart + CHANDLESIZE) {
if (y < CHANDLESIZE)
return DRAG_TOP;
else if (y > rc.bottom - CHANDLESIZE)
return DRAG_BOTTOM;
}
return DRAG_CENTER;
}
/************************************************************************
* MouseToDragRect
*
* This routine takes the mouse pointer coordinates from a mouse message
* and produces a rectangle that contains the coordinates that the drag
* rectangle should be displayed as. This is for tracking (moving/sizing)
* operations. It relies on a number of globals to have been set up
* prior to the call with such things as the current control, type of
* drag, old location of the control, offset of the original mouse press
* from the origin of the control, etc.
*
* The returned rectangle is pegged to the boundaries of the dialog (if it
* is for a control), and is aligned to the current grid units. It is in
* dialog units relative to the appropriate point based on whether it is
* for a control or the dialog, and must be converted to window points
* before the actual drag rectangle can be drawn on the screen.
*
* Arguments:
* INT x - Mouse X location (in window coordinates).
* INT y - Mouse Y location (in window coordinates).
* PRECT prc - Rectangle to return the appropriate drag rectangle
* in.
*
************************************************************************/
STATICFN VOID MouseToDragRect(
INT x,
INT y,
PRECT prc)
{
POINT pt;
INT fGridFlags;
pt.x = x;
pt.y = y;
MouseToDU(&pt);
switch (gHandleHit) {
case DRAG_LEFTBOTTOM:
SetRect(prc, pt.x, grcSelected.top, grcSelected.right,
(grcSelected.bottom - grcSelected.top) + pt.y);
fGridFlags = GRIDIZE_LEFT | GRIDIZE_BOTTOM;
break;
case DRAG_BOTTOM:
SetRect(prc, grcSelected.left, grcSelected.top,
grcSelected.right,
(grcSelected.bottom - grcSelected.top) + pt.y);
fGridFlags = GRIDIZE_BOTTOM;
break;
case DRAG_RIGHTBOTTOM:
SetRect(prc, grcSelected.left, grcSelected.top,
(grcSelected.right - grcSelected.left) + pt.x,
(grcSelected.bottom - grcSelected.top) + pt.y);
fGridFlags = GRIDIZE_BOTTOM | GRIDIZE_RIGHT;
break;
case DRAG_RIGHT:
SetRect(prc, grcSelected.left, grcSelected.top,
(grcSelected.right - grcSelected.left) + pt.x,
grcSelected.bottom);
fGridFlags = GRIDIZE_RIGHT;
break;
case DRAG_RIGHTTOP:
SetRect(prc, grcSelected.left, pt.y,
(grcSelected.right - grcSelected.left) + pt.x,
grcSelected.bottom);
fGridFlags = GRIDIZE_RIGHT | GRIDIZE_TOP;
break;
case DRAG_TOP:
SetRect(prc, grcSelected.left, pt.y,
grcSelected.right, grcSelected.bottom);
fGridFlags = GRIDIZE_TOP;
break;
case DRAG_LEFTTOP:
SetRect(prc, pt.x, pt.y, grcSelected.right, grcSelected.bottom);
fGridFlags = GRIDIZE_LEFT | GRIDIZE_TOP;
break;
case DRAG_LEFT:
SetRect(prc, pt.x, grcSelected.top,
grcSelected.right, grcSelected.bottom);
fGridFlags = GRIDIZE_LEFT;
break;
case DRAG_CENTER:
SetRect(prc, pt.x, pt.y,
(grcSelected.right - grcSelected.left) + pt.x,
(grcSelected.bottom - grcSelected.top) + pt.y);
fGridFlags = GRIDIZE_LEFT | GRIDIZE_TOP | GRIDIZE_SAMESIZE;
break;
}
GridizeRect(prc, fGridFlags);
FitRectToBounds(prc, gnOverHang, gHandleHit, gfDlgSelected);
}
/************************************************************************
* MouseToDU
*
* This routine converts the point at ppt from window coordinates
* for the current control into the closest Dialog Unit point.
*
* This routine normally assumes that the point is relative to the selected
* control, and will convert the point into DU's. The current control
* can either be the current dialog or one of its child controls.
* The DU's returned will be appropriate for the type of control. If
* it is the dialog, they will be relative to the apps client area.
* If it is a control, they will be relative to the "client" area of
* the current dialog.
*
* If there is no current selection (such as when dropping a new control),
* the point is assumed to already be relative to the dialog and it will
* will not be mapped. This only applies when called with a point for a
* control, not a dialog.
*
* Arguments:
* PPOINT ppt - Point to convert.
*
************************************************************************/
STATICFN VOID MouseToDU(
PPOINT ppt)
{
if (gfDlgSelected) {
/*
* Map the points from the dialog to the app client.
*/
ClientToScreen(gcd.npc->hwnd, ppt);
ScreenToClient(ghwndSubClient, ppt);
/*
* Subtract the cursor offset.
*/
ppt->x -= gptCursorOffset.x;
ppt->y -= gptCursorOffset.y;
}
else {
/*
* Map the points from the control to the dialog window,
* but only if there is a current selection. There will
* not be a current selection when dropping a new control,
* and the point must already be relative to the dialog!
*/
if (gnpcSel) {
ClientToScreen(gnpcSel->hwnd, ppt);
ScreenToClient(gcd.npc->hwnd, ppt);
}
/*
* Subtract the cursor offset, then the dialogs frame
* controls offset.
*/
ppt->x -= gptCursorOffset.x;
ppt->y -= gptCursorOffset.y;
}
/*
* Convert this position to dialog units.
*/
WinToDUPoint(ppt);
}
/************************************************************************
* CtrlButtonDown
*
* This routine is called by the control and drag window procs when
* the left mouse button is pressed. It checks for the Ctrl modifier key
* then passes control on to the appropriate routine.
*
* When hwnd is the dialog itself, fHandleWindow must be TRUE, even
* if a handle was not hit, because of the special-case code that must
* be done for the dialog (it doesn't have a separate drag window).
*
* Arguments:
* HWND hwnd - Window handle, can be a drag window or control.
* INT x - X mouse location (window coordinates).
* INT y - Y mouse location (window coordinates).
* BOOL fHandleWindow - TRUE if a handle was clicked on, or FALSE if the
* body of a control was clicked on.
*
************************************************************************/
VOID CtrlButtonDown(
HWND hwnd,
INT x,
INT y,
BOOL fHandleWindow)
{
POINT pt;
HWND hwndHit;
INT nOverHang;
/*
* Discard all mouse messages during certain operations.
*/
if (gfDisabled)
return;
/*
* Also, be sure any outstanding changes get applied
* without errors.
*/
if (!StatusApplyChanges())
return;
if (gCurTool != W_NOTHING) {
nOverHang = GetOverHang(gpwcdCurTool->iType, gpwcdCurTool->cyDefault);
DragNewBegin(gpwcdCurTool->cxDefault,
gpwcdCurTool->cyDefault, nOverHang);
}
else {
/*
* If the Control key is down, duplicate the control, unless
* it is the dialog (mouse duplicate of the dialog is not
* supported).
*/
if ((GetKeyState(VK_CONTROL) & 0x8000) &&
(PCFROMHWND(hwnd))->pwcd->iType != W_DIALOG) {
/*
* First, figure out which control was hit and select it.
*/
if (fHandleWindow) {
hwndHit = hwnd;
}
else {
pt.x = x;
pt.y = y;
hwndHit = CtrlHitTest(hwnd, &pt);
}
SelectControl(PCFROMHWND(hwndHit), TRUE);
/*
* If there is still a selection, begin dragging a copy
* of it. The check here is necessary because the prior
* call to SelectControl can unselect the last selected
* control if the Shift key was held down.
*/
if (gcSelected)
Duplicate();
}
else {
/*
* Start a drag operation. This can be either moving or sizing
* the control, depending on fHandleWindow and where the mouse
* click is at.
*/
DragBegin(hwnd, x, y, fHandleWindow);
}
}
}
/************************************************************************
* DragNewBegin
*
* This routine begins a drag operation when dropping a new control, or
* group of controls. It is NOT used when dragging existing controls.
*
* Arguments:
* INT cx - Width of the new control.
* INT cy - Height of the new control.
* INT nOverHang - How much the control can overhang the dialog bottom.
*
************************************************************************/
VOID DragNewBegin(
INT cx,
INT cy,
INT nOverHang)
{
POINTS mpt;
POINT pt;
DWORD dwPos;
/*
* 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);
/*
* Cancel any current selection, and set some state globals.
*/
CancelSelection(TRUE);
gHandleHit = DRAG_CENTER;
gState = STATE_DRAGGINGNEW;
SetCursor(hcurMove);
/*
* The cursor offset is set to be located in the middle of the
* new control. This causes the pointer to be initially located
* exactly in the center.
*/
gptCursorOffset.x = cx;
gptCursorOffset.y = cy;
DUToWinPoint(&gptCursorOffset);
gptCursorOffset.x /= 2;
gptCursorOffset.y /= 2;
/*
* Set a global with the overhang. This is used all during the
* drag operation we are starting.
*/
gnOverHang = nOverHang;
/*
* Now we make up a dummy rectangle for the new control. We start
* it at (0,0) with a size of cx and cy. The point where the mouse
* was when the command was done is obtained and mapped to the dialog
* (it is assumed that only controls will be done here, not a dialog).
* The new control rectangle is then converted to be at this location,
* it is gridized, the tracking rectangle is shown and we are off.
*/
SetRect(&grcSelected, 0, 0, cx, cy);
dwPos = GetMessagePos();
mpt = (*((POINTS *)&(dwPos)));
((pt).x = (mpt).x, (pt).y = (mpt).y);
ScreenToClient(gcd.npc->hwnd, &pt);
MouseToDragRect(pt.x, pt.y, &grcSelected);
InitTracking();
DrawTrackRect(&grcSelected, FALSE, TRUE);
/*
* Display the initial coordinates.
*/
StatusSetCoords(&grcSelected);
/*
* The mouse messages will come through the dialog subclass proc for
* these kinds of operations.
*/
SetCapture(gcd.npc->hwnd);
}
/************************************************************************
* DragBegin
*
* This routine begins a drag operation for either moving a control or
* sizing it. The tracking rectangle is not actually drawn until the
* mouse moves by a grid unit, however.
*
* To begin a drag on the dialog itself, fHandleWindow must be TRUE, even
* if a handle was not hit, because of the special-case code that must
* be done for the dialog (it doesn't have a separate drag window).
*
* Arguments:
* HWND hwnd - Window handle, can be a drag window or control.
* INT x - Starting X mouse location (window coordinates).
* INT y - Starting Y mouse location (window coordinates).
* BOOL fHandleWindow - TRUE if the drag is happening because a drag
* handle was clicked on, or FALSE if the drag is
* happening because the body of a control is
* clicked on.
*
*
************************************************************************/
STATICFN VOID DragBegin(
HWND hwnd,
INT x,
INT y,
BOOL fHandleWindow)
{
NPCTYPE npcT;
HWND hwndHit;
POINT pt;
NPCTYPE npc;
BOOL fPrevSelect = FALSE;
INT nBottom;
/*
* 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);
pt.x = x;
pt.y = y;
/*
* Is this drag happening because a drag handle was clicked on?
*/
if (fHandleWindow) {
/*
* Find out which handle was clicked on. It is assumed that
* hwnd is a drag window.
*/
gHandleHit = HandleHitTest(hwnd, pt.x, pt.y);
hwndHit = hwnd;
}
else {
/*
* The body of a control was clicked on. Set a global to say
* that we are moving a control, then find out which control
* was hit (with our own hit testing).
*/
gHandleHit = DRAG_CENTER;
hwndHit = CtrlHitTest(hwnd, &pt);
}
/*
* Find out if the control clicked on was the currently selected
* control already.
*/
npc = PCFROMHWND(hwndHit);
if (npc == gnpcSel)
fPrevSelect = TRUE;
/*
* Select the control. This can return FALSE if the control is
* unselected (shift key is down and the control is already selected).
*/
if (!SelectControl(npc, TRUE))
return;
/*
* If the dialog is selected, we make a rectangle that encloses all
* the controls. This will be used to limit the size that the dialog
* can be sized to so that it cannot cover any existing controls.
*/
if (gfDlgSelected) {
/*
* Seed the rectangle with impossible values.
*/
SetRect(&grcMinDialog, 32000, 32000, -32000, -32000);
/*
* Loop through all the controls, expanding the rectangle to
* fit around all the controls.
*/
for (npcT = npcHead; npcT; npcT = npcT->npcNext) {
if (npcT->rc.left < grcMinDialog.left)
grcMinDialog.left = npcT->rc.left;
if (npcT->rc.right > grcMinDialog.right)
grcMinDialog.right = npcT->rc.right;
if (npcT->rc.top < grcMinDialog.top)
grcMinDialog.top = npcT->rc.top;
/*
* When calculating the bottom boundary of the controls,
* make the rectangle shorter by the ovehang amount. This
* allows the dialog to be sized up so that it covers
* parts of controls with overhang (comboboxes).
*/
nBottom = npcT->rc.bottom - GetOverHang(npcT->pwcd->iType,
npcT->rc.bottom - npcT->rc.top);
if (nBottom > grcMinDialog.bottom)
grcMinDialog.bottom = nBottom;
}
OffsetRect(&grcMinDialog, gcd.npc->rc.left, gcd.npc->rc.top);
}
/*
* If the control clicked on was already the anchor, go right into
* dragging mode. If it was not, go into pre-drag mode, which will
* defer the calculation of offsets, etc., until after a certain
* small amount of time so the mouse can be "debounced".
*/
if (fPrevSelect) {
DragBegin2(&pt);
}
else {
gState = STATE_PREDRAG;
/*
* Save the point in a global. If the mouse pointer is moved
* too far away from this point, we will start the drag operation
* even if the pre-drag time has not elapsed yet.
*/
gptPreDragStart = pt;
/*
* Start the pre-drag timer.
*/
SetTimer(hwndHit, TID_PREDRAG, gmsecPreDrag, NULL);
}
/*
* The mouse messages from now on will go to the window clicked on,
* either the drag window or the control window.
*/
SetCapture(hwndHit);
}
/************************************************************************
* DragBegin2
*
* This routine continues the initiation of a drag operation started
* by DragBegin. It is separate because it calculates offsets based
* on where the mouse pointer is, and these calculations can be deferred
* until a later time than when DragBegin was called so that the mouse
* can be "debounced".
*
* Arguments:
* POINT ppt - Starting mouse location (window coordinates).
*
************************************************************************/
STATICFN VOID DragBegin2(
PPOINT ppt)
{
gState = STATE_DRAGGING;
/*
* Set the pointer to the "move" pointer if we are moving.
* Otherwise, the pointer should already be set to the proper
* sizing pointer.
*/
if (gHandleHit == DRAG_CENTER)
SetCursor(hcurMove);
/*
* Save away the initial offset of the cursor.
*/
CalcCursorOffset(ppt);
/*
* Initialize the track rectangle. Note we are calling DrawTrackRect
* with FALSE.
*/
DrawTrackRect(&grcSelected, gfDlgSelected, FALSE);
}
/****************************************************************************
* CtrlHitTest
*
* This routine walks the list of controls and determines which one is
* "hit" by this point. If a hit is found, the point is also converted
* to coordinates for the hit window.
*
* There is a special case when hitting controls over a groupbox.
* Controls within a groupbox will always be hit instead of the
* groupbox itself.
*
* Arguments:
* HWND hwnd - Window handle the coordinates are relative to.
* PPOINT ppt - Window point where the click occurred (window coords).
*
* Returns:
* The hwnd of the "hit" control will be returned. If no control was hit,
* the hwnd that was passed in is returned.
*
****************************************************************************/
STATICFN HWND CtrlHitTest(
HWND hwnd,
PPOINT ppt)
{
NPCTYPE npc;
RECT rc;
HWND hwndHit = (HWND)NULL;
BOOL fGroupHit = FALSE;
for (npc = npcHead; npc; npc = npc->npcNext) {
GetWindowRect(npc->hwnd, &rc);
ScreenToClientRect(npc->hwnd, &rc);
MyMapWindowRect(npc->hwnd, hwnd, &rc);
/*
* Is this a hit, and was there either no control hit as
* yet, or this control is not a groupbox, or the control
* that was previously hit was a groupbox also?
*/
if (PtInRect(&rc, *ppt) &&
(!hwndHit || npc->pwcd->iType != W_GROUPBOX || fGroupHit)) {
hwndHit = npc->hwnd;
if (npc->pwcd->iType == W_GROUPBOX)
fGroupHit = TRUE;
else
fGroupHit = FALSE;
}
}
if (hwndHit) {
MapWindowPoint(hwnd, hwndHit, ppt);
return hwndHit;
}
else {
return hwnd;
}
}
/****************************************************************************
* PreDragTimeout
*
* This function handles the WM_TIMER message from the control window
* proc. It is used so that the dragging of a newly selected control
* can be deferred for a small period of time to "debounce" the mouse.
*
* This function is also called if the mouse is moved too much during the
* debounce time, effectively cutting the debounce time short.
*
* Arguments:
* HWND hwnd - Window handle the timer came from.
* BOOL fTimedOut - TRUE if the predrag is ending because the timer
* expired. FALSE if the predrag is ending because
* the mouse was moved too far.
*
****************************************************************************/
VOID PreDragTimeout(
HWND hwnd,
BOOL fTimedOut)
{
POINT pt;
/*
* The debounce time is over and the mouse button is still
* down. Get the current mouse pointer location and go into
* drag mode.
*/
if (gState == STATE_PREDRAG) {
/*
* If we timed out (the mouse was not moved a large distance),
* eat any small movement that may have been done during the
* predrag time by setting the mouse cursor back to the location
* that it started at. Note that we do not do this if the mouse
* was moved a large distance, because the efect would be
* noticeable for that case, and we want the control to be
* moved then anyways.
*/
if (fTimedOut) {
pt = gptPreDragStart;
ClientToScreen(hwnd, &pt);
SetCursorPos(pt.x, pt.y);
}
DragBegin2(&gptPreDragStart);
}
KillTimer(hwnd, TID_PREDRAG);
}
/************************************************************************
* CtrlMouseMove
*
* This routine handles the mouse move messages for controls, the dialog,
* drag windows and when dropping a new control.
*
* During a drag operation, the tracking rectangle will be adjusted.
* If there is not a drag operation is effect, the mouse cursor will
* be changed to a sizing pointer if it is over a drag handle. If not
* over a drag handle, the pointer will be changed back to the arrow.
*
* Arguments:
* HWND hwnd - Window handle the x and y are relative to.
* BOOL fDragWindow - TRUE if hwnd is a drag window.
* INT x - X location of the mouse movement (window coords).
* INT y - Y location of the mouse movement (window coords).
*
************************************************************************/
VOID CtrlMouseMove(
HWND hwnd,
BOOL fDragWindow,
INT x,
INT y)
{
RECT rc;
RECT rc2;
HCURSOR hcur = NULL;
/*
* Discard all mouse messages during certain operations
* (but still set the pointer properly).
*/
if (gfDisabled) {
SetCursor(hcurArrow);
return;
}
switch (gState) {
case STATE_PREDRAG:
/*
* If the mouse was moved too far, consider the
* pre-drag time elapsed and go into drag mode.
*/
if (abs(gptPreDragStart.x - x) > gcxPreDragMax ||
abs(gptPreDragStart.y - y) > gcyPreDragMax)
PreDragTimeout(hwnd, FALSE);
break;
case STATE_DRAGGING:
case STATE_DRAGGINGNEW:
MouseToDragRect(x, y, &rc);
if (!EqualRect(&rc, &grcTrackDU)) {
/*
* If the tracking rectangle is not shown, this means that
* this is the first significant mouse move since the start
* of a drag operation, and we need to lock the window, get
* our clip DC, etc.
*/
if (!gfTrackRectShown)
InitTracking();
DrawTrackRect(&rc, gfDlgSelected, TRUE);
if (gcSelected > 1) {
/*
* Since there are multiple controls selected,
* rc will be the rectangle that surrounds them
* all. We really want to just show the anchor
* controls new position, so we have to do a
* little math to calculate and display it.
*/
rc2 = gnpcSel->rc;
OffsetRect(&rc2, rc.left - grcSelected.left,
rc.top - grcSelected.top);
StatusSetCoords(&rc2);
}
else {
/*
* Either a single control is being dragged or
* a new control is being dropped.
*/
StatusSetCoords(&rc);
}
}
break;
case STATE_SELECTING:
OutlineSelectDraw(x, y);
break;
default:
/*
* Is there a tool selected?
*/
if (gCurTool != W_NOTHING) {
hcur = hcurDropTool;
}
else {
/*
* If hwnd is a drag window, see if the pointer is over
* over any of the handles and change it to one of the
* sizing pointers if necessary. Otherwise set the pointer
* to the default arrow pointer.
*/
if (fDragWindow) {
switch (HandleHitTest(hwnd, x, y)) {
case DRAG_LEFTBOTTOM:
case DRAG_RIGHTTOP:
hcur = hcurSizeNESW;
break;
case DRAG_LEFTTOP:
case DRAG_RIGHTBOTTOM:
hcur = hcurSizeNWSE;
break;
case DRAG_BOTTOM:
case DRAG_TOP:
hcur = hcurSizeNS;
break;
case DRAG_RIGHT:
case DRAG_LEFT:
hcur = hcurSizeWE;
break;
case DRAG_CENTER:
default:
hcur = hcurArrow;
break;
}
}
else {
hcur = hcurArrow;
}
}
break;
}
if (hcur)
SetCursor(hcur);
}
/************************************************************************
* DragCancel
*
* This function cancels any drag operation in effect. It handles
* such things as erasing any visible tracking rectangle, freeing any
* "copy" data, setting globals and updating the status display. It
* can be used no matter how the drag operation was started.
*
************************************************************************/
VOID DragCancel(VOID)
{
HWND hwnd;
switch (gState) {
case STATE_PREDRAG:
/*
* Stop the timer. Note that this assumes the timer
* was attached to the capture window. This should
* be safe (see the associated SetTimer).
*/
if (hwnd = GetCapture())
KillTimer(hwnd, TID_PREDRAG);
break;
case STATE_DRAGGING:
case STATE_DRAGGINGNEW:
CancelTracking();
if (gpResCopy) {
MyFree(gpResCopy);
gpResCopy = NULL;
}
break;
}
gState = STATE_NORMAL;
ReleaseCapture();
SetCursor(hcurArrow);
StatusUpdate();
StatusSetEnable();
}
/************************************************************************
* CtrlButtonUp
*
* This function is called when the left mouse button is released. Depending
* on the mode, it will complete the operation started when the button
* was pressed down.
*
* Arguments:
* INT x - Mouse X location (in window coords).
* INT y - Mouse Y location (in window coords).
*
************************************************************************/
VOID CtrlButtonUp(
INT x,
INT y)
{
/*
* Discard all mouse messages during certain operations.
*/
if (gfDisabled)
return;
switch (gState) {
case STATE_PREDRAG:
/*
* They released the mouse button during the debounce time,
* so cancel the drag.
*/
DragCancel();
break;
case STATE_DRAGGING:
case STATE_DRAGGINGNEW:
DragEnd(x, y);
break;
case STATE_SELECTING:
OutlineSelectEnd(x, y);
break;
default:
break;
}
}
/************************************************************************
* DragEnd
*
* This function completes all kinds of drag operations. If dragging a
* new control, it will be dropped at the specified location. If dragging
* an existing control, it will be positioned to the given location.
*
* Arguments:
* INT x - X location the control ended up at (in window coords).
* INT y - Y location the control ended up at (in window coords).
*
************************************************************************/
VOID DragEnd(
INT x,
INT y)
{
PDIALOGBOXHEADER pdbh;
PCONTROLDATA pcd;
INT cControls;
RECT rc;
INT i;
INT cx;
INT cy;
CancelTracking();
MouseToDragRect(x, y, &rc);
if (gState == STATE_DRAGGING) {
PositionControl(&rc);
}
else {
if (gpResCopy) {
pdbh = (PDIALOGBOXHEADER)SkipResHeader(gpResCopy);
cControls = (INT)pdbh->NumberOfItems;
pcd = SkipDialogBoxHeader(pdbh);
cx = rc.left - grcCopy.left;
cy = rc.top - grcCopy.top;
/*
* Loop through all the controls, adjusting their position
* according to where the drag rectangle ended up.
*/
for (i = 0; i < cControls; i++) {
/*
* Add cx and cy to the resource's x and y fields.
*/
pcd->x += (WORD)cx;
pcd->y += (WORD)cy;
pcd = SkipControlData(pcd);
}
/*
* Now we go and create all the controls, adding them to
* the current dialog. It is assumed that the image in
* gpResCopy specifies controls to add, and not a dialog
* to create!
*/
if (ResToDialog(gpResCopy, FALSE)) {
gfResChged = gfDlgChanged = TRUE;
ShowFileStatus(FALSE);
}
MyFree(gpResCopy);
gpResCopy = NULL;
StatusUpdate();
StatusSetEnable();
}
else {
/*
* Drop the new control.
*/
DropControl(gpwcdCurTool, &rc);
if (!gfToolLocked)
ToolboxSelectTool(W_NOTHING, FALSE);
}
}
gState = STATE_NORMAL;
ReleaseCapture();
SetCursor(hcurArrow);
}