/* * mycomp - Dialog box property sheet for "My Computer" * * For now, we display only Drives. */ #include "tweakui.h" #pragma BEGIN_CONST_DATA const static DWORD CODESEG rgdwHelp[] = { IDC_ICONLVTEXT, IDH_GROUP, IDC_ICONLVTEXT2, IDH_MYCOMP, IDC_ICONLV, IDH_MYCOMP, IDC_FLDGROUP, IDH_GROUP, IDC_FLDNAMETXT, IDH_FOLDERNAME, IDC_FLDNAMELIST, IDH_FOLDERNAME, IDC_FLDLOCTXT, IDH_FOLDERNAME, IDC_FLDLOC, IDH_FOLDERNAME, IDC_FLDCHG, IDH_FOLDERNAME, 0, 0, }; #pragma END_CONST_DATA /***************************************************************************** * * CFolderDesc - Describes a single special folder * *****************************************************************************/ #define MAKEKL(nm) \ KL const c_kl##nm = { &pcdii->hkCUExplorer, \ c_tszUserShellFolders, c_tsz##nm } MAKEKL(Desktop); MAKEKL(Programs); MAKEKL(Personal); MAKEKL(Favorites); MAKEKL(Startup); MAKEKL(Recent); MAKEKL(SendTo); MAKEKL(StartMenu); MAKEKL(Templates); KL const c_klMyMusic = { &pcdii->hkCUExplorer, c_tszUserShellFolders, TEXT("My Music") }; KL const c_klMyVideo = { &pcdii->hkCUExplorer, c_tszUserShellFolders, TEXT("My Video") }; KL const c_klMyPictures = { &pcdii->hkCUExplorer, c_tszUserShellFolders, TEXT("My Pictures") }; #undef MAKEKL KL const c_klProgramFiles = { &g_hkLMSMWCV, 0, c_tszProgramFilesDir }; KL const c_klCommonFiles = { &g_hkLMSMWCV, 0, c_tszCommonFilesDir }; KL const c_klSourcePath = { &g_hkLMSMWCV, c_tszSetup, c_tszSourcePath }; /* * HACKHACK - Fake some private CSIDL's * by stealing the various CSIDL_COMMON_* values. */ enum { CSIDL_SOURCEPATH = CSIDL_COMMON_STARTUP, }; /* * Declare as a struct so you can initialize it statically. */ struct CFolderDesc { /* fldd */ PIDL GetPidl() const; BOOL SetValue(LPTSTR ptsz) const; /* returns fNeedLogoff */ inline int GetFriendlyName(LPTSTR pszBuf, int cch) const { return LoadString(hinstCur, _csidl + IDS_FOLDER_BASE, pszBuf, cch); } UINT _csidl; PKL _pkl; /* * These are really private members, but you can't say "private" * in a struct definition if you want it to be statically initializable. */ static BOOL _UnexpandEnvironmentString(LPTSTR ptsz, LPCTSTR ptszEnv); static void _SetUserShellFolder(LPTSTR ptsz, LPCTSTR ptszSubkey); }; /***************************************************************************** * * CFolderDesc::GetPidl * * Wrapper around SHGetSpecialFolderLocation that also knows how * to read our hacky values. * *****************************************************************************/ PIDL CFolderDesc::GetPidl() const { HRESULT hres; PIDL pidl; TCHAR tszPath[MAX_PATH]; switch (_csidl) { case CSIDL_PROGRAM_FILES: case CSIDL_PROGRAM_FILES_COMMON: case CSIDL_SOURCEPATH: if (GetStrPkl(tszPath, cbX(tszPath), _pkl)) { pidl = pidlSimpleFromPath(tszPath); } else { pidl = NULL; } break; default: if (SUCCEEDED(SHGetSpecialFolderLocation(NULL, _csidl, &pidl))) { } else { pidl = NULL; } break; } return pidl; } /***************************************************************************** * * CFolderDesc::_UnexpandEnvironmentString * * If the string begins with the value of the environment string, * then change it to said string. * * Example: * In: "C:\WINNT\SYSTEM32\FOO.TXT", "%SystemRoot%" * Out: "%SystemRoot%\SYSTEM32\FOO.TXT" * *****************************************************************************/ BOOL CFolderDesc::_UnexpandEnvironmentString(LPTSTR ptsz, LPCTSTR ptszEnv) { TCHAR tszEnv[MAX_PATH]; DWORD ctch; BOOL fRc; /* * Note that NT ExpandEnvironmentStrings returns the wrong * value, so we can't rely on it. */ ExpandEnvironmentStrings(ptszEnv, tszEnv, cA(tszEnv)); ctch = lstrlen(tszEnv); /* * Source must be at least as long as the env string for * us to have a chance of succeeding. This check avoids * accidentally reading past the end of the source. */ if ((DWORD)lstrlen(ptsz) >= ctch) { if (CompareString(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE, tszEnv, ctch, ptsz, ctch) == 2) { int ctchEnv = lstrlen(ptszEnv); /* * Must use hmemcpy to avoid problems with overlap. */ hmemcpy(ptsz + ctchEnv, ptsz + ctch, cbCtch(1 + lstrlen(ptsz + ctch))); hmemcpy(ptsz, ptszEnv, ctchEnv); fRc = 1; } else { fRc = 0; } } else { fRc = 0; } return fRc; } /***************************************************************************** * * CFolderDesc::_SetUserShellFolder * * Don't use REG_EXPAND_SZ if the shell doesn't support it. * *****************************************************************************/ void CFolderDesc::_SetUserShellFolder(LPTSTR ptsz, LPCTSTR ptszSubkey) { HKEY hk; if (g_fShellSz) { if (!_UnexpandEnvironmentString(ptsz, TEXT("%USERPROFILE%")) && !_UnexpandEnvironmentString(ptsz, TEXT("%SystemRoot%"))) { } } if (RegCreateKey(pcdii->hkCUExplorer, c_tszUserShellFolders, &hk) == 0) { RegSetValueEx(hk, ptszSubkey, 0, g_fShellSz ? REG_EXPAND_SZ : REG_SZ, (LPBYTE)ptsz, cbCtch(1 + lstrlen(ptsz))); RegCloseKey(hk); } } /***************************************************************************** * * CFolderDesc::SetValue * * Stash the puppy. * * Returns nonzero if logoff required. * *****************************************************************************/ BOOL CFolderDesc::SetValue(LPTSTR ptsz) const { TCHAR tszDefault[MAX_PATH]; UINT ctch; BOOL fNeedLogoff = FALSE; /* * Is it already in the default location? * * Note that the special gizmos don't have default * locations. */ ctch = GetWindowsDirectory(tszDefault, cA(tszDefault)); if (ctch && tszDefault[ctch - 1] != TEXT('\\')) { tszDefault[ctch++] = TEXT('\\'); } if (LoadString(hinstCur, _csidl + IDS_DEFAULT_BASE, &tszDefault[ctch], cA(tszDefault) - ctch)) { if (lstrcmpi(tszDefault, ptsz) == 0) { /* * In default location. */ DelPkl(_pkl); } else { /* * In other location. * * Note that we cannot use SetStrPkl here, because * we need to set the value type to REG_EXPAND_SZ. * */ _SetUserShellFolder(ptsz, _pkl->ptszSubkey); } fNeedLogoff = TRUE; } else { SetStrPkl(_pkl, ptsz); /* * On NT5, Program Files and Common Files are CSIDLs, * and Program Files is an environment variable! */ if (g_fNT5 && (_csidl == CSIDL_PROGRAM_FILES || _csidl == CSIDL_PROGRAM_FILES_COMMON)) { fNeedLogoff = TRUE; } } return fNeedLogoff; } /***************************************************************************** * * CSpecialFolders -- Wangle the "special folders" combobox * *****************************************************************************/ static const CFolderDesc c_rgfldd[] = { { CSIDL_DESKTOPDIRECTORY, &c_klDesktop }, { CSIDL_PROGRAMS, &c_klPrograms }, { CSIDL_PERSONAL, &c_klPersonal }, { CSIDL_FAVORITES, &c_klFavorites }, { CSIDL_STARTUP, &c_klStartup }, { CSIDL_RECENT, &c_klRecent }, { CSIDL_SENDTO, &c_klSendTo }, { CSIDL_STARTMENU, &c_klStartMenu }, { CSIDL_TEMPLATES, &c_klTemplates }, { CSIDL_MYMUSIC, &c_klMyMusic }, { CSIDL_MYVIDEO, &c_klMyVideo }, { CSIDL_MYPICTURES, &c_klMyPictures }, { CSIDL_PROGRAM_FILES, &c_klProgramFiles }, { CSIDL_PROGRAM_FILES_COMMON, &c_klCommonFiles }, { CSIDL_SOURCEPATH, &c_klSourcePath }, }; #define cfldd cA(c_rgfldd) class CSpecialFolders { public: void Init(HWND hwndCombo, HWND hwndText); void Resync(); void Destroy(); BOOL Apply(); /* returns fNeedLogoff */ void ChangeFolder(HWND hdlg); private: typedef struct FOLDERINSTANCE { BOOL fEdited; PIDL pidl; } FINST, *PFINST; static int CALLBACK _ChangeFolder_Callback(HWND hwnd, UINT wm, LPARAM lp, LPARAM lpRef); HWND _hwndCombo; HWND _hwndText; /* IDC_FLDLOC */ BOOL _fWarned; /* Did we warn about changing folders? */ PIDL _pidlEditing; /* Which one is the user editing? */ FINST _rgfinst[cfldd]; }; /***************************************************************************** * * CSpecialFolders::Destroy * * Free the memory. * *****************************************************************************/ void CSpecialFolders::Destroy() { UINT i; for (i = 0; i < cfldd; i++) { _rgfinst[i].fEdited = 0; if (_rgfinst[i].pidl) { Ole_Free(_rgfinst[i].pidl); _rgfinst[i].pidl = 0; } } } /***************************************************************************** * * CSpecialFolders::Reset * *****************************************************************************/ void CSpecialFolders::Init(HWND hwndCombo, HWND hwndText) { _hwndCombo = hwndCombo; _hwndText = hwndText; /* * Free the old memory, if any. */ Destroy(); /* * Set up the new combobox. */ ComboBox_ResetContent(_hwndCombo); UINT i; for (i = 0; i < cfldd; i++) { _rgfinst[i].pidl = c_rgfldd[i].GetPidl(); if (_rgfinst[i].pidl) { TCHAR tsz[MAX_PATH]; c_rgfldd[i].GetFriendlyName(tsz, cA(tsz)); int iItem = ComboBox_AddString(_hwndCombo, tsz); ComboBox_SetItemData(_hwndCombo, iItem, i); } } ComboBox_SetCurSel(_hwndCombo, 0); Resync(); } /***************************************************************************** * * CSpecialFolders::Resync * * Update goo since the combo box changed. * *****************************************************************************/ void CSpecialFolders::Resync() { LRESULT icsidl = Misc_Combo_GetCurItemData(_hwndCombo); TCHAR tsz[MAX_PATH]; tsz[0] = TEXT('\0'); SHGetPathFromIDList(_rgfinst[icsidl].pidl, tsz); SetWindowText(_hwndText, tsz); } /***************************************************************************** * * CSpecialFolders::Apply * * Updating the Folder locations is really annoying, thanks * to NT's roving profiles. * *****************************************************************************/ BOOL CSpecialFolders::Apply() { UINT i; BOOL fNeedLogoff = FALSE; for (i = 0; i < cfldd; i++) { if (_rgfinst[i].fEdited) { TCHAR tsz[MAX_PATH]; SHGetPathFromIDList(_rgfinst[i].pidl, tsz); BOOL fNeedLogoffT = c_rgfldd[i].SetValue(tsz); fNeedLogoff |= fNeedLogoffT; } } return fNeedLogoff; } /***************************************************************************** * * CSpecialFolders::_ChangeFolder_Callback * * Start the user at the old location, and don't let the user pick * something that collides with something else. * *****************************************************************************/ int CALLBACK CSpecialFolders::_ChangeFolder_Callback(HWND hwnd, UINT wm, LPARAM lp, LPARAM lpRef) { CSpecialFolders *self = (CSpecialFolders *)lpRef; int icsidl; TCHAR tsz[MAX_PATH]; switch (wm) { case BFFM_INITIALIZED: SendMessage(hwnd, BFFM_SETSELECTION, 0, (LPARAM)self->_pidlEditing); break; case BFFM_SELCHANGED: /* Picking nothing is bad */ if (!lp) goto bad; /* Picking yourself is okay; just don't pick somebody else */ if (ComparePidls((PIDL)lp, self->_pidlEditing) == 0) goto done; for (icsidl = 0; icsidl < cfldd; icsidl++) { if (self->_rgfinst[icsidl].pidl && ComparePidls(self->_rgfinst[icsidl].pidl, (PIDL)lp) == 0) { bad:; SendMessage(hwnd, BFFM_ENABLEOK, 0, 0); goto done; } } /* Don't allow a removable drive */ tsz[1] = TEXT('\0'); /* Not a typo */ SHGetPathFromIDList((PIDL)lp, tsz); if (tsz[1] == TEXT(':')) { tsz[3] = TEXT('\0'); if (GetDriveType(tsz) == DRIVE_REMOVABLE) { goto bad; } } break; } done:; return 0; } /***************************************************************************** * * CSpecialFolders::ChangeFolder * *****************************************************************************/ void CSpecialFolders::ChangeFolder(HWND hdlg) { if (_fWarned || MessageBoxId(hdlg, IDS_WARNFOLDERCHANGE, g_tszName, MB_YESNO | MB_DEFBUTTON2) == IDYES) { int iItem; int icsidl; BROWSEINFO bi; LPITEMIDLIST pidl; TCHAR tsz[MAX_PATH]; TCHAR tszTitle[MAX_PATH]; TCHAR tszName[MAX_PATH]; _fWarned = TRUE; iItem = ComboBox_GetCurSel(_hwndCombo); ComboBox_GetLBText(_hwndCombo, iItem, tszName); LoadString(hinstCur, IDS_FOLDER_PATTERN, tsz, cA(tsz)); wsprintf(tszTitle, tsz, tszName); bi.hwndOwner = hdlg; bi.pidlRoot = 0; bi.pszDisplayName = tsz; /* Garbage */ bi.lpszTitle = tszTitle; bi.ulFlags = BIF_RETURNONLYFSDIRS; bi.lpfn = _ChangeFolder_Callback; icsidl = (int)ComboBox_GetItemData(_hwndCombo, iItem); _pidlEditing = _rgfinst[icsidl].pidl; bi.lParam = (LPARAM)this; pidl = SHBrowseForFolder(&bi); if (pidl) { if (ComparePidls(pidl, _rgfinst[icsidl].pidl) != 0) { Ole_Free(_rgfinst[icsidl].pidl); _rgfinst[icsidl].pidl = (PIDL)pidl; _rgfinst[icsidl].fEdited = TRUE; Common_SetDirty(hdlg); Resync(); } else { Ole_Free(pidl); } } } } /***************************************************************************** * * My Computer Dialog Info * *****************************************************************************/ typedef class _MDI { /* mdi = my computer dialog info */ public: DWORD dwNoDrives; DWORD dwValidDrives; CSpecialFolders _sf; } MDI, *PMDI; MDI mdi; #define pdmi (&mdi) /***************************************************************************** * * MyComp_BuildRoot * * Build the root directory of a drive. The buffer must be 4 chars. * *****************************************************************************/ LPTSTR PASCAL MyComp_BuildRoot(LPTSTR ptsz, UINT uiDrive) { ptsz[0] = uiDrive + TEXT('A'); ptsz[1] = TEXT(':'); ptsz[2] = TEXT('\\'); ptsz[3] = TEXT('\0'); return ptsz; } /***************************************************************************** * * MyComp_LV_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. * *****************************************************************************/ #define idiPhantom -11 /* Magic index for disconnected drive */ int PASCAL MyComp_LV_GetIcon(LPARAM insi) { if (pdmi->dwValidDrives & (1 << insi)) { SHFILEINFO sfi; TCHAR tszRoot[4]; /* Root directory thing */ SHGetFileInfo(MyComp_BuildRoot(tszRoot, (UINT)insi), 0, &sfi, cbX(sfi), SHGFI_SYSICONINDEX | SHGFI_SMALLICON); return sfi.iIcon; } else { if (g_fNT) { UnicodeFromPtsz(wsz, g_tszPathShell32); return mit.Shell_GetCachedImageIndex(wsz, idiPhantom, 0); } else { return mit.Shell_GetCachedImageIndex(g_tszPathShell32, idiPhantom, 0); } } } /***************************************************************************** * * MyComp_OnInitDialog * * For now, just populate with each physical local drive. * *****************************************************************************/ BOOL PASCAL MyComp_OnInitDialog(HWND hwnd) { UINT ui; TCHAR tszDrive[3]; tszDrive[1] = TEXT(':'); tszDrive[2] = TEXT('\0'); pdmi->dwNoDrives = GetRegDword(g_hkCUSMWCV, c_tszRestrictions, c_tszNoDrives, 0); pdmi->dwValidDrives = GetLogicalDrives(); for (ui = 0; ui < 26; ui++) { int iIcon = MyComp_LV_GetIcon(ui); tszDrive[0] = ui + TEXT('A'); LV_AddItem(hwnd, ui, tszDrive, iIcon, !(pdmi->dwNoDrives & (1 << ui))); } /* * And initialize the special folders stuff. */ HWND hdlg = GetParent(hwnd); pdmi->_sf.Init(GetDlgItem(hdlg, IDC_FLDNAMELIST), GetDlgItem(hdlg, IDC_FLDLOC)); return 1; } /***************************************************************************** * * MyComp_OnDestroy * * Free the memory we allocated. * *****************************************************************************/ void PASCAL MyComp_OnDestroy(HWND hdlg) { /* * Destroy the special folders stuff. */ pdmi->_sf.Destroy(); } #if 0 /***************************************************************************** * * MyComp_FactoryReset * * This is scary and un-undoable, so let's do extra confirmation. * *****************************************************************************/ void PASCAL MyComp_FactoryReset(HWND hdlg) { if (MessageBoxId(hdlg, IDS_MyCompRESETOK, tszName, MB_YESNO + MB_DEFBUTTON2) == IDYES) { pcdii->fRunShellInf = 1; Common_NeedLogoff(hdlg); PropSheet_Apply(GetParent(hdlg)); } } #endif /***************************************************************************** * * MyComp_OnApply * * Write the changes to the registry. * *****************************************************************************/ void PASCAL MyComp_OnApply(HWND hdlg) { HWND hwnd = GetDlgItem(hdlg, IDC_ICONLV); DWORD dwDrives = 0; LV_ITEM lvi; for (lvi.iItem = 0; lvi.iItem < 26; lvi.iItem++) { lvi.stateMask = LVIS_STATEIMAGEMASK; Misc_LV_GetItemInfo(hwnd, &lvi, lvi.iItem, LVIF_STATE); if (!LV_IsChecked(&lvi)) { dwDrives |= 1 << lvi.iItem; } } if (pdmi->dwNoDrives != dwDrives) { DWORD dwChanged; UINT ui; TCHAR tszRoot[4]; SetRegDword(g_hkCUSMWCV, c_tszRestrictions, c_tszNoDrives, dwDrives); /* Recompute GetLogicalDrives() in case new drives are here */ dwChanged = (pdmi->dwNoDrives ^ dwDrives) & GetLogicalDrives(); pdmi->dwNoDrives = dwDrives; /* * SHCNE_UPDATEDIR doesn't work for CSIDL_DRIVES because * Drivesx.c checks the restrictions only in response to a * SHCNE_ADDDRIVE. So walk through every drive that changed * and send a SHCNE_DRIVEADD or SHCNE_DRIVEREMOVED for it. */ for (ui = 0; ui < 26; ui++) { DWORD dwMask = 1 << ui; if (dwChanged & dwMask) { MyComp_BuildRoot(tszRoot, ui); SHChangeNotify((dwDrives & dwMask) ? SHCNE_DRIVEREMOVED : SHCNE_DRIVEADD, SHCNF_PATH, tszRoot, 0L); } } } /* * And update the special folders, too. */ BOOL fNeedLogoff = pdmi->_sf.Apply(); if (fNeedLogoff) { Common_NeedLogoff(hdlg); } } /***************************************************************************** * * MyComp_OnCommand * * Ooh, we got a command. * *****************************************************************************/ void PASCAL MyComp_OnCommand(HWND hdlg, int id, UINT codeNotify) { switch (id) { case IDC_FLDNAMELIST: if (codeNotify == CBN_SELCHANGE) pdmi->_sf.Resync(); break; case IDC_FLDCHG: if (codeNotify == BN_CLICKED) pdmi->_sf.ChangeFolder(hdlg); break; } } /***************************************************************************** * * Oh yeah, we need this too. * *****************************************************************************/ #pragma BEGIN_CONST_DATA LVV lvvMyComp = { MyComp_OnCommand, 0, 0, /* MyComp_LV_Dirtify */ MyComp_LV_GetIcon, MyComp_OnInitDialog, MyComp_OnApply, MyComp_OnDestroy, 0, 4, /* iMenu */ rgdwHelp, 0, /* Double-click action */ lvvflIcons | /* We need icons */ lvvflCanCheck, /* And check boxes */ NULL, }; #pragma END_CONST_DATA /***************************************************************************** * * Our window procedure. * *****************************************************************************/ INT_PTR EXPORT MyComp_DlgProc(HWND hdlg, UINT wm, WPARAM wParam, LPARAM lParam) { return LV_DlgProc(&lvvMyComp, hdlg, wm, wParam, lParam); }