/* * template - Dialog box property sheet for "Templates" */ #include "tweakui.h" #define ctchProgMax 80 #pragma BEGIN_CONST_DATA const static DWORD CODESEG rgdwHelp[] = { IDC_TEMPLATETEXT, IDH_TEMPLATE, IDC_TEMPLATE, IDH_TEMPLATE, IDC_LVDELETE, IDH_TEMPLATEDEL, 0, 0, }; #pragma END_CONST_DATA typedef struct TI { /* ti - template info */ BYTE isi; /* Index to state icon */ HKEY hkRoot; /* Root for locating the key */ /* HKCR or HKCR\CLSID */ TCH tszExt[10]; /* Filename extension (for icon refresh) */ TCH tszKey[ctchKeyMax + 6]; /* 6 = strlen("\\CLSID") */ } TI, *PTI; #define itiPlvi(plvi) ((UINT)(plvi)->lParam) #define ptiIti(iti) (&ptdii->pti[iti]) #define ptiPlvi(plvi) ptiIti(itiPlvi(plvi)) typedef struct TDII { Declare_Gxa(TI, ti); WNDPROC wpTemplate; BOOL fRundll; /* Need to run Rundll on Apply */ } TDII, *PTDII; TDII tdii; #define ptdii (&tdii) /***************************************************************************** * * ptszStrRChr * * Get the rightmost occurrence. * *****************************************************************************/ PTSTR PASCAL ptszStrRChr(PCTSTR ptsz, TCH tch) { PTSTR ptszRc = 0; for (ptsz = ptszStrChr(ptsz, tch); ptsz; ptsz = ptszStrChr(ptsz + 1, tch)) { ptszRc = (PTSTR)ptsz; } return ptszRc; } /***************************************************************************** * * ptszFilenameCqn * * Get the filename part of a cqn. * *****************************************************************************/ PTSTR PASCAL ptszFilenameCqn(PCTSTR cqn) { PTSTR ptsz = ptszStrRChr(cqn, TEXT('\\')); return ptsz ? ptsz + 1 : (PTSTR)cqn; } /***************************************************************************** * * Path_Append * * Append a filename to a directory, inserting a backslash * as necessary. * *****************************************************************************/ void Path_Append(LPTSTR ptszDir, LPCTSTR ptszFile) { ptszDir = TweakUi_TrimTrailingBs(ptszDir); *ptszDir++ = TEXT('\\'); lstrcpy(ptszDir, ptszFile); } /***************************************************************************** * * Template_NudgeExplorer * * Explorer doesn't recognize changes to templates until an application * terminates, so we simply execute Rundll spuriously. It realizes that * there is nothing to do and exits. This exit triggers Explorer to * rebuild the filename extension list. * * This nudge doesn't work on NT 5, so we also have to nuke the * ShellNew cache. * *****************************************************************************/ #pragma BEGIN_CONST_DATA ConstString(c_tszRundll, "rundll32"); #pragma END_CONST_DATA void PASCAL Template_NudgeExplorer(void) { WinExec(c_tszRundll, SW_HIDE); RegDeleteTree(g_hkCUSMWCV, TEXT("Explorer\\Discardable\\PostSetup\\ShellNew")); } /***************************************************************************** * * Template_SubkeyExists * *****************************************************************************/ BOOL PASCAL Template_SubkeyExists(HKEY hk, PCTSTR ptszSubkey) { return GetRegStr(hk, 0, ptszSubkey, 0, 0); } /***************************************************************************** * * Template_AddTemplateInfo * * pti must be the next ti in the array (i.e., Misc_AllocPx) * * Returns the icon index, if successful, or -1 on error. * *****************************************************************************/ int PASCAL Template_AddTemplateInfo(HWND hwnd, PCTSTR ptszExt, PTI pti, BOOL fCheck) { int iRc; SHFILEINFO sfi; if (SHGetFileInfo(ptszExt, 0, &sfi, cbX(sfi), SHGFI_TYPENAME | SHGFI_USEFILEATTRIBUTES | SHGFI_SYSICONINDEX | SHGFI_SMALLICON)) { pti->isi = fCheck + 1; iRc = LV_AddItem(hwnd, ptdii->cti++, sfi.szTypeName, sfi.iIcon, fCheck); } else { iRc = -1; } return iRc; } /***************************************************************************** * * TemplateCallbackInfo * * Information handed to a template callback. * * hk - The ptszShellNew subkey itself * *****************************************************************************/ typedef BOOL (PASCAL *TCICALLBACK)(struct TCI *ptci, HKEY hk, PCTSTR ptszShellNew); typedef struct TCI { TCICALLBACK pfn; PTI pti; /* Template info containing result */ HWND hwnd; /* Listview window handle */ PCTSTR ptszExt; /* The .ext being studied */ } TCI, *PTCI; /***************************************************************************** * * Template_CheckShellNew * * Look for the ShellNew stuff (or ShellNew- if temporarily disabled). * * Returns boolean success/failure. * * If ptszExt exists, then we create the key, too. * *****************************************************************************/ BOOL PASCAL Template_CheckShellNew(PTCI ptci, HKEY hkBase, PCTSTR ptszShellNew) { HKEY hk; BOOL fRc; if (_RegOpenKey(hkBase, ptszShellNew, &hk) == 0) { if (Template_SubkeyExists(hk, c_tszNullFile) || Template_SubkeyExists(hk, c_tszFileName) || Template_SubkeyExists(hk, c_tszCommand) || Template_SubkeyExists(hk, c_tszData)) { fRc = ptci->pfn(ptci, hk, ptszShellNew); } else { fRc = 0; } RegCloseKey(hk); } else { fRc = 0; } return fRc; } /***************************************************************************** * * Template_AddHkeyTemplate * * We have a candidate location for a ShellNew. * * There is some weirdness here. The actual ShellNew can live at * a sublevel. For example, the ".doc" extension contains several * ShellNew's: * * HKCR\.doc = WordPad.Document.1 * HKCR\.doc\WordDocument\ShellNew * HKCR\.doc\Word.Document.6\ShellNew * HKCR\.doc\WordPad.Document.1\ShellNew * * Based on the main value (HKCR\.doc), we choose to use the template * for "Wordpad.Document.1". * * pti->hkRoot - hkey at which to start (either HKCR or HKCR\CLSID) * pti->tszKey - subkey to open (.ext or {guid}) * * pti->hkRoot\pti->tszKey = actual key to open * * hkBase = the key at hkRoot\ptszKey * hk = the key where ShellNew actually lives * *****************************************************************************/ BOOL PASCAL Template_AddHkeyTemplate(PTCI ptci) { HKEY hkBase; BOOL fRc; PTI pti = ptci->pti; if (_RegOpenKey(pti->hkRoot, pti->tszKey, &hkBase) == 0) { TCH tszProg[ctchProgMax+1]; if (GetRegStr(hkBase, 0, 0, tszProg, cbX(tszProg))) { HKEY hk = NULL; if (_RegOpenKey(hkBase, tszProg, &hk) == 0) { lstrcatnBsA(pti->tszKey, tszProg); } else { _RegOpenKey(hkBase, 0, &hk); /* hk = AddRef(hkBase) */ } if (hk) { /* Now open the ShellNew subkey or the ShellNew- subkey */ fRc = fLorFF(Template_CheckShellNew(ptci, hk, c_tszShellNew), Template_CheckShellNew(ptci, hk, c_tszShellNewDash)); RegCloseKey(hk); } else { fRc = 0; } } else { fRc = 0; } } else { fRc = 0; } return fRc; } /***************************************************************************** * * Template_LocateExtension * * We have a filename extension (ptci->ptszExt). * * Get its program id (progid). * * We look in two places. If the progid has a registered CLSID * (HKCR\progid\CLSID), then we look in HKCR\CLSID\{guid}. * * If the CLSID fails to locate anything, then we look under * HKCR\.ext directly. * *****************************************************************************/ BOOL PASCAL Template_LocateExtension(PTCI ptci) { BOOL fRc; PTI pti = ptci->pti = (PTI)Misc_AllocPx(&ptdii->gxa); if (pti) { TCH tszProg[ctchProgMax + ctchKeyMax + 6]; /* 6 = strlen(\\CLSID) */ lstrcpyn(pti->tszExt, ptci->ptszExt, cA(pti->tszExt)); if (GetRegStr(hkCR, ptci->ptszExt, 0, tszProg, cbCtch(ctchProgMax)) && tszProg[0]) { /* * Make sure the progid exists, or we end up putting garbage * into the listview. */ if (RegKeyExists(hkCR, tszProg)) { /* * Is this an OLE class? */ lstrcatnBsA(tszProg, c_tszClsid); if (GetRegStr(hkCR, tszProg, 0, pti->tszKey, cbX(pti->tszKey))) { pti->hkRoot = pcdii->hkClsid; fRc = Template_AddHkeyTemplate(ptci); } else { fRc = 0; } /* * If we haven't succeeded yet, then try under the extension * itself. */ if (!fRc) { pti->hkRoot = hkCR; lstrcpyn(pti->tszKey, ptci->ptszExt, cA(pti->tszKey)); fRc = Template_AddHkeyTemplate(ptci); } } else { fRc = 0; } } else { fRc = 0; } } else { fRc = 0; } return fRc; } /***************************************************************************** * * Template_AddTemplates_AddMe * * Add any templates that exist. * * For each filename extension, get its corresponding class. * Then ask Template_LocateExtension to do the rest. * *****************************************************************************/ BOOL PASCAL Template_AddTemplates_AddMe(PTCI ptci, HKEY hk, PCTSTR ptszShellNew) { return Template_AddTemplateInfo(ptci->hwnd, ptci->ptszExt, ptci->pti, ptszShellNew == c_tszShellNew) + 1; } /***************************************************************************** * * Template_AddTemplates * * Add any templates that exist. * * For each filename extension, get its corresponding class. * Then ask Template_LocateExtension to do the rest. * *****************************************************************************/ void PASCAL Template_AddTemplates(HWND hwnd) { int i; TCI tci; TCH tszExt[10]; tci.pfn = Template_AddTemplates_AddMe; tci.hwnd = hwnd; tci.ptszExt = tszExt; HCURSOR hcurPrev = SetCursor(LoadCursor(0, IDC_WAIT)); for (i = 0; ; i++) { switch (RegEnumKey(HKEY_CLASSES_ROOT, i, tszExt, cbX(tszExt))) { case ERROR_SUCCESS: /* * Don't show ".lnk" in the templates list because it's weird. */ if (tszExt[0] == TEXT('.') && lstrcmpi(tszExt, c_tszDotLnk)) { Template_LocateExtension(&tci); } break; case ERROR_MORE_DATA: /* Can't be a .ext if > 10 */ break; default: goto endenum; } } endenum:; SetCursor(hcurPrev); } /***************************************************************************** * * File template callback info * *****************************************************************************/ typedef struct FTCI { TCI tci; PCTSTR ptszSrc; PCTSTR ptszDst; PCTSTR ptszLastBS; PCTSTR ptszShellNew; } FTCI, *PFTCI; /***************************************************************************** * * Template_CopyFile * * Copy a file with the hourglass. * *****************************************************************************/ BOOL PASCAL Template_CopyFile(PFTCI pftci) { HCURSOR hcurPrev; BOOL fRc; hcurPrev = SetCursor(LoadCursor(0, MAKEINTRESOURCE(IDC_WAIT))); fRc = CopyFile(pftci->ptszSrc, pftci->ptszDst, 0); SetCursor(hcurPrev); return fRc; } /***************************************************************************** * * Template_AddFileTemplate_CheckMe * * Make sure the key isn't a Command key. * *****************************************************************************/ BOOL PASCAL Template_AddFileTemplate_CheckMe(PTCI ptci, HKEY hk, PCTSTR ptszShellNew) { PFTCI pftci = (PFTCI)ptci; pftci->ptszShellNew = ptszShellNew; if (Template_SubkeyExists(hk, c_tszCommand)) { ptci->ptszExt = 0; } return 1; } /***************************************************************************** * * Template_IsTemplatable * * Determine whether there is an application that can handle this * extension. We assume it's okay if the item is not self-executing. * *****************************************************************************/ BOOL PASCAL Template_IsTemplatable(PCTSTR ptszFile) { LONG cb; TCH tszShort[MAX_PATH]; DWORD ctch; ctch = GetFullPathName(ptszFile, cA(tszShort), tszShort, NULL); if (ctch && ctch < MAX_PATH) { /* If this fails, just proceed with the value from GFPN */ GetShortPathName(ptszFile, tszShort, cA(tszShort)); TCH tszExe[MAX_PATH]; if (FindExecutable(tszShort, NULL, tszExe) >= (HINSTANCE)IntToPtr(HINSTANCE_ERROR)) { return lstrcmpi(tszExe, tszShort) != 0; } } return 0; } /***************************************************************************** * * Template_ReplaceTemplate * * Replace the current template with the new one. * *****************************************************************************/ UINT PASCAL Template_ReplaceTemplate(PFTCI pftci) { #define ptci (&pftci->tci) UINT id; if (ptci->ptszExt) { if (MessageBoxId(GetParent(ptci->hwnd), IDS_CONFIRMNEWTEMPLATE, pftci->ptszSrc, MB_YESNO | MB_DEFBUTTON2 | MB_SETFOREGROUND) == IDYES) { HKEY hk; lstrcatnBsA(ptci->pti->tszKey, pftci->ptszShellNew); if (_RegOpenKey(ptci->pti->hkRoot, ptci->pti->tszKey, &hk) == 0) { if (Template_CopyFile(pftci)) { RegDeleteValue(hk, c_tszNullFile); RegDeleteValue(hk, c_tszData); RegSetValuePtsz(hk, c_tszFileName, pftci->ptszLastBS+1); id = 0; } else { id = IDS_COPYFAIL; } RegCloseKey(hk); } else { id = IDS_REGFAIL; } } else { id = 0; } } else { id = IDS_CANNOTTEMPLATE; } return id; } #undef ptci /***************************************************************************** * * Template_AddFileTemplate * * Add the file as a new template type. * *****************************************************************************/ UINT PASCAL Template_AddFileTemplate(HWND hwnd, PCTSTR ptszSrc) { #define pftci (&ftci) UINT id; FTCI ftci; pftci->tci.pfn = Template_AddFileTemplate_CheckMe; pftci->tci.hwnd = hwnd; pftci->ptszSrc = ptszSrc; pftci->ptszLastBS = ptszFilenameCqn(ptszSrc) - 1; /* -> \filename.ext */ if (pftci->ptszLastBS) { pftci->tci.ptszExt = ptszStrRChr(pftci->ptszLastBS, '.'); /* -> .ext */ if (pftci->tci.ptszExt) { HKEY hk; if (_RegOpenKey(hkCR, pftci->tci.ptszExt, &hk) == 0) { if (Template_IsTemplatable(ptszSrc)) { PTI pti; TCH tszDst[MAX_PATH]; pftci->ptszDst = tszDst; SHGetPathFromIDList(pcdii->pidlTemplates, tszDst); lstrcatnBsA(tszDst, pftci->ptszLastBS+1); /* Snoop at the next pti to ensure we can get it later */ pti = (PTI)Misc_AllocPx(&ptdii->gxa); if (pti) { if (Template_LocateExtension(&pftci->tci)) { id = Template_ReplaceTemplate(pftci); } else { if (Template_CopyFile(pftci)) { HKEY hk2; if (RegCreateKey(hk, c_tszShellNew, &hk2) == 0) { RegSetValuePtsz(hk2, c_tszFileName, pftci->ptszLastBS+1); RegCloseKey(hk2); Misc_LV_SetCurSel(hwnd, Template_AddTemplateInfo(hwnd, pftci->tci.ptszExt, pti, 1)); Template_NudgeExplorer(); id = 0; /* No problemo */ } else { id = IDS_REGFAIL; } } else { id = IDS_COPYFAIL; } } } else { id = 0; /* out of memory! */ } } else { id = IDS_BADEXT; } RegCloseKey(hk); } else { id = IDS_BADEXT; } } else { id = IDS_BADEXT; } } else { id = 0; /* This can't happen! */ } return id; } /***************************************************************************** * * Tools_Template_OnDropFiles * * Put the file into the template edit control. * *****************************************************************************/ void PASCAL Tools_Template_OnDropFiles(HWND hwnd, HDROP hdrop) { if (DragQueryFile(hdrop, (UINT)-1, 0, 0) == 1) { UINT id; TCH tszSrc[MAX_PATH]; DragQueryFile(hdrop, 0, tszSrc, cA(tszSrc)); id = Template_AddFileTemplate(hwnd, tszSrc); if (id) { MessageBoxId(GetParent(hwnd), id, tszSrc, MB_OK | MB_SETFOREGROUND); } } else { MessageBoxId(GetParent(hwnd), IDS_TOOMANY, g_tszName, MB_OK | MB_SETFOREGROUND); } DragFinish(hdrop); } /***************************************************************************** * * Tools_Template_WndProc * * Subclass procedure so we can handle drag-drop to the template * edit control. * *****************************************************************************/ LRESULT EXPORT Tools_Template_WndProc(HWND hwnd, UINT wm, WPARAM wParam, LPARAM lParam) { switch (wm) { case WM_DROPFILES: Tools_Template_OnDropFiles(hwnd, (HDROP)wParam); return 0; } return CallWindowProc(ptdii->wpTemplate, hwnd, wm, wParam, lParam); } /***************************************************************************** * * Template_GetIcon * * Produce the icon associated with an item. This is called when * we need to rebuild the icon list after the icon cache has been * purged. * *****************************************************************************/ int PASCAL Template_GetIcon(LPARAM iti) { SHFILEINFO sfi; PTI pti = ptiIti(iti); sfi.iIcon = 0; OutputDebugString(pti->tszExt); SHGetFileInfo(pti->tszExt, 0, &sfi, cbX(sfi), SHGFI_USEFILEATTRIBUTES | SHGFI_SYSICONINDEX | SHGFI_SMALLICON); return sfi.iIcon; } /***************************************************************************** * * Template_OnInitDialog * * Turn the "drop a file here" gizmo into a valid drop target. * *****************************************************************************/ BOOL PASCAL Template_OnInitDialog(HWND hwnd) { ptdii->wpTemplate = SubclassWindow(hwnd, Tools_Template_WndProc); DragAcceptFiles(hwnd, 1); if (Misc_InitPgxa(&ptdii->gxa, cbX(TI))) { Template_AddTemplates(hwnd); } return 1; } /***************************************************************************** * * Template_OnDelete * * Really nuke it. The interaction between this and adding a new * template is sufficiently weird that I don't want to try to do * delayed-action. * *****************************************************************************/ void PASCAL Template_OnDelete(HWND hwnd, int iItem) { LV_ITEM lvi; HKEY hk; PTI pti; TCH tszDesc[MAX_PATH]; lvi.pszText = tszDesc; lvi.cchTextMax = cA(tszDesc); Misc_LV_GetItemInfo(hwnd, &lvi, iItem, LVIF_PARAM | LVIF_TEXT); pti = ptiPlvi(&lvi); if (MessageBoxId(GetParent(hwnd), IDS_TEMPLATEDELETEWARN, lvi.pszText, MB_YESNO | MB_DEFBUTTON2) == IDYES) { if (_RegOpenKey(pti->hkRoot, pti->tszKey, &hk) == 0) { RegDeleteTree(hk, c_tszShellNewDash); RegDeleteTree(hk, c_tszShellNew); ListView_DeleteItem(hwnd, iItem); Misc_LV_EnsureSel(hwnd, iItem); Common_SetDirty(GetParent(hwnd)); RegCloseKey(hk); } } } /***************************************************************************** * * Template_OnSelChange * * Disable the Remove button if we can't remove the thing. * *****************************************************************************/ void PASCAL Template_OnSelChange(HWND hwnd, int iItem) { PTI pti = ptiIti(Misc_LV_GetParam(hwnd, iItem)); HKEY hk; BOOL fEnable; if (_RegOpenKey(pti->hkRoot, pti->tszKey, &hk) == 0) { if (GetRegStr(hk, pti->isi == isiUnchecked ? c_tszShellNewDash : c_tszShellNew, c_tszCommand, 0, 0)) { fEnable = 0; } else { fEnable = 1; } RegCloseKey(hk); } else { fEnable = 0; } EnableWindow(GetDlgItem(GetParent(hwnd), IDC_LVDELETE), fEnable); } /***************************************************************************** * * Template_OnApply * *****************************************************************************/ void PASCAL Template_OnApply(HWND hdlg) { HWND hwnd = GetDlgItem(hdlg, IDC_TEMPLATE); int cItems = ListView_GetItemCount(hwnd); BOOL fDirty; LV_ITEM lvi; fDirty = 0; for (lvi.iItem = 0; lvi.iItem < cItems; lvi.iItem++) { PTI pti; lvi.stateMask = LVIS_STATEIMAGEMASK; Misc_LV_GetItemInfo(hwnd, &lvi, lvi.iItem, LVIF_PARAM | LVIF_STATE); pti = ptiPlvi(&lvi); if (pti->isi != isiPlvi(&lvi)) { PCTSTR ptszFrom, ptszTo; if (pti->isi == isiUnchecked) { ptszFrom = c_tszShellNewDash; ptszTo = c_tszShellNew; } else { ptszFrom = c_tszShellNew; ptszTo = c_tszShellNewDash; } if (Misc_RenameReg(pti->hkRoot, pti->tszKey, ptszFrom, ptszTo)) { pti->isi = isiPlvi(&lvi); } fDirty = 1; } } if (fDirty) { Template_NudgeExplorer(); } } /***************************************************************************** * * Template_OnDestroy * * Free the memory we allocated. * *****************************************************************************/ void PASCAL Template_OnDestroy(HWND hdlg) { Misc_FreePgxa(&ptdii->gxa); } /***************************************************************************** * * Oh yeah, we need this too. * *****************************************************************************/ #pragma BEGIN_CONST_DATA LVCI lvciTemplate[] = { { IDC_LVDELETE, Template_OnDelete }, { 0, 0 }, }; LVV lvvTemplate = { 0, /* Template_OnCommand */ 0, /* Template_OnInitContextMenu */ 0, /* Template_Dirtify */ Template_GetIcon, Template_OnInitDialog, Template_OnApply, Template_OnDestroy, Template_OnSelChange, 3, rgdwHelp, 0, /* Double-click action */ lvvflIcons | /* We need icons */ lvvflCanCheck | /* And check boxes */ lvvflCanDelete, /* and you can delete them too */ lvciTemplate, }; #pragma END_CONST_DATA /***************************************************************************** * * Our window procedure. * *****************************************************************************/ INT_PTR EXPORT Template_DlgProc(HWND hdlg, UINT wm, WPARAM wParam, LPARAM lParam) { return LV_DlgProc(&lvvTemplate, hdlg, wm, wParam, lParam); }