/*
 * pickicon - Icon picker
 */

#include "stdafx.h"
#include "pickicon.h"
#include "windowsx.h"
#include "commdlg.h"
#include "resource.h"
#include "util.h"

#define cA(a) (sizeof(a)/sizeof(a[0]))

typedef TCHAR TCH;

typedef struct COFN {           /* common open file name */
    OPENFILENAME ofn;           /* The thing COMMDLG wants */
    TCH tsz[MAX_PATH];          /* Where we build the name */
    TCH tszFilter[100];         /* File open/save filter */
} COFN, *PCOFN;

/*****************************************************************************
 *
 *  InitOpenFileName
 *
 *  Initialize a COFN structure.
 *
 *****************************************************************************/

void PASCAL
InitOpenFileName(HWND hwnd, PCOFN pcofn, UINT ids, LPCTSTR pszInit)
{
    int itchMax;
    TCH tch;

    ZeroMemory(&pcofn->ofn, sizeof(pcofn->ofn));
    pcofn->ofn.lStructSize |= sizeof(pcofn->ofn);
    pcofn->ofn.hwndOwner = hwnd;
    pcofn->ofn.lpstrFilter = pcofn->tszFilter;
    pcofn->ofn.lpstrFile = pcofn->tsz;
    pcofn->ofn.nMaxFile = MAX_PATH;
    pcofn->ofn.Flags |= (OFN_HIDEREADONLY | OFN_NOCHANGEDIR);

    /* Get the filter string */
    itchMax = LoadString(SC::GetHinst(), ids, pcofn->tszFilter, cA(pcofn->tszFilter));

    if (itchMax) {
        /* Marker character must not be DBCS */
        tch = pcofn->tszFilter[itchMax-1];
        LPTSTR ptsz = pcofn->tszFilter;
        while (ptsz < &pcofn->tszFilter[itchMax]) {
            if (*ptsz == tch) *ptsz++ = '\0';
            else ptsz = CharNext(ptsz);
        }
    }

    /* Set the initial value */
    lstrcpyn(pcofn->tsz, pszInit, cA(pcofn->tsz));
}


/*
 *  Instance info for the dialog.
 */
typedef struct PIDI {		/* PickIcon dialog instance */
    LPTSTR ptszIconPath;	/* Which file? */
    UINT ctchIconPath;
    int iIconIndex;		/* Which icon number? */
    int *piIconIndex;
    TCH tszCurFile[MAX_PATH];	/* The path in the list box */
} PIDI, *PPIDI;

#define cxIcon GetSystemMetrics(SM_CXICON)
#define cyIcon GetSystemMetrics(SM_CYICON)

/*****************************************************************************
 *
 *  PickIcon_ppidiHdlg
 *
 *	Extract the PPIDI from an hdlg.
 *
 *****************************************************************************/

#define PickIcon_ppidiHdlg(hdlg) ((PPIDI)GetWindowLongPtr(hdlg, DWLP_USER))

/*****************************************************************************
 *
 *  PickIcon_OnMeasureItem
 *
 *	Tell USER the size of each item.
 *
 *****************************************************************************/

void PASCAL
PickIcon_OnMeasureItem(HWND hdlg, LPMEASUREITEMSTRUCT lpmi, PPIDI ppidi)
{
    lpmi->itemWidth = cxIcon + 12;
    lpmi->itemHeight = cyIcon + 4;
}

/*****************************************************************************
 *
 *  PickIcon_OnDrawItem
 *
 *	Draw an icon.
 *
 *****************************************************************************/

void PASCAL
PickIcon_OnDrawItem(HWND hdlg, LPDRAWITEMSTRUCT lpdi, PPIDI ppidi)
{
    SetBkColor(lpdi->hDC, GetSysColor((lpdi->itemState & ODS_SELECTED) ?
					COLOR_HIGHLIGHT : COLOR_WINDOW));

    /* repaint the selection state */
    ExtTextOut(lpdi->hDC, 0, 0, ETO_OPAQUE, &lpdi->rcItem, NULL, 0, NULL);

	/*
	 * Preserve icon shape when BitBlitting it to a
	 * mirrored DC.
	 */
	DWORD dwLayout=0L;
	if ((dwLayout=GetLayout(lpdi->hDC)) & LAYOUT_RTL)
	{
		SetLayout(lpdi->hDC, dwLayout|LAYOUT_BITMAPORIENTATIONPRESERVED);
	}

    /* draw the icon centered in the rectangle */
    if ((int)lpdi->itemID >= 0) {
	DrawIcon(lpdi->hDC,
		(lpdi->rcItem.left + lpdi->rcItem.right - cxIcon) / 2,
		(lpdi->rcItem.bottom + lpdi->rcItem.top - cyIcon) / 2,
		(HICON)lpdi->itemData);
    }

	/*
	 * Restore the DC to its previous layout state.
	 */
	if (dwLayout & LAYOUT_RTL)
	{
		SetLayout(lpdi->hDC, dwLayout);
	}

    /* if it has the focus, draw the focus */
    if (lpdi->itemState & ODS_FOCUS) {
	DrawFocusRect(lpdi->hDC, &lpdi->rcItem);
    }
}

/*****************************************************************************
 *
 *  PickIcon_OnDeleteItem
 *
 *	USER is nuking an item.  Clean it up.
 *
 *****************************************************************************/

#define PickIcon_OnDeleteItem(hdlg, lpdi, ppidi) \
    DestroyIcon((HICON)(lpdi)->itemData)

/*****************************************************************************
 *
 *  PickIcon_FillIconList
 *
 *	Fill in all the icons.  If the user picks a bad place, we leave
 *	garbage in the path (so he can edit the name) and leave the list
 *	box blank.
 *
 *****************************************************************************/

void PickIcon_FillIconList(HWND hdlg, PPIDI ppidi)
{
    HCURSOR hcurOld = SetCursor(LoadCursor(NULL, IDC_WAIT));
    HWND hwnd = GetDlgItem(hdlg, IDC_PICKICON);
    if (!IsWindow (hwnd))
        return;

    ListBox_SetColumnWidth(hwnd, cxIcon + 12);
    ListBox_ResetContent(hwnd);

	TCHAR szFile[cA(ppidi->tszCurFile)];
    GetDlgItemText(hdlg, IDC_PICKPATH, szFile, cA(szFile));

	// support indirect paths (e.g. %SystemRoot%\...")
	TCHAR szExpandedFile[cA(ppidi->tszCurFile)];
	ExpandEnvironmentStrings (szFile, szExpandedFile, cA(szExpandedFile));

	if (SearchPath(0, szExpandedFile, 0, cA(ppidi->tszCurFile),
				   ppidi->tszCurFile, 0)) {
ExtractIcons:
		int cIcons;
		cIcons = ExtractIconEx(ppidi->tszCurFile, 0, 0, 0, 0);
		if (cIcons) {
			HICON *rgIcons = (HICON *)LocalAlloc(LPTR, cIcons * sizeof(HICON));
			if (rgIcons) {
				cIcons = (int)ExtractIconEx(ppidi->tszCurFile, 0,
											rgIcons, NULL, cIcons);
				if (cIcons) {
					int iicon;
					SendMessage(hwnd, WM_SETREDRAW, 0, 0);
					for (iicon = 0; iicon < cIcons; iicon++) {
						ListBox_AddString(hwnd, rgIcons[iicon]);
					}
					if (ListBox_SetCurSel(hwnd, ppidi->iIconIndex) == LB_ERR) {
						ListBox_SetCurSel(hwnd, 0);
					}
					SendMessage(hwnd, WM_SETREDRAW, 1, 0);
				} else {		/* Mysteriously unable to extract */
				}
				LocalFree((HLOCAL)rgIcons);
			} else {			/* Not enough memory to load icons */
			}
		} else {			/* No icons in the file */
		}
		// if indirect path was specified (e.g. "%SystemRoot%\..."), preserve indirection
		if ((lstrcmp (szExpandedFile, szFile)            != 0) &&
			(lstrcmp (szExpandedFile, ppidi->tszCurFile) == 0))
			lstrcpy (ppidi->tszCurFile, szFile);
		SetDlgItemText(hdlg, IDC_PICKPATH, ppidi->tszCurFile);
	} else {				/* File not found */
		SC sc;
		MMCErrorBox (sc.FromWin32(ERROR_FILE_NOT_FOUND));
		goto ExtractIcons;
	}

    InvalidateRect(hwnd, 0, 1);
    SetCursor(hcurOld);
}

/*****************************************************************************
 *
 *  PickIcon_OnInitDialog
 *
 *	Dialog init.  Populate the list box with what we came in with.
 *
 *****************************************************************************/

void PASCAL
PickIcon_OnInitDialog(HWND hdlg, PPIDI ppidi)
{
    SetWindowLongPtr(hdlg, DWLP_USER, (LPARAM)ppidi);
    SetDlgItemText(hdlg, IDC_PICKPATH,
		   lstrcpyn(ppidi->tszCurFile,
			    ppidi->ptszIconPath, cA(ppidi->tszCurFile)));
    SendDlgItemMessage(hdlg, IDC_PICKPATH, EM_LIMITTEXT,
		       ppidi->ctchIconPath, 0);
    PickIcon_FillIconList(hdlg, ppidi);
}

/*****************************************************************************
 *
 *  PickIcon_OnBrowse
 *
 *****************************************************************************/

void PASCAL
PickIcon_OnBrowse(HWND hdlg, PPIDI ppidi)
{
    DWORD dw;
    COFN cofn;
    InitOpenFileName(hdlg, &cofn, IDS_ICONFILES, ppidi->tszCurFile);
    dw = GetFileAttributes(ppidi->tszCurFile);
    if (dw == 0xFFFFFFFF || (dw & FILE_ATTRIBUTE_DIRECTORY)) {
	cofn.tsz[0] = '\0';
    }

    if (GetOpenFileName(&cofn.ofn)) {
        SetDlgItemText(hdlg, IDC_PICKPATH, cofn.tsz);
        SendMessage(hdlg, DM_SETDEFID, IDOK, 0);
	PickIcon_FillIconList(hdlg, ppidi);
    }
}

/*****************************************************************************
 *
 *  PickIcon_NameChange
 *
 *  Determine whether the thing in the edit control doesn't match the
 *  thing whose icons we are showing.
 *
 *****************************************************************************/

BOOL PASCAL
PickIcon_NameChange(HWND hdlg, PPIDI ppidi)
{
    TCH tszBuffer[MAX_PATH];
    GetDlgItemText(hdlg, IDC_PICKPATH, tszBuffer, cA(tszBuffer));
    return lstrcmpi(tszBuffer, ppidi->tszCurFile);
}

/*****************************************************************************
 *
 *  PickIcon_OnOk
 *
 *	If the name has changed, treat this as a "Okay, now reload
 *	the icons" rather than "Okay, I'm finished".
 *
 *****************************************************************************/

void PASCAL
PickIcon_OnOk(HWND hdlg, PPIDI ppidi)
{
    if (PickIcon_NameChange(hdlg, ppidi)) {
	PickIcon_FillIconList(hdlg, ppidi);
    } else {
	int iIconIndex = (int)SendDlgItemMessage(hdlg, IDC_PICKICON,
						LB_GETCURSEL, 0, 0L);
	if (iIconIndex >= 0) {	/* We have an icon */
	    *ppidi->piIconIndex = iIconIndex;
	    lstrcpyn(ppidi->ptszIconPath, ppidi->tszCurFile,
		     ppidi->ctchIconPath);
	    EndDialog(hdlg, 1);
	} else {		/* No icon, act like cancel */
	    EndDialog(hdlg, 0);
	}
    }
}

/*****************************************************************************
 *
 *  PickIcon_OnCommand
 *
 *****************************************************************************/

void PASCAL
PickIcon_OnCommand(HWND hdlg, int id, UINT codeNotify, PPIDI ppidi)
{
    switch (id) {
    case IDOK: PickIcon_OnOk(hdlg, ppidi); break;
    case IDCANCEL: EndDialog(hdlg, 0); break;

    case IDC_PICKBROWSE: PickIcon_OnBrowse(hdlg, ppidi); break;

    /*
     *	When the name changes, remove the selection highlight.
     */
    case IDC_PICKPATH:
		if (PickIcon_NameChange(hdlg, ppidi)) {
			SendDlgItemMessage(hdlg, IDC_PICKICON, LB_SETCURSEL, (WPARAM)-1, 0);
		}
		break;

    case IDC_PICKICON:
		if (codeNotify == LBN_DBLCLK) {
			PickIcon_OnOk(hdlg, ppidi);
		}
		break;
    }
}

/*****************************************************************************
 *
 *  PickIcon_DlgProc
 *
 *	Dialog procedure.
 *
 *****************************************************************************/

/*
 * The HANDLE_WM_* macros weren't designed to be used from a dialog
 * proc, so we need to handle the messages manually.  (But carefully.)
 */

INT_PTR EXPORT
PickIcon_DlgProc(HWND hdlg, UINT wm, WPARAM wParam, LPARAM lParam)
{
    PPIDI ppidi = PickIcon_ppidiHdlg(hdlg);

    switch (wm) {
    case WM_INITDIALOG: PickIcon_OnInitDialog(hdlg, (PPIDI)lParam); break;

    case WM_COMMAND:
	PickIcon_OnCommand(hdlg, (int)GET_WM_COMMAND_ID(wParam, lParam),
				 (UINT)GET_WM_COMMAND_CMD(wParam, lParam),
				 ppidi);
	break;

    case WM_DRAWITEM:
	PickIcon_OnDrawItem(hdlg, (LPDRAWITEMSTRUCT)lParam, ppidi);
	break;

    case WM_MEASUREITEM:
	PickIcon_OnMeasureItem(hdlg, (LPMEASUREITEMSTRUCT)lParam, ppidi);
	break;

    case WM_DELETEITEM:
	PickIcon_OnDeleteItem(hdlg, (LPDELETEITEMSTRUCT)lParam, ppidi);
	break;

    default: return 0;	/* Unhandled */
    }
    return 1;		/* Handled */
}


/*****************************************************************************
 *
 *  PickIconDlg
 *
 *	Ask the user to pick an icon.
 *
 *	hwnd - owner window
 *	ptszIconPath - (in) default icon file
 *		      (out) chosen icon file
 *	ctchIconPath - size of ptszIconPath buffer
 *	piIconIndex - (in) default icon index
 *		      (out) index of chosen icon
 *
 *	If the dialog box is cancelled, then no values are changed.
 *
 *****************************************************************************/

MMCBASE_API INT_PTR PASCAL
PickIconDlg(HWND hwnd, LPTSTR ptszIconPath, UINT ctchIconPath, int *piIconIndex)
{
    PIDI pidi;

    pidi.ptszIconPath = ptszIconPath;
    pidi.ctchIconPath = ctchIconPath;
    pidi.piIconIndex = piIconIndex;
    pidi.iIconIndex = *piIconIndex;

    return DialogBoxParam(SC::GetHinst(), MAKEINTRESOURCE(IDD_PICKICON), hwnd,
			  PickIcon_DlgProc, (LPARAM)&pidi);
}
