/*
 * misc - misc stuff
 */

#include "tweakui.h"

/*****************************************************************************
 *
 *  Eschew the C runtime
 *
 *****************************************************************************/

void *  __cdecl operator new(size_t nSize)
{
    // Zero init just to save some headaches
    return((LPVOID)LocalAlloc(LPTR, nSize));
}


void  __cdecl operator delete(void *pv)
{
    LocalFree((HLOCAL)pv);
}

/*****************************************************************************
 *
 *  Misc_NextPx
 *
 *  Allocate another slot in the growable X array, returning its index.
 *  The slot is not eaten, however.
 *
 *  The array may move as a result of this call; the caller promises
 *  not to use the resulting pointer after the next alloc call.
 *
 *****************************************************************************/

PV PASCAL
Misc_AllocPx(PGXA pgxa)
{
    if (pgxa->cx >= pgxa->cxAlloc) {
	PV pv = lReAlloc(pgxa->pv, pgxa->cbx * (pgxa->cxAlloc + 5));
	if (pv) {
	    pgxa->pv = pv;
	    pgxa->cxAlloc += 5;
	} else {
	    return 0;
	}
    }
    return (PBYTE)pgxa->pv + pgxa->cbx * pgxa->cx;
}

/*****************************************************************************
 *
 *  Misc_InitPgxa
 *
 *****************************************************************************/

BOOL PASCAL
Misc_InitPgxa(PGXA pgxa, int cbx)
{
    pgxa->cbx = cbx;
    pgxa->cxAlloc = 5;
    pgxa->cx = 0;
    pgxa->pv = lAlloc(pgxa->cbx * pgxa->cxAlloc);
    return pgxa->pv ? TRUE : FALSE;
}

/*****************************************************************************
 *
 *  Misc_FreePgxa
 *
 *****************************************************************************/

void PASCAL
Misc_FreePgxa(PGXA pgxa)
{
    if (pgxa->pv) {
	lFree(pgxa->pv);
    }
}

/*****************************************************************************
 *
 *  Misc_EnableMenuFromHdlgId
 *
 *	Enable or disable a menu item based on a dialog item.
 *
 *****************************************************************************/

void PASCAL
Misc_EnableMenuFromHdlgId(HMENU hmenu, HWND hdlg, UINT idc)
{
    EnableMenuItem(hmenu, idc, MF_BYCOMMAND | (
		IsWindowEnabled(GetDlgItem(hdlg, idc)) ? 0 : MFS_GRAYED));
}

/*****************************************************************************
 *
 *  Misc_LV_GetCurSel
 *
 *	Return the selection index.
 *
 *****************************************************************************/


int PASCAL
Misc_LV_GetCurSel(HWND hwnd)
{
    return ListView_GetNextItem(hwnd, -1, LVNI_FOCUSED);
}

/*****************************************************************************
 *
 *  Misc_LV_SetCurSel
 *
 *	Set the selection index.
 *
 *****************************************************************************/

void PASCAL
Misc_LV_SetCurSel(HWND hwnd, int iIndex)
{
    ListView_SetItemState(hwnd, iIndex, LVIS_FOCUSED | LVIS_SELECTED,
					LVIS_FOCUSED | LVIS_SELECTED);
}

/*****************************************************************************
 *
 *  Misc_LV_EnsureSel
 *
 *  Make sure *something* is selected.  Try to make it iItem.
 *
 *****************************************************************************/

void PASCAL
Misc_LV_EnsureSel(HWND hwnd, int iItem)
{
    int iItemMax = ListView_GetItemCount(hwnd) - 1;
    if (iItem > iItemMax) {
	iItem = iItemMax;
    }
    Misc_LV_SetCurSel(hwnd, iItem);
}

/*****************************************************************************
 *
 *  Misc_LV_GetItemInfo
 *
 *  Grab some info out of an item.
 *
 *  The incoming LV_ITEM should have any fields initialized which are
 *  required by the mask.  (E.g., the state mask.)
 *
 *****************************************************************************/

void PASCAL
Misc_LV_GetItemInfo(HWND hwnd, LV_ITEM FAR *plvi, int iItem, UINT mask)
{
    plvi->iItem = iItem;
    plvi->iSubItem = 0;
    plvi->mask = mask;
    ListView_GetItem(hwnd, plvi);
}

/*****************************************************************************
 *
 *  Misc_LV_GetParam
 *
 *	Convert an iItem into the associated parameter.
 *
 *****************************************************************************/

LPARAM PASCAL
Misc_LV_GetParam(HWND hwnd, int iItem)
{
    LV_ITEM lvi;
    Misc_LV_GetItemInfo(hwnd, &lvi, iItem, LVIF_PARAM);
    return lvi.lParam;
}

/*****************************************************************************
 *
 *  Misc_LV_HitTest
 *
 *	Perform a hit-test on the listview.  The LPARAM parameter is
 *	the screen coordinates of the message.
 *
 *****************************************************************************/

void PASCAL
Misc_LV_HitTest(HWND hwnd, LV_HITTESTINFO FAR *phti, LPARAM lpPos)
{
    phti->pt.x = (signed short)LOWORD(lpPos);
    phti->pt.y = (signed short)HIWORD(lpPos);

    MapWindowPoints(HWND_DESKTOP, hwnd, &phti->pt, 1);

    ListView_HitTest(hwnd, phti);
}


/*****************************************************************************
 *
 *  Misc_Combo_GetCurItemData
 *
 *      Get the current item data from a combo box.
 *
 *****************************************************************************/

LRESULT PASCAL
Misc_Combo_GetCurItemData(HWND hwnd)
{
    int iItem = ComboBox_GetCurSel(hwnd);
    return ComboBox_GetItemData(hwnd, iItem);
}


/*****************************************************************************
 *
 *  Misc_Trim
 *
 *	Trim leading and trailing spaces from a string.  Returns a pointer
 *	to the first non-space character, and slaps a '\0' on top of the
 *	last trailing space.
 *
 *****************************************************************************/

LPTSTR PASCAL
Misc_Trim(LPTSTR ptsz)
{
    int ctch;

    for ( ; (unsigned)(*ptsz - 1) <= ' '; ptsz++);
    for (ctch = lstrlen(ptsz); ctch && (unsigned)ptsz[ctch-1] <= ' '; ctch--);
    if (ctch) {
	ptsz[ctch] = '\0';
    }
    return ptsz;
}

/*****************************************************************************
 *
 *  Misc_SetDrbSize
 *
 *	Set the size of the buffer in a DRB.
 *
 *****************************************************************************/

typedef struct DRB {			/* dynamically resizable buffer */
    PV pv;				/* Actual buffer */
    UINT cb;				/* Size of buffer in bytes */
} DRB, *PDRB;

void PASCAL
Misc_SetDrbSize(PDRB pdrb, UINT cb)
{
    if (pdrb->pv) {
	LocalFree(pdrb->pv);
	pdrb->pv = 0;
	pdrb->cb = 0;
    }
    if (cb) {
	pdrb->pv = LocalAlloc(LMEM_FIXED, cb);
	if (pdrb->pv) {
	    pdrb->cb = cb;
	}
    }
}

/*****************************************************************************
 *
 *  Misc_EnsureDrb
 *
 *	Make sure that the DRB can handle at least cb bytes.
 *
 *	Round up to 4K boundary for convenience.
 *
 *****************************************************************************/

BOOL PASCAL
Misc_EnsureDrb(PDRB pdrb, UINT cb)
{
    if (cb <= pdrb->cb) {
    } else {
	Misc_SetDrbSize(pdrb, (cb + 1023) & ~1023);
    }
    return pdrb->pv ? TRUE : FALSE;
}

/*****************************************************************************
 *
 *  Misc_CopyRegWorker
 *
 *  Copy a registry tree from one point to another.
 *
 *  Since this is a recursive routine, don't allocate lots of goo off the
 *  stack.
 *
 *****************************************************************************/

typedef struct RCI {			/* registry copy info */
    DRB drb;				/* Data being copied */
    TCH tszKey[ctchKeyMax];		/* Key name buffer */
} RCI, *PRCI;

BOOL PASCAL
Misc_CopyRegWorker(PRCI prci, HKEY hkSrcRoot, PCTSTR ptszSrc,
			      HKEY hkDstRoot, PCTSTR ptszDst)
{
    HKEY hkSrc, hkDst;
    BOOL fRc;
    if (_RegOpenKey(hkSrcRoot, ptszSrc, &hkSrc) == 0) {
	if (RegCreateKey(hkDstRoot, ptszDst, &hkDst) == 0) {
	    int i;

	    /*
	     *	The first loop copies the values.
	     */
	    for (i = 0; ; i++) {
                DWORD ctch = cA(prci->tszKey), cb = 0, dwType;
                switch (RegEnumValue(hkSrc, i, prci->tszKey, &ctch, 0,
				     &dwType, 0, &cb)) {
		case ERROR_NO_MORE_ITEMS: goto valuesdone;

		/*
		 *  We can get an ERROR_SUCCESS if the value length is 0.
		 */
		case ERROR_SUCCESS:
		case ERROR_MORE_DATA:
		    if (Misc_EnsureDrb(&prci->drb, cb)) {
			DWORD dwType;
			if (RegQueryValueEx(hkSrc, prci->tszKey, 0, &dwType,
                                            (LPBYTE)prci->drb.pv, &cb) == 0 &&
			    RegSetValueEx(hkDst, prci->tszKey, 0, dwType,
                                            (LPBYTE)prci->drb.pv, cb) == 0) {
			} else {
			    fRc = 0; goto stopkey;
			}
		    } else {
			fRc = 0; goto stopkey;
		    }
		    break;

		default:
		    fRc = 0; goto stopkey;
		}
	    }
	    valuesdone:;

	    /*
	     *	The second loop recurses on each subkey.
	     */
	    for (i = 0; ; i++) {
		switch (RegEnumKey(hkSrc, i, prci->tszKey, cA(prci->tszKey))) {
		case ERROR_NO_MORE_ITEMS: goto keysdone;

		/*
		 *  We can get an ERROR_SUCCESS if the subkey length is 0.
		 *  (Thought that might be illegal for other reasons, but
		 *  we'll go along with it anyway.)
		 */
		case ERROR_SUCCESS:
		case ERROR_MORE_DATA:
		    if (Misc_CopyRegWorker(prci, hkSrc, prci->tszKey,
						 hkDst, prci->tszKey)) {
		    } else {
			fRc = 0; goto stopkey;
		    }
		    break;

		default:
		    fRc = 0; goto stopkey;
		}
	    }
	    keysdone:;
            fRc = TRUE;

	    stopkey:;
	    RegCloseKey(hkDst);
	} else {
	    fRc = 0;
	}
	RegCloseKey(hkSrc);
    } else {
	fRc = 0;
    }
    return fRc;
}

/*****************************************************************************
 *
 *  Misc_CopyReg
 *
 *  Copy a registry tree from one point to another.
 *
 *****************************************************************************/

BOOL PASCAL
Misc_CopyReg(HKEY hkSrcRoot, PCTSTR ptszSrc, HKEY hkDstRoot, PCTSTR ptszDst)
{
    BOOL fRc;
    RCI rci;
    rci.drb.pv = 0;
    rci.drb.cb = 0;

    fRc = Misc_CopyRegWorker(&rci, hkSrcRoot, ptszSrc, hkDstRoot, ptszDst);

    Misc_SetDrbSize(&rci.drb, 0);

    if (fRc) {
    } else {
	/* Clean up partial copy */
	RegDeleteTree(hkDstRoot, ptszDst);
    }

    return fRc;
}

/*****************************************************************************
 *
 *  Misc_RenameReg
 *
 *  Rename a registry key by copying it and deleting the original.
 *
 *****************************************************************************/

BOOL PASCAL
Misc_RenameReg(HKEY hkRoot, PCTSTR ptszKey, PCTSTR ptszSrc, PCTSTR ptszDst)
{
    BOOL fRc;
    HKEY hk;

    if (_RegOpenKey(hkRoot, ptszKey, &hk) == 0) {
        if (Misc_CopyReg(hk, ptszSrc, hk, ptszDst)) {
            RegDeleteTree(hk, ptszSrc);
            fRc = TRUE;
        } else {
            fRc = FALSE;
        }
        RegCloseKey(hk);
    } else {
        fRc = FALSE;
    }

    return fRc;
}

/*****************************************************************************
 *
 *  lstrcatnBs
 *
 *  Like lstrcatn, but with a backslash stuck in between.
 *
 *****************************************************************************/

void PASCAL
lstrcatnBs(PTSTR ptszDst, PCTSTR ptszSrc, int ctch)
{
    int ctchDst = lstrlen(ptszDst);
    ptszDst += ctchDst;
    ctch -= ctchDst;
    if (ctch > 1) {
	ptszDst[0] = TEXT('\\');
	lstrcpyn(ptszDst + 1, ptszSrc, ctch - 1);
    }
}

/*****************************************************************************
 *
 *  Misc_GetShellIconSize
 *
 *****************************************************************************/

#pragma BEGIN_CONST_DATA

KL const c_klShellIconSize = { &c_hkCU, c_tszMetrics, c_tszShellIconSize };

#pragma END_CONST_DATA


UINT PASCAL
Misc_GetShellIconSize(void)
{
    return GetIntPkl(GetSystemMetrics(SM_CXICON), &c_klShellIconSize);
}

/*****************************************************************************
 *
 *  Misc_SetShellIconSize
 *
 *	Dork the shell icon size and rebuild.
 *
 *****************************************************************************/

void PASCAL
Misc_SetShellIconSize(UINT ui)
{
    SetIntPkl(ui, &c_klShellIconSize);
    SendMessage(HWND_BROADCAST, WM_SETTINGCHANGE, SPI_SETNONCLIENTMETRICS, 0L);
}

/*****************************************************************************
 *
 *  Misc_RebuildIcoCache
 *
 *	Force the shell to rebuild its icon cache.
 *
 *	Due to the way the shell works, we have to do this by changing
 *	the icon sizes, then changing it back.
 *
 *****************************************************************************/

void PASCAL
Misc_RebuildIcoCache(void)
{
    UINT cxIcon = Misc_GetShellIconSize();
    Misc_SetShellIconSize(cxIcon-1);
    Misc_SetShellIconSize(cxIcon);
    Explorer_HackPtui();
}

/*****************************************************************************
 *
 *  SetWaitCursor
 *
 *****************************************************************************/

HCURSOR SetWaitCursor(void)
{
    return SetCursor(LoadCursor(0, IDC_WAIT));
}