Windows2000/private/shell/win16/commctrl/tbcust.c
2020-09-30 17:12:32 +02:00

1412 lines
43 KiB
C

#include "ctlspriv.h"
#ifdef IEWIN31_25
#include "toolbar2.h"
#else
#include "toolbar.h"
#endif
#include "..\inc\help.h" // Help IDs
#ifdef WIN32
#define SEND_WM_COMMAND(hwnd, id, hwndCtl, codeNotify) \
(void)SendMessage((hwnd), WM_COMMAND, MAKEWPARAM((UINT)(id),(UINT)(codeNotify)), (LPARAM)(HWND)(hwndCtl))
#else
// dont cast result to void since we depend on this hack to get a handle back
// from some WM_COMMAND messages
#define SEND_WM_COMMAND(hwnd, id, hwndCtl, codeNotify) \
SendMessage((hwnd), WM_COMMAND, (WPARAM)(int)(id), MAKELPARAM((UINT)(hwndCtl), (codeNotify)))
#endif
void FAR PASCAL FlushButtonCache(PTBSTATE ptb);
#define SPACESTRLEN 20
#define FLAG_NODEL 0x8000
#define FLAG_HIDDEN 0x4000
#define FLAG_SEP 0x2000
#define FLAG_ALLFLAGS (FLAG_NODEL|FLAG_HIDDEN|FLAG_SEP)
typedef struct { /* instance data for toolbar edit dialog */
HWND hDlg; /* dialog hwnd */
PTBSTATE ptb; // current toolbar state
int iPos; /* position to insert into */
} ADJUSTDLGDATA, FAR *LPADJUSTDLGDATA;
int g_dyButtonHack = 0; // to pass on before WM_INITDIALOG
int NEAR PASCAL GetPrevButton(PTBSTATE ptb, int iPos)
{
/* This means to delete the preceding space
*/
for (--iPos; ; --iPos)
{
if (iPos < 0)
break;
if (!(ptb->Buttons[iPos].fsState & TBSTATE_HIDDEN))
break;;
}
return(iPos);
}
BOOL NEAR PASCAL GetAdjustInfo(PTBSTATE ptb, int iItem, LPTBBUTTON ptbButton, LPSTR lpString, int cbString)
{
#ifdef WIN32
TBNOTIFY tbn;
tbn.pszText = lpString;
tbn.cchText = cbString;
tbn.iItem = iItem;
if (lpString)
*lpString = 0;
if ((BOOL)SendNotify(ptb->hwndCommand, ptb->hwnd, TBN_GETBUTTONINFO, &tbn.hdr))
{
*ptbButton = tbn.tbButton;
return TRUE;
}
return FALSE;
#else
LPADJUSTINFO lpai;
// for compatibility with old users of we do this bogus stuff with WM_COMMAND
HGLOBAL hInfo = (HANDLE)(int)SEND_WM_COMMAND(ptb->hwndCommand, GetWindowID(ptb->hwnd), iItem, TBN_ADJUSTINFO);
if (!hInfo)
return FALSE;
// param validation...
lpai = (LPADJUSTINFO)GlobalLock(hInfo);
if (lpai)
{
TBInputStruct(ptb, ptbButton, &lpai->tbButton);
if (lpString)
lstrcpyn(lpString, (LPSTR)lpai + ptb->uStructSize, cbString);
GlobalUnlock(hInfo);
return TRUE;
}
return FALSE;
#endif
}
BOOL FAR PASCAL SendItemNotify(PTBSTATE ptb, int iItem, int code)
{
#ifdef WIN32
TBNOTIFY tbn;
tbn.iItem = iItem;
// default return from SendNotify is false
return SendNotify(ptb->hwndCommand, ptb->hwnd, code, &tbn.hdr);
#else
// for compatibility with old users of we do this bogus stuff with WM_COMMAND
// stuffing in an item index in a field that should be an hwnd
return SEND_WM_COMMAND(ptb->hwndCommand, GetWindowID(ptb->hwnd), (HWND)iItem, code);
#endif
}
void NEAR PASCAL SendCmdNotify(PTBSTATE ptb, int code)
{
#ifdef WIN32
NMHDR hdr;
SendNotify(ptb->hwndCommand, ptb->hwnd, code, &hdr);
#else
SEND_WM_COMMAND(ptb->hwndCommand, GetWindowID(ptb->hwnd), ptb->hwnd, code);
#endif
}
// this is used to deal with the case where the ptb structure is re-alloced
// after a InsertButtons()
PTBSTATE NEAR PASCAL FixPTB(HWND hwnd)
{
PTBSTATE ptb = (PTBSTATE)GetWindowInt(hwnd, 0);
if (ptb->hdlgCust)
{
LPADJUSTDLGDATA lpad = (LPADJUSTDLGDATA)GetWindowLong(ptb->hdlgCust, DWL_USER);
#ifdef DEBUG
if (lpad->ptb != ptb)
DebugMsg(DM_TRACE, "Fixing busted ptb pointer");
#endif
lpad->ptb = ptb;
}
return ptb;
}
void FAR PASCAL MoveButton(PTBSTATE ptb, int nSource)
{
int nDest;
RECT rc;
HCURSOR hCursor;
MSG32 msg32;
/* You can't move separators like this
*/
if (nSource < 0)
return;
// Make sure it is all right to "delete" the selected button
if (!SendItemNotify(ptb, nSource, TBN_QUERYDELETE))
return;
hCursor = SetCursor(LoadCursor(HINST_THISDLL, MAKEINTRESOURCE(IDC_MOVEBUTTON)));
SetCapture(ptb->hwnd);
// Get the dimension of the window.
GetClientRect(ptb->hwnd, &rc);
for (; ; )
{
while (!PeekMessage32(&msg32, NULL, 0, 0, PM_REMOVE, TRUE))
;
if (GetCapture() != ptb->hwnd)
goto AbortMove;
// See if the application wants to process the message...
if (CallMsgFilter32(&msg32, MSGF_COMMCTRL_TOOLBARCUST, TRUE) != 0)
continue;
switch (msg32.message)
{
case WM_KEYDOWN:
case WM_KEYUP:
case WM_CHAR:
break;
case WM_LBUTTONUP:
RelayToToolTips(ptb->hwndToolTips, ptb->hwnd, msg32.message, msg32.wParam, msg32.lParam);
if (((short)HIWORD(msg32.lParam) > (short)(rc.bottom + ptb->iButWidth)) ||
((short)LOWORD(msg32.lParam) > (rc.right + ptb->iButWidth)) ||
((short)HIWORD(msg32.lParam) < -ptb->iButHeight / 2) ||
((short)LOWORD(msg32.lParam) < -ptb->iButWidth / 2))
{
/* If the button was dragged off the toolbar, delete it.
*/
DeleteSrcButton:
DeleteButton(ptb, nSource);
SendCmdNotify(ptb, TBN_TOOLBARCHANGE);
FlushToolTipsMgr(ptb);
}
else
{
TBBUTTON tbbAdd;
/* Add half a button to X so that it looks like it is centered
* over the target button, iff we have a horizontal layout.
* Add half a button to Y otherwise.
*/
if (rc.right != ptb->iButWidth)
nDest = TBHitTest(ptb,
(short)LOWORD(msg32.lParam) + ptb->iButWidth / 2,
(short)HIWORD(msg32.lParam));
else
nDest = TBHitTest(ptb,
(short)LOWORD(msg32.lParam),
(short)HIWORD(msg32.lParam) + ptb->iButHeight / 2);
if (nDest < 0)
nDest = -1 - nDest;
if (nDest > 0 &&
(ptb->Buttons[nDest - 1].fsState & TBSTATE_WRAP) &&
(short)LOWORD(msg32.lParam) > ptb->iButWidth &&
SendItemNotify(ptb, --nDest, TBN_QUERYINSERT))
{
tbbAdd = ptb->Buttons[nSource];
DeleteButton(ptb, nSource);
if (nDest > nSource)
{
--nDest;
}
/* Insert befor spaces, but after buttons. */
if (!(ptb->Buttons[nDest].fsStyle & TBSTYLE_SEP))
nDest++;
goto InsertSrcButton;
}
else if (nDest == nSource)
{
/* This means to delete the preceding space, or to move a
button to the previous row.
*/
nSource = GetPrevButton(ptb, nSource);
if (nSource < 0)
goto AbortMove;
// If the preceding item is a space with no ID, and
// the app says it's OK, then delete it.
if ((ptb->Buttons[nSource].fsStyle & TBSTYLE_SEP)
&& !ptb->Buttons[nSource].idCommand
&& SendItemNotify(ptb, nSource, TBN_QUERYDELETE))
goto DeleteSrcButton;
}
else if (nDest == nSource + 1)
{
// This means to add a preceding space
--nDest;
if (SendItemNotify(ptb, nDest, TBN_QUERYINSERT))
{
tbbAdd.iBitmap = 0;
tbbAdd.idCommand = 0;
tbbAdd.iString = -1;
tbbAdd.fsState = 0;
tbbAdd.fsStyle = TBSTYLE_SEP;
goto InsertSrcButton;
}
}
else if (SendItemNotify(ptb, nDest, TBN_QUERYINSERT))
{
HWND hwndT;
/* This is a normal move operation
*/
tbbAdd = ptb->Buttons[nSource];
DeleteButton(ptb, nSource);
if (nDest > nSource)
--nDest;
InsertSrcButton:
hwndT = ptb->hwnd;
InsertButtons(ptb, nDest, 1, &tbbAdd);
ptb = FixPTB(hwndT);
SendCmdNotify(ptb, TBN_TOOLBARCHANGE);
FlushToolTipsMgr(ptb);
}
else
{
AbortMove:
;
}
}
goto AllDone;
case WM_RBUTTONDOWN:
goto AbortMove;
default:
TranslateMessage32(&msg32, TRUE);
DispatchMessage32(&msg32, TRUE);
break;
}
}
AllDone:
SetCursor(hCursor);
ReleaseCapture();
}
#define GNI_HIGH 0x0001
#define GNI_LOW 0x0002
int NEAR PASCAL GetNearestInsert(PTBSTATE ptb, int iPos, int iNumButtons, UINT uFlags)
{
int i;
BOOL bKeepTrying;
/* Find the nearest index where we can actually insert items
*/
for (i = iPos; ; ++i, --iPos)
{
bKeepTrying = FALSE;
/* Notice we favor going high if both flags are set
*/
if ((uFlags & GNI_HIGH) && i <= iNumButtons)
{
bKeepTrying = TRUE;
if (SendItemNotify(ptb, i, TBN_QUERYINSERT))
return(i);
}
if ((uFlags & GNI_LOW) && iPos >= 0)
{
bKeepTrying = TRUE;
if (SendItemNotify(ptb, i, TBN_QUERYINSERT))
return(iPos);
}
if (!bKeepTrying)
return(-1); /* There was no place to add buttons. */
}
}
BOOL NEAR PASCAL InitAdjustDlg(HWND hDlg, LPADJUSTDLGDATA lpad)
{
HDC hDC;
HFONT hFont;
HWND hwndCurrent, hwndNew;
PTBBUTTON ptbButton;
int i, iPos, nItem, nWid, nMaxWid;
TBBUTTON tbAdjust;
char szDesc[128];
lpad->hDlg = hDlg;
lpad->ptb->hdlgCust = hDlg;
/* Determine the item nearest the desired item that will allow
* insertion.
*/
iPos = GetNearestInsert(lpad->ptb, lpad->iPos, lpad->ptb->iNumButtons,
GNI_HIGH | GNI_LOW);
if (iPos < 0)
/* No item allowed insertion, so leave the dialog */
{
return(FALSE);
}
/* Reset the lists of used and available items.
*/
hwndCurrent = GetDlgItem(hDlg, IDC_CURRENT);
SendMessage(hwndCurrent, LB_RESETCONTENT, 0, 0L);
hwndNew = GetDlgItem(hDlg, IDC_BUTTONLIST);
SendMessage(hwndNew, LB_RESETCONTENT, 0, 0L);
for (i = 0, ptbButton = lpad->ptb->Buttons; i < lpad->ptb->iNumButtons; ++i, ++ptbButton)
{
UINT uFlags;
int iBitmap;
uFlags = 0;
// Non-deletable and hidden items show up grayed.
if (!SendItemNotify(lpad->ptb, i, TBN_QUERYDELETE))
{
uFlags |= FLAG_NODEL;
}
if (ptbButton->fsState & TBSTATE_HIDDEN)
{
uFlags |= FLAG_HIDDEN;
}
/* Separators have no bitmaps (even ones with IDs). Only set
* the separator flag if there is no ID (it is a "real"
* separator rather than an owner item).
*/
if (ptbButton->fsStyle&TBSTYLE_SEP)
{
if (!(ptbButton->idCommand))
{
uFlags |= FLAG_SEP;
}
iBitmap = -1;
}
else
{
iBitmap = ptbButton->iBitmap;
}
/* Add the item and the data
* Note: A negative number in the LOWORD indicates no bitmap;
* otherwise it is the bitmap index.
*/
if ((int)SendMessage(hwndCurrent, LB_ADDSTRING, 0, (LPARAM)(LPSTR)c_szNULL) != i)
{
return(FALSE);
}
SendMessage(hwndCurrent, LB_SETITEMDATA, i, MAKELPARAM(iBitmap, uFlags));
}
/* Add a dummy "nodel" space at the end so things can be inserted at the end.
*/
if ((int)SendMessage(hwndCurrent, LB_ADDSTRING, 0, (LPARAM)(LPSTR)c_szNULL) == i)
{
SendMessage(hwndCurrent, LB_SETITEMDATA, i, MAKELPARAM(-1, FLAG_NODEL | FLAG_SEP));
}
/* Now add a space at the beginning of the "new" list.
*/
if (SendMessage(hwndNew, LB_ADDSTRING, 0, (LPARAM)(LPSTR)c_szNULL) == LB_ERR)
{
return(FALSE);
}
SendMessage(hwndNew, LB_SETITEMDATA, 0, MAKELPARAM(-1, FLAG_SEP));
/* We need this to determine the widest (in pixels) item string.
*/
hDC = GetDC(hwndCurrent);
hFont = (HFONT)(int)SendMessage(hwndCurrent, WM_GETFONT, 0, 0L);
if (hFont)
{
hFont = SelectObject(hDC, hFont);
}
nMaxWid = 0;
for (i = 0; ; ++i)
{
// Get the info about the i'th item from the app.
if (!GetAdjustInfo(lpad->ptb, i, &tbAdjust, szDesc, sizeof(szDesc)))
break;
/* Don't show separators that don't have commands
*/
if (!(tbAdjust.fsStyle & TBSTYLE_SEP) || tbAdjust.idCommand)
{
/* Get the maximum width of a string.
*/
MGetTextExtent(hDC, szDesc, lstrlen(szDesc), &nWid, NULL);
if (nMaxWid < nWid)
{
nMaxWid = nWid;
}
nItem = PositionFromID(lpad->ptb, tbAdjust.idCommand);
if (nItem < 0)
/* If the item is not on the toolbar already */
{
/* Don't show hidden buttons
*/
if (!(tbAdjust.fsState & TBSTATE_HIDDEN))
{
nItem = (int)SendMessage(hwndNew, LB_ADDSTRING, 0,
(LPARAM)(LPSTR)szDesc);
if (nItem != LB_ERR)
{
SendMessage(hwndNew, LB_SETITEMDATA, nItem,
MAKELPARAM(tbAdjust.fsStyle & TBSTYLE_SEP
? -1 : tbAdjust.iBitmap, i));
}
}
}
else
/* The item is on the toolbar already */
{
/* Preserve the flags and bitmap.
*/
DWORD dwTemp = SendMessage(hwndCurrent, LB_GETITEMDATA, nItem, 0L);
SendMessage(hwndCurrent, LB_DELETESTRING, nItem, 0L);
if ((int)SendMessage(hwndCurrent, LB_INSERTSTRING, nItem,
(LPARAM)(LPSTR)szDesc) != nItem)
{
ReleaseDC(hwndCurrent, hDC);
return(FALSE);
}
SendMessage(hwndCurrent, LB_SETITEMDATA, nItem,
MAKELPARAM(LOWORD(dwTemp), HIWORD(dwTemp) | i));
}
}
}
if (hFont)
{
SelectObject(hDC, hFont);
}
ReleaseDC(hwndCurrent, hDC);
/* Add on some extra and set the extents for both lists.
*/
nMaxWid += lpad->ptb->iButWidth + 2 + 1;
SendMessage(hwndNew, LB_SETHORIZONTALEXTENT, nMaxWid, 0L);
SendMessage(hwndCurrent, LB_SETHORIZONTALEXTENT, nMaxWid, 0L);
/* Set the sels and return.
*/
SendMessage(hwndNew, LB_SETCURSEL, 0, 0L);
SendMessage(hwndCurrent, LB_SETCURSEL, iPos, 0L);
SEND_WM_COMMAND(hDlg, IDC_CURRENT, hwndCurrent, LBN_SELCHANGE);
return(TRUE);
}
#define IsSeparator(x) (HIWORD(x) & FLAG_SEP)
void NEAR PASCAL PaintAdjustLine(PTBSTATE ptb, DRAWITEMSTRUCT FAR *lpdis)
{
HDC hdc = lpdis->hDC;
HWND hwndList = lpdis->hwndItem;
PSTR pszText;
RECT rc = lpdis->rcItem;
int nBitmap, nLen, nItem = lpdis->itemID;
COLORREF oldBkColor, oldTextColor;
BOOL bSelected, bHasFocus;
int wHeight;
if (lpdis->CtlID != IDC_BUTTONLIST && lpdis->CtlID != IDC_CURRENT)
return;
nBitmap = LOWORD(lpdis->itemData);
if (IsSeparator(lpdis->itemData))
nLen = SPACESTRLEN;
else
{
nLen = (int)SendMessage(hwndList, LB_GETTEXTLEN, nItem, 0L);
if (nLen < 0)
return;
}
pszText = (PSTR)LocalAlloc(LPTR, nLen + 1);
if (!pszText)
return;
if (IsSeparator(lpdis->itemData))
nLen = LoadString(HINST_THISDLL, IDS_SPACE, pszText, SPACESTRLEN);
else
SendMessage(hwndList, LB_GETTEXT, nItem, (LPARAM)(LPSTR)pszText);
if (lpdis->itemAction != ODA_FOCUS)
{
COLORREF clr;
char szSample[2];
/* We don't care about focus if the item is not selected.
*/
bSelected = lpdis->itemState & ODS_SELECTED;
bHasFocus = bSelected && (GetFocus() == hwndList);
if (HIWORD(lpdis->itemData) & (FLAG_NODEL | FLAG_HIDDEN))
clr = g_clrGrayText;
else if (bHasFocus)
clr = g_clrHighlightText;
else
clr = g_clrWindowText;
oldTextColor = SetTextColor(hdc, clr);
oldBkColor = SetBkColor(hdc, bHasFocus ? g_clrHighlight : g_clrWindow);
szSample[0] = 'W';
MGetTextExtent(hdc, szSample, 1, NULL, &wHeight);
ExtTextOut(hdc, rc.left + ptb->iButWidth + 2,
(rc.top + rc.bottom - wHeight) / 2,
ETO_CLIPPED | ETO_OPAQUE, &rc, pszText, nLen, NULL);
/* We really care about the bitmap value here; this is not just an
* indicator for the separator.
*/
if (nBitmap >= 0)
{
TBBUTTON tbbAdd;
HBITMAP hbmOldGlyphs, hbmOldMono;
tbbAdd.iBitmap = nBitmap;
tbbAdd.iString = -1;
tbbAdd.fsStyle = TBSTYLE_BUTTON;
tbbAdd.fsState = (BYTE)((HIWORD(lpdis->itemData) & FLAG_HIDDEN) ? 0 : TBSTATE_ENABLED);
// BUGBUG: we may need to enter a critical section around this (using globals)
// We need to kick-start the bitmap selection process.
ptb->nSelectedBM = -1;
hbmOldGlyphs = SelectBM(g_hdcGlyphs, ptb, 0);
if (!hbmOldGlyphs)
goto Error1;
hbmOldMono = SelectObject(g_hdcMono, g_hbmMono);
DrawButton(hdc, rc.left + 1, rc.top + 1,
ptb->iButWidth, ptb->iButHeight, ptb, &tbbAdd, FALSE);
if (hbmOldMono)
SelectObject(g_hdcMono, hbmOldMono);
SelectObject(g_hdcGlyphs, hbmOldGlyphs);
Error1:
;
}
SetBkColor(hdc, oldBkColor);
SetTextColor(hdc, oldTextColor);
/* Frame the item if it is selected but does not have the focus.
*/
if (bSelected && !bHasFocus)
{
nLen = rc.left + (int)SendMessage(hwndList,
LB_GETHORIZONTALEXTENT, 0, 0L);
if (rc.right < nLen)
rc.right = nLen;
FrameRect(hdc, &rc, g_hbrHighlight);
}
}
if (lpdis->itemAction == ODA_FOCUS || (lpdis->itemState & ODS_FOCUS))
DrawFocusRect(hdc, &rc);
LocalFree((HLOCAL)pszText);
}
void NEAR PASCAL LBMoveButton(LPADJUSTDLGDATA lpad, UINT wIDSrc, int iPosSrc,
UINT wIDDst, int iPosDst, int iSelOffset)
{
HWND hwndSrc, hwndDst;
DWORD dwTemp;
PSTR pStr;
TBBUTTON tbAdjust;
int iTopDst;
hwndSrc = GetDlgItem(lpad->hDlg, wIDSrc);
hwndDst = GetDlgItem(lpad->hDlg, wIDDst);
/* Make sure we can delete the source and insert at the dest
*/
dwTemp = SendMessage(hwndSrc, LB_GETITEMDATA, iPosSrc, 0L);
if (iPosSrc < 0 || (HIWORD(dwTemp) & FLAG_NODEL))
return;
if (wIDDst == IDC_CURRENT &&
!SendItemNotify(lpad->ptb, iPosDst, TBN_QUERYINSERT))
return;
/* Get the string for the source
*/
pStr = (PSTR)LocalAlloc(LPTR,
(int)(SendMessage(hwndSrc, LB_GETTEXTLEN, iPosSrc, 0L)) + 1);
if (!pStr)
return;
SendMessage(hwndSrc, LB_GETTEXT, iPosSrc, (LPARAM)(LPSTR)pStr);
SendMessage(hwndDst, WM_SETREDRAW, 0, 0L);
iTopDst = (int)SendMessage(hwndDst, LB_GETTOPINDEX, 0, 0L);
/* If we are inserting into the available button list, we need to determine
* the insertion point
*/
if (wIDDst == IDC_BUTTONLIST)
{
/* Insert this back in the available list if this is not a space or a
* hidden button.
*/
if (HIWORD(dwTemp)&(FLAG_SEP | FLAG_HIDDEN))
{
iPosDst = 0;
goto DelTheSrc;
}
else
{
UINT uTemp;
uTemp = HIWORD(dwTemp) & ~(FLAG_ALLFLAGS);
/* This just does a linear search for where to put the
* item. Slow, but this only happens when the user clicks
* the "Remove" button.
*/
for (iPosDst = 1; ; ++iPosDst)
{
/* Notice that this will break out when iPosDst is
* past the number of items, since -1 will be returned
*/
if ((UINT)HIWORD(SendMessage(hwndDst, LB_GETITEMDATA,
iPosDst, 0L)) >= uTemp)
break;
}
}
}
else if (iPosDst < 0)
goto CleanUp;
/* Attempt to insert the new string
*/
if ((int)SendMessage(hwndDst, LB_INSERTSTRING, iPosDst, (LPARAM)(LPSTR)pStr)
== iPosDst)
{
/* Attempt to sync up the actual toolbar.
*/
if (wIDDst == IDC_CURRENT)
{
HWND hwndT;
if (IsSeparator(dwTemp))
{
// Make up a dummy lpInfo if this is a space
tbAdjust.iBitmap = 0;
tbAdjust.idCommand = 0;
tbAdjust.fsState = 0;
tbAdjust.fsStyle = TBSTYLE_SEP;
}
else
{
// callback to get info
if (!GetAdjustInfo(lpad->ptb, HIWORD(dwTemp) & ~(FLAG_ALLFLAGS), &tbAdjust, NULL, 0))
goto DelTheDst;
}
hwndT = lpad->ptb->hwnd;
if (!InsertButtons(lpad->ptb, iPosDst, 1, &tbAdjust))
{
DelTheDst:
SendMessage(hwndDst, LB_DELETESTRING, iPosDst, 0L);
goto CleanUp;
}
else
{
lpad->ptb = FixPTB(hwndT);
}
if (wIDSrc == IDC_CURRENT && iPosSrc >= iPosDst)
++iPosSrc;
}
SendMessage(hwndDst, LB_SETITEMDATA, iPosDst, dwTemp);
DelTheSrc:
/* Don't delete the "Separator" in the new list
*/
if (wIDSrc != IDC_BUTTONLIST || iPosSrc != 0)
{
SendMessage(hwndSrc, LB_DELETESTRING, iPosSrc, 0L);
if (wIDSrc == wIDDst)
{
if (iPosSrc < iPosDst)
--iPosDst;
if (iPosSrc < iTopDst)
--iTopDst;
}
}
/* Delete the corresponding button
*/
if (wIDSrc == IDC_CURRENT)
DeleteButton(lpad->ptb, iPosSrc);
/* Only set the src index if the two windows are different
*/
if (wIDSrc != wIDDst)
{
if (SendMessage(hwndSrc, LB_SETCURSEL, iPosSrc, 0L) == LB_ERR)
SendMessage(hwndSrc, LB_SETCURSEL, iPosSrc - 1, 0L);
SEND_WM_COMMAND(lpad->hDlg, wIDSrc, hwndSrc, LBN_SELCHANGE);
}
/* Send the final SELCHANGE message after everything else is done
*/
SendMessage(hwndDst, LB_SETCURSEL, iPosDst + iSelOffset, 0L);
SEND_WM_COMMAND(lpad->hDlg, wIDDst, hwndDst, LBN_SELCHANGE);
}
CleanUp:
LocalFree((HLOCAL)pStr);
if (wIDSrc == wIDDst)
SendMessage(hwndDst, LB_SETTOPINDEX, iTopDst, 0L);
SendMessage(hwndDst, WM_SETREDRAW, 1, 0L);
InvalidateRect(hwndDst, NULL, TRUE);
SendCmdNotify(lpad->ptb, TBN_TOOLBARCHANGE);
}
void NEAR PASCAL SafeEnableWindow(HWND hDlg, UINT wID, HWND hwndDef, BOOL bEnable)
{
HWND hwndEnable;
hwndEnable = GetDlgItem(hDlg, wID);
if (!bEnable && GetFocus() == hwndEnable)
SendMessage(hDlg, WM_NEXTDLGCTL, (WPARAM)hwndDef, 1L);
EnableWindow(hwndEnable, bEnable);
}
int NEAR PASCAL InsertIndex(LPADJUSTDLGDATA lpad, POINT pt, BOOL bDragging)
{
HWND hwndCurrent = GetDlgItem(lpad->hDlg, IDC_CURRENT);
int nItem = LBItemFromPt(hwndCurrent, pt, bDragging);
if (nItem >= 0)
{
if (!SendItemNotify(lpad->ptb, nItem, TBN_QUERYINSERT))
nItem = -1;
}
DrawInsert(lpad->hDlg, hwndCurrent, bDragging ? nItem : -1);
return(nItem);
}
BOOL NEAR PASCAL IsInButtonList(HWND hDlg, POINT pt)
{
ScreenToClient(hDlg, &pt);
return(ChildWindowFromPoint(hDlg, pt) == GetDlgItem(hDlg, IDC_BUTTONLIST));
}
BOOL NEAR PASCAL HandleDragMsg(LPADJUSTDLGDATA lpad, HWND hDlg, WPARAM wID, LPDRAGLISTINFO lpns)
{
switch (wID)
{
case IDC_CURRENT:
switch (lpns->uNotification)
{
case DL_BEGINDRAG:
{
int nItem = (int)SendMessage(lpns->hWnd, LB_GETCURSEL, 0, 0L);
if (HIWORD(SendMessage(lpns->hWnd, LB_GETITEMDATA, nItem, 0L)) & FLAG_NODEL)
return SetDlgMsgResult(hDlg, WM_COMMAND, FALSE);
return SetDlgMsgResult(hDlg, WM_COMMAND, TRUE);
}
case DL_DRAGGING:
{
int nDropIndex;
DraggingSomething:
nDropIndex = InsertIndex(lpad, lpns->ptCursor, TRUE);
if (nDropIndex >= 0 || IsInButtonList(hDlg, lpns->ptCursor))
{
SetCursor(LoadCursor(HINST_THISDLL,
MAKEINTRESOURCE(IDC_MOVEBUTTON)));
return SetDlgMsgResult(hDlg, WM_COMMAND, 0);
}
return SetDlgMsgResult(hDlg, WM_COMMAND, DL_STOPCURSOR);
}
case DL_DROPPED:
{
int nDropIndex, nSrcIndex;
nDropIndex = InsertIndex(lpad, lpns->ptCursor, FALSE);
nSrcIndex = (int)SendMessage(lpns->hWnd, LB_GETCURSEL, 0, 0L);
if (nDropIndex >= 0)
{
if ((UINT)(nDropIndex - nSrcIndex) > 1)
LBMoveButton(lpad, IDC_CURRENT, nSrcIndex,
IDC_CURRENT, nDropIndex, 0);
}
else if (IsInButtonList(hDlg, lpns->ptCursor))
{
LBMoveButton(lpad, IDC_CURRENT, nSrcIndex, IDC_BUTTONLIST, 0, 0);
}
break;
}
case DL_CANCELDRAG:
CancelDrag:
/* This erases the insert icon if it exists.
*/
InsertIndex(lpad, lpns->ptCursor, FALSE);
break;
default:
break;
}
break;
case IDC_BUTTONLIST:
switch (lpns->uNotification)
{
case DL_BEGINDRAG:
return SetDlgMsgResult(hDlg, WM_COMMAND, TRUE);
case DL_DRAGGING:
goto DraggingSomething;
case DL_DROPPED:
{
int nDropIndex;
nDropIndex = InsertIndex(lpad, lpns->ptCursor, FALSE);
if (nDropIndex >= 0)
LBMoveButton(lpad, IDC_BUTTONLIST,
(int)SendMessage(lpns->hWnd, LB_GETCURSEL, 0, 0L),
IDC_CURRENT, nDropIndex, 0);
break;
}
case DL_CANCELDRAG:
goto CancelDrag;
default:
break;
}
break;
default:
break;
}
return(0);
}
#pragma data_seg(DATASEG_READONLY)
const static DWORD aAdjustHelpIDs[] = { // Context Help IDs
IDC_RESET, IDH_COMCTL_RESET,
IDC_APPHELP, IDH_HELP,
IDC_MOVEUP, IDH_COMCTL_MOVEUP,
IDC_MOVEDOWN, IDH_COMCTL_MOVEDOWN,
IDC_BUTTONLIST, IDH_COMCTL_AVAIL_BUTTONS,
IDOK, IDH_COMCTL_ADD,
IDC_REMOVE, IDH_COMCTL_REMOVE,
IDC_CURRENT, IDH_COMCTL_BUTTON_LIST,
IDCANCEL, IDH_COMCTL_CLOSE,
0, 0
};
#pragma data_seg()
BOOL CALLBACK AdjustDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
LPADJUSTDLGDATA lpad = (LPADJUSTDLGDATA)GetWindowLong(hDlg, DWL_USER);
switch (uMsg)
{
case WM_INITDIALOG:
SetWindowLong(hDlg, DWL_USER, lParam); /* LPADJUSTDLGDATA pointer */
if (!InitAdjustDlg(hDlg, (LPADJUSTDLGDATA)lParam))
EndDialog(hDlg, FALSE);
ShowWindow(hDlg, SW_SHOW);
UpdateWindow(hDlg);
SetFocus(GetDlgItem(hDlg, IDC_CURRENT));
MakeDragList(GetDlgItem(hDlg, IDC_CURRENT));
MakeDragList(GetDlgItem(hDlg, IDC_BUTTONLIST));
return FALSE;
case WM_MEASUREITEM:
#define lpmis ((MEASUREITEMSTRUCT FAR *)lParam)
if (lpmis->CtlID == IDC_BUTTONLIST || lpmis->CtlID == IDC_CURRENT)
{
int nHeight;
HWND hwndList = GetDlgItem(hDlg, lpmis->CtlID);
HDC hDC = GetDC(hwndList);
char szSample[2];
szSample[0] = 'W';
MGetTextExtent(hDC, szSample, 1, NULL, &nHeight);
// note, we use this lame hack because we get WM_MEASUREITEMS
// before our WM_INITDIALOG where we get the lpad setup
if (nHeight < g_dyButtonHack + 2)
nHeight = g_dyButtonHack + 2;
lpmis->itemHeight = nHeight;
ReleaseDC(hwndList, hDC);
}
break;
case WM_DRAWITEM:
PaintAdjustLine(lpad->ptb, (DRAWITEMSTRUCT FAR *)lParam);
break;
/*case WM_HELP:
SendCmdNotify(lpad->ptb, TBN_CUSTHELP);
break;*/
case WM_HELP:
WinHelp((HWND)((LPHELPINFO)lParam)->hItemHandle, NULL,
HELP_WM_HELP, (DWORD)(LPSTR)aAdjustHelpIDs);
break;
case WM_CONTEXTMENU:
WinHelp((HWND)wParam, NULL, HELP_CONTEXTMENU,
(DWORD)(LPVOID)aAdjustHelpIDs);
break;
case WM_COMMAND:
switch (GET_WM_COMMAND_ID(wParam, lParam))
{
case IDC_APPHELP:
SendCmdNotify(lpad->ptb, TBN_CUSTHELP);
break;
case IDOK:
{
int iPos, nItem;
nItem = (int)SendDlgItemMessage(hDlg, IDC_BUTTONLIST,
LB_GETCURSEL, 0, 0L);
iPos = (int)SendDlgItemMessage(hDlg, IDC_CURRENT,
LB_GETCURSEL, 0, 0L);
if (iPos == -1)
iPos = 0;
LBMoveButton(lpad, IDC_BUTTONLIST, nItem, IDC_CURRENT, iPos, 1);
break;
}
case IDC_BUTTONLIST:
switch (GET_WM_COMMAND_CMD(wParam, lParam))
{
case LBN_DBLCLK:
SendMessage(hDlg, WM_COMMAND, IDOK, 0L);
break;
case LBN_SETFOCUS:
case LBN_KILLFOCUS:
{
RECT rc;
if (SendMessage(GET_WM_COMMAND_HWND(wParam, lParam), LB_GETITEMRECT,
(int)SendMessage(GET_WM_COMMAND_HWND(wParam, lParam), LB_GETCURSEL,
0, 0L), (LONG)(LPRECT)&rc) != LB_ERR)
InvalidateRect(GET_WM_COMMAND_HWND(wParam, lParam), &rc, FALSE);
}
default:
break;
}
break;
case IDC_CURRENT:
switch (GET_WM_COMMAND_CMD(wParam, lParam))
{
case LBN_SELCHANGE:
{
BOOL bDelOK;
HWND hwndList = GET_WM_COMMAND_HWND(wParam, lParam);
int iPos = (int)SendMessage(hwndList, LB_GETCURSEL, 0, 0L);
SafeEnableWindow(hDlg, IDOK, hwndList, SendItemNotify(lpad->ptb, iPos, TBN_QUERYINSERT));
bDelOK = !(HIWORD(SendMessage(hwndList, LB_GETITEMDATA, iPos, 0L)) & FLAG_NODEL);
SafeEnableWindow(hDlg, IDC_REMOVE, hwndList, bDelOK);
SafeEnableWindow(hDlg, IDC_MOVEUP, hwndList, bDelOK &&
GetNearestInsert(lpad->ptb, iPos - 1, 0, GNI_LOW) >= 0);
SafeEnableWindow(hDlg, IDC_MOVEDOWN, hwndList, bDelOK &&
GetNearestInsert(lpad->ptb, iPos + 2,
lpad->ptb->iNumButtons, GNI_HIGH) >= 0);
break;
}
case LBN_DBLCLK:
SendMessage(hDlg, WM_COMMAND, IDC_REMOVE, 0L);
break;
case LBN_SETFOCUS:
case LBN_KILLFOCUS:
{
RECT rc;
if (SendMessage(GET_WM_COMMAND_HWND(wParam, lParam), LB_GETITEMRECT,
(int)SendMessage(GET_WM_COMMAND_HWND(wParam, lParam), LB_GETCURSEL,
0, 0L), (LONG)(LPRECT)&rc) != LB_ERR)
InvalidateRect(GET_WM_COMMAND_HWND(wParam, lParam), &rc, FALSE);
}
default:
break;
}
break;
case IDC_REMOVE:
{
int iPos = (int)SendDlgItemMessage(hDlg, IDC_CURRENT, LB_GETCURSEL, 0, 0);
LBMoveButton(lpad, IDC_CURRENT, iPos, IDC_BUTTONLIST, 0, 0);
break;
}
case IDC_MOVEUP:
case IDC_MOVEDOWN:
{
int iPosSrc, iPosDst;
iPosSrc = (int)SendDlgItemMessage(hDlg, IDC_CURRENT, LB_GETCURSEL, 0, 0L);
if (wParam == IDC_MOVEUP)
iPosDst = GetNearestInsert(lpad->ptb, iPosSrc - 1, 0, GNI_LOW);
else
iPosDst = GetNearestInsert(lpad->ptb, iPosSrc + 2, lpad->ptb->iNumButtons, GNI_HIGH);
LBMoveButton(lpad, IDC_CURRENT, iPosSrc, IDC_CURRENT, iPosDst, 0);
break;
}
case IDC_RESET:
{
// ptb will change across call below
HWND hwndT = lpad->ptb->hwnd;
SendCmdNotify(lpad->ptb, TBN_RESET);
// ptb probably changed across above call
lpad->ptb = FixPTB(hwndT);
}
/* Reset the dialog, but exit if something goes wrong. */
lpad->iPos = 0;
if (InitAdjustDlg(hDlg, lpad))
break;
/* We have to fall through because we won't know where to insert
* buttons after resetting.
*/
case IDCANCEL:
EndDialog(hDlg, TRUE);
break;
default:
return(FALSE);
}
break;
default:
if (uMsg == uDragListMsg)
return HandleDragMsg(lpad, hDlg, wParam, (LPDRAGLISTINFO)lParam);
return(FALSE);
}
return(TRUE);
}
// BUGBUG: this should support saving to an IStream
/* This saves the state of the toolbar. Spaces are saved as -1 (-2 if hidden)
* and other buttons are just saved as the command ID. When restoring, all
* ID's are filled in, and the app is queried for all buttons so that the
* bitmap and state information may be filled in. Button ID's that are not
* returned from the app are removed.
*/
BOOL FAR PASCAL SaveRestoreFromReg(PTBSTATE ptb, BOOL bWrite, HKEY hkr, LPCSTR pszSubKey, LPCSTR pszValueName)
{
BOOL bRet = FALSE;
if (bWrite)
{
UINT uSize = ptb->iNumButtons * sizeof(DWORD);
DWORD *pData = (DWORD *)LocalAlloc(LPTR, uSize);
if (pData)
{
HKEY hkeySave;
if (RegCreateKey(hkr, pszSubKey, &hkeySave) == ERROR_SUCCESS)
{
int i;
for (i = 0; i < ptb->iNumButtons; i++)
{
if (ptb->Buttons[i].idCommand)
pData[i] = ptb->Buttons[i].idCommand;
else
{
// If the separator has an ID, then it is an "owner" item.
if (ptb->Buttons[i].fsState & TBSTATE_HIDDEN)
pData[i] = (DWORD)-2; // hidden
else
pData[i] = (DWORD)-1; // normal seperator
}
}
if (RegSetValueEx(hkeySave, (LPSTR)pszValueName, 0, REG_BINARY, (LPVOID)pData, uSize) == ERROR_SUCCESS)
bRet = TRUE;
RegCloseKey(hkeySave);
}
LocalFree((HLOCAL)pData);
}
}
else
{
HKEY hkey;
if (RegOpenKey(hkr, pszSubKey, &hkey) == ERROR_SUCCESS)
{
DWORD cbSize = 0;
if ((RegQueryValueEx(hkey, (LPSTR)pszValueName, 0, NULL, NULL, &cbSize) == ERROR_SUCCESS) &&
(cbSize > sizeof(DWORD)) &&
(cbSize < (512 * sizeof(DWORD)))) // sanity check...
{
UINT uSize = (UINT)cbSize;
DWORD *pData = (DWORD *)LocalAlloc(LPTR, uSize);
if (pData)
{
DWORD dwType;
DWORD cbSize = (DWORD)uSize;
if ((RegQueryValueEx(hkey, (LPSTR)pszValueName, 0, &dwType, (LPVOID)pData, &cbSize) == ERROR_SUCCESS) &&
(dwType == REG_BINARY) &&
(cbSize == (DWORD)uSize))
{
int iButtonIndex;
int iNumButtons = (int)uSize / sizeof(DWORD);
PTBSTATE pTemp;
// Before reloading the buttons, delete the tooltips
// of the previous buttons (if they exist).
if (ptb && ptb->hwndToolTips) {
TOOLINFO ti;
ti.cbSize = sizeof(ti);
ti.hwnd = ptb->hwnd;
for (iButtonIndex = 0;
iButtonIndex < ptb->iNumButtons; iButtonIndex++) {
if (!(ptb->Buttons[iButtonIndex].fsStyle & TBSTYLE_SEP)) {
ti.uId = ptb->Buttons[iButtonIndex].idCommand;
SendMessage(ptb->hwndToolTips, TTM_DELTOOL,
0, (LPARAM)(LPTOOLINFO)&ti);
}
}
}
// grow pbt to hold new buttons
pTemp = (PTBSTATE)LocalReAlloc(ptb, sizeof(TBSTATE) + (iNumButtons - 1) * sizeof(TBBUTTON), LMEM_MOVEABLE);
if (pTemp)
{
int i;
#ifdef DEBUG
if (ptb != pTemp)
DebugMsg(DM_TRACE, "toolbar load, ptb changed on re-alloc");
#endif
ptb = pTemp;
SetWindowInt(ptb->hwnd, 0, (int)ptb);
ptb->iNumButtons = iNumButtons;
for (i = 0; i < ptb->iNumButtons; i++)
{
if ((long)pData[i] < 0)
{
ptb->Buttons[i].fsStyle = TBSTYLE_SEP;
ptb->Buttons[i].iBitmap = g_dxButtonSep;
ptb->Buttons[i].idCommand = 0;
if (pData[i] == (DWORD)-1)
ptb->Buttons[i].fsState = 0;
else
{
Assert(pData[i] == (DWORD)-2);
ptb->Buttons[i].fsState = TBSTATE_HIDDEN;
}
}
else
{
ptb->Buttons[i].idCommand = pData[i];
ptb->Buttons[i].iBitmap = -1;
}
}
// Now query for all buttons, and fill in the rest of the info
SendCmdNotify(ptb, TBN_BEGINADJUST);
for (i = 0; ; i++)
{
TBBUTTON tbAdjust;
tbAdjust.idCommand = 0;
if (!GetAdjustInfo(ptb, i, &tbAdjust, NULL, 0))
break;
if (!(tbAdjust.fsStyle & TBSTYLE_SEP) || tbAdjust.idCommand)
{
int iPos = PositionFromID(ptb, tbAdjust.idCommand);
if (iPos >= 0)
ptb->Buttons[iPos] = tbAdjust;
if (ptb->hwndToolTips) {
TOOLINFO ti;
// don't bother setting the rect because we'll do it below
// in FlushToolTipsMgr;
ti.cbSize = sizeof(ti);
ti.uFlags = 0;
ti.hwnd = ptb->hwnd;
ti.uId = ptb->Buttons[iPos].idCommand;
ti.lpszText = LPSTR_TEXTCALLBACK;
SendMessage(ptb->hwndToolTips, TTM_ADDTOOL, 0, (LPARAM)(LPTOOLINFO)&ti);
}
}
}
SendCmdNotify(ptb, TBN_ENDADJUST);
// cleanup all the buttons that were not recognized
// do this backwards to minimize data movement (and iNumButtons changes)
for (i = ptb->iNumButtons - 1; i >= 0; i--)
{
// DeleteButton does no realloc, so ptb will not move
if (ptb->Buttons[i].iBitmap < 0)
DeleteButton(ptb, (UINT)i);
}
bRet = (ptb->iNumButtons != 0); // success
FlushButtonCache(ptb);
// bugbug: break autosize to a function and call it
SendMessage(ptb->hwnd, TB_AUTOSIZE, 0, 0);
InvalidateRect(ptb->hwnd, NULL, TRUE);
FlushToolTipsMgr(ptb);
}
}
LocalFree((HLOCAL)pData);
}
}
RegCloseKey(hkey);
}
}
return bRet;
}
#ifndef WIN32
#pragma data_seg(DATASEG_READONLY)
const char c_szToolbarStates[] = "Software\\Microsoft\\Windows\\CurrentVersion\\ToolbarState";
#pragma data_seg()
BOOL FAR PASCAL SaveRestore(PTBSTATE ptb, BOOL bWrite, LPSTR FAR *lpNames)
{
// note, we ignore lpNames[1] (the ini file name)
// ... we hope we don't get conflicts with lpNames[0] entires
return SaveRestoreFromReg(ptb, bWrite, HKEY_CURRENT_USER, c_szToolbarStates, lpNames[0]);
}
#endif
void FAR PASCAL CustomizeTB(PTBSTATE ptb, int iPos)
{
ADJUSTDLGDATA ad;
HWND hwndT = ptb->hwnd; // ptb will change across call below
if (ptb->hdlgCust) // We are already customizing this toolbar
return;
ad.ptb = ptb;
ad.iPos = iPos;
// REVIEW: really should be per thread data, but not likely to cause a problem
g_dyButtonHack = ptb->iButWidth; // see note in WM_MEASUREITEM code
SendCmdNotify(ptb, TBN_BEGINADJUST);
DialogBoxParam(HINST_THISDLL, MAKEINTRESOURCE(ADJUSTDLG), ptb->hwndCommand,
AdjustDlgProc, (LPARAM)(LPADJUSTDLGDATA)&ad);
// ptb probably changed across above call
ptb = (PTBSTATE)GetWindowInt(hwndT, 0);
ptb->hdlgCust = NULL;
SendCmdNotify(ptb, TBN_ENDADJUST);
// SendCmdNotify(ptb, TBN_TOOLBARCHANGE);
}