PAGEMOUS.CPP

/* 
* PAGEMOUS.CPP
* Patron Chapter 20
*
* Implementation of mouse-related member functions of CPage.
* The remainder is in PAGE.CPP. This separate file keeps this
* grungy hit-testing/drawing code out of our way.
*
* Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
*
* Kraig Brockschmidt, Microsoft
* Internet : kraigb@microsoft.com
* Compuserve: >INTERNET:kraigb@microsoft.com
*/


#include "patron.h"


//Lookups into the array using g_rgHTCode[x+y*3] in PAGEMOUS.CPP
#define YTOP 0
#define YMID 1
#define YBOT 2
#define XLEFT 0
#define XMID 1
#define XRIGHT 2

//Values to restrict sizing in CPage::OnMouseMove
#define SIZINGTOP 0x0001
#define SIZINGBOTTOM 0x0002
#define SIZINGLEFT 0x0004
#define SIZINGRIGHT 0x0008


//This array is for hit-testing lookups
static UINT g_rgHTCode[9]={HTTOPLEFT, HTTOP, HTTOPRIGHT
, HTLEFT, HTCLIENT, HTRIGHT, HTBOTTOMLEFT, HTBOTTOM
, HTBOTTOMRIGHT};


//This is for restricting tracking based on the hit-test
static UINT g_rguSizingFlags[9]={SIZINGTOP | SIZINGLEFT, SIZINGTOP
, SIZINGTOP | SIZINGRIGHT, SIZINGLEFT, 0, SIZINGRIGHT
, SIZINGBOTTOM | SIZINGLEFT, SIZINGBOTTOM
, SIZINGBOTTOM | SIZINGRIGHT};



/*
* CPage::OnRightDown
*
* Purpose:
* Called when the user clicks with the right button on this
* page. If there is an object here, determined by the last
* hit-test code, the we'll make a popup-menu for it.
*
* Parameters:
* uKeys UINT carrying the key state.
* x, y UINT coordinates of the click in device units.
*
* Return Value:
* BOOL Indicates if the action changed the object.
*/

BOOL CPage::OnRightDown(UINT uKeys, UINT x, UINT y)
{
HMENU hMenu;
HMENU hMenuRes;
HINSTANCE hInst;
HWND hWndFrame, hWndT;
POINT pt;
UINT i, cItems;

//Select the tenant under the mouse, if there is one.
if (!SelectTenantAtPoint(x, y))
return FALSE;

/*
* Get the top-level window to which menu command will go. This
* will be whatever parent doesn't have a parent itself...
*/
hWndT=GetParent(m_hWnd);

while (NULL!=hWndT)
{
hWndFrame=hWndT;
hWndT=GetParent(hWndT);
}

/*
* Build a popup menu for this object with Cut, Copy, Delete,
* and object verbs.
*/
hInst=GETWINDOWINSTANCE(m_hWnd); //Macro in BOOK1632.H
hMenuRes=LoadMenu(hInst, MAKEINTRESOURCE(IDR_RIGHTPOPUPMENU));

if (NULL==hMenuRes)
return FALSE;

hMenu=CreatePopupMenu();
cItems=GetMenuItemCount(hMenuRes);

for (i=0; i < cItems; i++)
{
TCHAR szTemp[80];
int id, uFlags;

GetMenuString(hMenuRes, i, szTemp, sizeof(szTemp)
, MF_BYPOSITION);
id=GetMenuItemID(hMenuRes, i);

uFlags=(0==id) ? MF_SEPARATOR : MF_STRING | MF_ENABLED;
AppendMenu(hMenu, uFlags, id, szTemp);
}

DestroyMenu(hMenuRes);

//Munge the Object menu item
m_pTenantCur->AddVerbMenu(hMenu, MENUPOS_OBJECTONPOPUP);

//CHAPTER20MOD
//Enable or disable the Links item.
i=FQueryLinksInPage() ? MF_ENABLED : MF_DISABLED | MF_GRAYED;
EnableMenuItem(hMenu, IDM_EDITLINKS, i | MF_BYCOMMAND);
//End CHAPTER20MOD

SETPOINT(pt, x, y);
ClientToScreen(m_hWnd, &pt);

TrackPopupMenu(hMenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON
, pt.x, pt.y, 0, hWndFrame, NULL);

DestroyMenu(hMenu);
return FALSE;
}




/*
* CPage::SelectTenantAtPoint
*
* Purpose:
* Selects whatever tenant is at the point (x,y) if there is one,
* deselecting the previously selected tenant.
*
* Parameters:
* x, y UINT coordinates of the mouse.
*
* Return Value:
* BOOL TRUE if there is a tenant here, FALSE otherwise.
*/

BOOL CPage::SelectTenantAtPoint(UINT x, UINT y)
{
UINT iTenant;
PCTenant pTenant;
PCDocument pDoc;

iTenant=TenantFromPoint(x, y, &pTenant);

if (NULL==pTenant)
return FALSE;

//Make the document window active in any case
pDoc=(PCDocument)SendMessage(GetParent(m_hWnd), DOCM_PDOCUMENT
, 0, 0L);

if (NULL!=pDoc)
BringWindowToTop(pDoc->Window());

//If this one is already current, we might be now sizing.
if (pTenant==m_pTenantCur)
return TRUE;

//Deselect the current tenant
if (NULL!=m_pTenantCur)
m_pTenantCur->Select(FALSE);

//Move this tenant to the top of the list
m_iTenantCur=0;

SendMessage(m_hWndTenantList, LB_DELETESTRING, iTenant, 0L);
SendMessage(m_hWndTenantList, LB_INSERTSTRING, 0
, (LONG)pTenant);

//Select and repaint the new tenant to show it up front
m_pTenantCur=pTenant;

m_pTenantCur->Repaint();
m_pTenantCur->Select(TRUE);

return TRUE;
}





/*
* CPage::OnLeftDown
*
* Purpose:
* Called when the user clicks with the left button on this page.
* We find the object under that position that is visibly on top
* (always the first one under this location in the page list since
* we paint in reverse order) and select it.
*
* Parameters:
* uKeys UINT carrying the key state.
* x, y UINT coordinates of the click in device units.
*
* Return Value:
* BOOL Indicates if the action changed the object.
*/

BOOL CPage::OnLeftDown(UINT uKeys, UINT x, UINT y)
{
/*
* If the mouse is in a position to start dragging,
* start the timer as with sizing below.
*/
if (HTCAPTION==m_uHTCode)
{
m_fDragPending=TRUE;

//Save down point and start timer.
m_ptDown.x=x;
m_ptDown.y=y;

m_uKeysDown=uKeys;

m_fTimer=TRUE;
SetTimer(m_hWnd, IDTIMER_DEBOUNCE, m_cDelay, NULL);
return FALSE;
}

/*
* If the mouse is in a position to start sizing, start
* the debounce timer and note the condition. The sizing
* will start in OnTimer or OnMouseMove. This will always
* happen on the currently selected tenant, and m_uHTCode is
* set in OnNCHitTest below.
*/
if (HTNOWHERE!=m_uHTCode && HTCLIENT!=m_uHTCode)
{
m_fSizePending=TRUE;

//Save down point and start timer.
m_ptDown.x=x;
m_ptDown.y=y;

m_fTimer=TRUE;
SetTimer(m_hWnd, IDTIMER_DEBOUNCE, m_cDelay, NULL);
return FALSE;
}

SelectTenantAtPoint(x, y);
return FALSE;
}






/*
* CPage::OnLeftUp
*
* Purpose:
* Called when the user clicks up with the left button on this
* page. We stop tracking on this message, if necessary, and
* resize the object.
*
* Parameters:
* uKeys UINT carrying the key state.
* x, y UINT coordinates of the click in device units.
*
* Return Value:
* BOOL Indicates if this action changed the object.
*/

BOOL CPage::OnLeftUp(UINT uKeys, UINT x, UINT y)
{
RECT rc, rcT;

if (m_fSizePending || m_fDragPending)
{
m_fSizePending=FALSE;
m_fDragPending=FALSE;

if (m_fTimer)
{
KillTimer(m_hWnd, IDTIMER_DEBOUNCE);
m_fTimer=FALSE;
}

return FALSE;
}

if (!m_fTracking)
return FALSE;

//Remove the dotted rectangle.
RECTFROMRECTL(rc, m_rcl)
DrawFocusRect(m_hDC, &rc);
ReleaseDC(m_hWnd, m_hDC);

ReleaseCapture();
m_fTracking=FALSE;

//If the original and new rects are the same, nothing happened.
RECTFROMRECTL(rcT, m_rclOrg);

if (EqualRect(&rc, &rcT))
return FALSE;

RECTFROMRECTL(rcT, m_rclOrg);
InvalidateRect(m_hWnd, &rcT, TRUE);

//Invalidate on the screen before accounting for scrolling
InvalidateRect(m_hWnd, &rc, TRUE);

//Factor in scrolling and tell the tenant where it now stands.
OffsetRect(&rc, (int)m_pPG->m_xPos, (int)m_pPG->m_yPos);
RECTLFROMRECT(m_rcl, rc);
m_pTenantCur->RectSet(&m_rcl, TRUE, TRUE);

UpdateWindow(m_hWnd);
return TRUE;
}





/*
* CPage::OnLeftDoubleClick
*
* Purpose:
* Called when the user double-clicks with the left button on this
* page. We find the object under that position that is visibly on
* top (always the first one under this location in the page list
* since we paint in reverse order) and activate it.
*
* Parameters:
* uKeys UINT carrying the key state.
* x, y UINT coordinates of the click in device units.
*
* Return Value:
* BOOL Indicates if the action changed the object.
*/

BOOL CPage::OnLeftDoubleClick(UINT uKeys, UINT x, UINT y)
{
/*
* The current tenant is the only one that can be activated, so
* we just have to make sure the mouse is there. For that we
* can use the last hit-test code we saw since it's updated on
* every mouse move.
*/

if (HTNOWHERE!=m_uHTCode)
return m_pTenantCur->Activate(OLEIVERB_PRIMARY);

return FALSE;
}






/*
* CPage::OnMouseMove
*
* Purpose:
* Processes WM_MOUSEMOVE on a page so we can handle tracking
* resize of a tenant.
*
* Parameters:
* x, y int device coordinates to check.
*
* Return Value:
* None
*/

void CPage::OnMouseMove(UINT uKeys, int x, int y)
{
RECT rc, rcO, rcB;
int cxy;

if (m_fSizePending || m_fDragPending)
{
int dx, dy;

dx=(x > m_ptDown.x) ? (x-m_ptDown.x) : (m_ptDown.x-x);
dy=(y > m_ptDown.y) ? (y-m_ptDown.y) : (m_ptDown.y-y);

/*
* Has the mouse moved outside the debounce distance? If
* so, we can start sizing. Note that this happens
* regardless of the timer state.
*/
if (dx > m_cxyDist || dy > m_cxyDist)
{
POINT pt;
BOOL fSize=m_fSizePending;
BOOL fDrag=m_fDragPending;

m_fSizePending=FALSE;
m_fDragPending=FALSE;

if (m_fTimer)
{
KillTimer(m_hWnd, IDTIMER_DEBOUNCE);
m_fTimer=FALSE;
}

if (fDrag)
{
//Set dirty flag if drag & drop changed things.
m_pPG->m_fDirty |= DragDrop(m_uKeysDown, x, y);
return;
}

if (fSize)
StartSizeTracking();

/*
* Since we might have moved out of the sizing handle
* in order to start the operation, we need to set the
* m_uSizingFlags field based on the original down point
* for subsequent mouse moves to function properly.
* Note that OnNCHitTest expects screen coordinates.
*/
SETPOINT(pt, m_ptDown.x, m_ptDown.y);
ClientToScreen(m_hWnd, &pt);
OnNCHitTest(pt.x, pt.y);
OnSetCursor(m_uHTCode);
return;
}
}

if (!m_fTracking)
return;

//Get rid of the old rectangle.
RECTFROMRECTL(rc, m_rcl)
DrawFocusRect(m_hDC, &rc);

/*
* Calculate the new. The flags in m_uSizingFlags tell us what
* to change. We limit the object by the page margins and a
* minimum size of 3*CXYHANDLE in either dimension.
*/
cxy=3*CXYHANDLE;

RECTFROMRECTL(rcO, m_rclOrg);
RECTFROMRECTL(rcB, m_rclBounds);

if (m_uSizingFlags & SIZINGTOP)
{
if (y >= rcO.bottom-cxy)
y=rcO.bottom-cxy;

if (y <= rcB.top) //Limit to top of page.
y=rcB.top;

m_rcl.top=y;
}

if (m_uSizingFlags & SIZINGBOTTOM)
{
if (y <= rcO.top+cxy)
y=rcO.top+cxy;

if (y >= rcB.bottom) //Limit to bottom of page.
y=rcB.bottom;

m_rcl.bottom=y;
}

if (m_uSizingFlags & SIZINGLEFT)
{
if (x >= rcO.right-cxy)
x=rcO.right-cxy;

if (x <= rcB.left) //Limit to left of page.
x=rcB.left;

m_rcl.left=x;
}

if (m_uSizingFlags & SIZINGRIGHT)
{
if (x <= rcO.left+cxy)
x=rcO.left+cxy;

if (x >= rcB.right) //Limit to right of page.
x=rcB.right;

m_rcl.right=x;
}


//Draw the new
RECTFROMRECTL(rc, m_rcl)
DrawFocusRect(m_hDC, &rc);

return;
}




/*
* CPage::OnTimer
*
* Purpose:
* Processes WM_TIMER messages to a page used to perform mouse
* debouncing.
*
* Parameters:
* uID UINT timer ID.
*
* Return Value:
* None
*/

void CPage::OnTimer(UINT uID)
{
if (m_fSizePending || m_fDragPending)
{
BOOL fSize=m_fSizePending;
BOOL fDrag=m_fDragPending;

/*
* Having this function called means the delay requirement
* is satisfied. Start tracking for sizing or dragging.
*/

m_fSizePending=FALSE;
m_fDragPending=FALSE;

KillTimer(m_hWnd, IDTIMER_DEBOUNCE);
m_fTimer=FALSE;

if (fDrag)
{
POINT pt;

GetCursorPos(&pt);
m_pPG->m_fDirty |= DragDrop(m_uKeysDown
, m_ptDown.x, m_ptDown.y);
return;
}

if (fSize)
StartSizeTracking();
}

return;
}





/*
* CPage::StartSizeTracking
*
* Purpose:
* Begins sizing of a tenant when mouse debounce conditions are
* met.
*
* Parameters:
* uID UINT timer ID.
*
* Return Value:
* None
*/

void CPage::StartSizeTracking(void)
{
RECT rc;

m_pTenantCur->RectGet(&m_rcl, TRUE);
SetCapture(m_hWnd);
m_fTracking=TRUE;

m_hDC=GetDC(m_hWnd);

//Place the rectangle exactly where it is on the screen.
RECTFROMRECTL(rc, m_rcl)
OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);
RECTLFROMRECT(m_rcl, rc);
m_rclOrg=m_rcl;

DrawFocusRect(m_hDC, &rc);

m_pPG->CalcBoundingRect(&rc, TRUE);
RECTLFROMRECT(m_rclBounds, rc);
return;
}





/*
* CPage::OnNCHitTest
*
* Purpose:
* Processes WM_NCHITTEST on a page so we can check for hits on the
* handles of the selected object for resizing. We only save
* information for ourselves and do not interfere with normal
* hit-testing.
*
* Parameters:
* x, y UINT device coordinates to check.
*
* Return Value:
* None
*/

void CPage::OnNCHitTest(UINT x, UINT y)
{
RECT rc;
RECTL rcl;
int iMid1, iMid2;
int xHit, yHit;
POINT pt;
int x0, y0;

/*
* Ignore this message if it occurs during tracking to adjust
* for the behavior of oddball mouse drivers.
*/
if (m_fSizePending || m_fTracking)
return;

//Default: don't start sizing on a click, don't hit an object.
m_uSizingFlags=0;
m_uHTCode=HTNOWHERE;

if (NULL==m_pTenantCur)
return;

//Convert device points to our coordinates
m_pTenantCur->RectGet(&rcl, FALSE);
RECTFROMRECTL(rc, rcl);
RectConvertMappings(&rc, NULL, TRUE);
OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);

SETPOINT(pt, x, y);
ScreenToClient(m_hWnd, &pt);
x0=pt.x;
y0=pt.y;

if (x0 < rc.left || x0 > rc.right)
return;

if (y0 < rc.top || y0 > rc.bottom)
return;

//It's at least in the object.
m_uHTCode=HTCLIENT;

//Check for hits in horizontal regions
xHit=NOVALUE;
iMid1=rc.left+((rc.right-rc.left-CXYHANDLE) >> 1);
iMid2=rc.left+((rc.right-rc.left+CXYHANDLE) >> 1);

if (x0 >= rc.left && x0 <= rc.left+CXYHANDLE)
xHit=XLEFT;
else if (x0 >= iMid1 && x0 <= iMid2)
xHit=XMID;
else if (x0 >= rc.right-CXYHANDLE && x0 <= rc.right)
xHit=XRIGHT;

//Don't exit yet if we didn't hit a handle--might hit a y edge.

//Check for hits in vertical regions
yHit=NOVALUE;
iMid1=rc.top+((rc.bottom-rc.top-CXYHANDLE) >> 1);
iMid2=rc.top+((rc.bottom-rc.top+CXYHANDLE) >> 1);

if (y0 >= rc.top && y0 <= rc.top+CXYHANDLE)
yHit=YTOP;
else if (y0 >= iMid1 && y0 <= iMid2)
yHit=YMID;
else if (y0 >= rc.bottom-CXYHANDLE && y0 <= rc.bottom)
yHit=YBOT;

/*
* If we hit any edge, but didn't hit a handle, then one of xHit
* and yHit will be NOVALUE and the other something else. When
* we hit an edge on the 'something else' then we're on a drag
* point.
*/

if ((NOVALUE==xHit && NOVALUE==yHit)
|| (XMID==xHit && YMID==yHit)
|| (NOVALUE==xHit && YMID==yHit)
|| (XMID==xHit && NOVALUE==yHit))
return;

if ((NOVALUE==xHit && (YTOP==yHit || YBOT==yHit))
|| ((XLEFT==xHit || XRIGHT==xHit) && NOVALUE==yHit))
{
m_uHTCode=HTCAPTION;
return;
}

//We hit a handle, so save our HT code
m_uSizingFlags=g_rguSizingFlags[xHit+(yHit*3)];
m_uHTCode=g_rgHTCode[xHit+(yHit*3)];
return;
}





/*
* CPage::SetCursor
*
* Purpose:
* Processes WM_SETCURSOR using the code from OnNCHitTest.
*
* Parameters:
* x, y UINT device coordinates to check.
*
* Return Value:
* LRESULT HT* code for Windows.
*/

BOOL CPage::OnSetCursor(UINT uHTCode)
{
HCURSOR hCur;
UINT iCur;

/*
* We really just ignore uHTCode and use the one we saved
* in OnNCHitTest.
*/

switch (m_uHTCode)
{
case HTTOP:
case HTBOTTOM:
iCur=IDC_VARROWS;
break;

case HTLEFT:
case HTRIGHT:
iCur=IDC_HARROWS;
break;


case HTTOPLEFT:
case HTBOTTOMRIGHT:
iCur=IDC_NWSEARROWS;
break;

case HTTOPRIGHT:
case HTBOTTOMLEFT:
iCur=IDC_NESWARROWS;
break;

case HTCAPTION:
iCur=IDC_SMALLARROWS;
break;

default:
return FALSE;
}

hCur=UICursorLoad(iCur);
SetCursor(hCur);

return TRUE;
}





/*
* CPage::TenantFromPoint
* (Protected)
*
* Purpose:
* Finds the tenant under the given device coordinates on this
* page.
*
* Parmeters:
* x, y UINT coordinates.
* ppTenant PCTenant * in which to return the pointer.
*
* Return Value:
* UINT Index of the matched tenant, NOVALUE if not
* found.
*/

UINT CPage::TenantFromPoint(UINT x, UINT y, PCTenant *ppTenant)
{
PCTenant pTenant;
RECTL rcl;
UINT i;
int x0, y0;

x0=x+m_pPG->m_xPos;
y0=y+m_pPG->m_yPos;

for (i=0; i < m_cTenants; i++)
{
if (!TenantGet(i, &pTenant, FALSE))
continue;

pTenant->RectGet(&rcl, TRUE);

//Essentially Perform PointInRECTL
if (x0 >= rcl.left && x0 <= rcl.right)
{
if (y0 <=rcl.bottom && y0 >=rcl.top)
{
*ppTenant=pTenant;
return i;
}
}
}

*ppTenant=NULL;
return NOVALUE;
}







/*
* CPage::DragDrop
*
* Purpose:
* Performs drag-drop operations from the page window
*
* Parmeters:
* uKeys UINT state of the keyboard
* x, y UINT mouse coordinates of the starting click.
*
* Return Value:
* BOOL TRUE if we modified the page, FALSE otherwise.
*/

BOOL CPage::DragDrop(UINT uKeys, UINT x, UINT y)
{
LPDROPSOURCE pIDropSource;
LPDATAOBJECT pIDataObject;
HRESULT hr;
DWORD dwEffect;
POINTL ptl;
SIZEL szl;
RECTL rcl;
RECT rc, rcT;

pIDropSource=new CDropSource();

if (NULL==pIDropSource)
return FALSE;

pIDropSource->AddRef();
m_pPG->m_fDragSource=TRUE;


/*
* Store a pick point with the data indicating the offset from
* the upper left of the rectangle where we grabbed it. This is
* so the UI feedback in IDropTarget lines up with this tenant.
*/

m_pTenantCur->RectGet(&rcl, TRUE);
ptl.x=x+m_pPG->m_xPos-rcl.left;
ptl.y=y+m_pPG->m_yPos-rcl.top;
pIDataObject=TransferObjectCreate(&ptl);

if (NULL==pIDataObject)
{
pIDropSource->Release();
return FALSE;
}

m_pPG->m_fMoveInPage=FALSE;

dwEffect=DROPEFFECT_COPY | DROPEFFECT_MOVE;
hr=DoDragDrop(pIDataObject, pIDropSource
, DROPEFFECT_COPY | DROPEFFECT_MOVE, &dwEffect);

pIDataObject->Release();
pIDropSource->Release();

m_pPG->m_fDragSource=FALSE;

//No drop-no action.
if (DRAGDROP_S_DROP!=GetScode(hr) || DROPEFFECT_NONE==dwEffect)
return FALSE;

/*
* If m_pPG->m_fMoveInPage is set, then we just change the
* coordinates on m_pTenantCur and we're done.
*/
if (m_pPG->m_fMoveInPage)
{
m_pTenantCur->Invalidate();

/*
* Clip to page boundaries. We know that ptDrop has to be
* in the page somewhere or we would not have dropped
* (effect was NONE). So first make sure that ptDrop is
* within 3*CXYHANDLE of the right or bottom, and if so,
* pull it out to 3*CXYHANDLE. Then we can just clip the
* size to the page rectangle and we'll always be sure to
* have at least a sizeable object.
*/
m_pTenantCur->SizeGet(&szl, TRUE);
SetRect(&rc, (int)m_pPG->m_ptDrop.x, (int)m_pPG->m_ptDrop.y
, 0, 0);
RectConvertMappings(&rc, NULL, TRUE);

m_pPG->CalcBoundingRect(&rcT, FALSE);
OffsetRect(&rcT, (int)m_pPG->m_xPos, (int)m_pPG->m_yPos);

if (rc.left >= rcT.right-3*CXYHANDLE)
rc.left=rcT.right-3*CXYHANDLE;

if (rc.top >= rcT.bottom-3*CXYHANDLE)
rc.top=rcT.bottom-3*CXYHANDLE;

rc.right=rc.left+(int)szl.cx;
rc.bottom=rc.top+(int)szl.cy;
IntersectRect(&rc, &rc, &rcT);

RECTLFROMRECT(rcl, rc);

m_pTenantCur->RectSet(&rcl, TRUE, FALSE);
m_pTenantCur->Repaint();
return TRUE;
}

/*
* Otherwise we may have to delete the old tenant if the effect
* was move. This will not happen in the move in page case.
*/

if (DROPEFFECT_MOVE==dwEffect)
{
TenantDestroy();
return TRUE;
}

//Copy is a clean operation
return FALSE;
}