/* * addrm - Dialog box property sheet for "Add/Remove" */ #include "tweakui.h" #pragma BEGIN_CONST_DATA const static DWORD CODESEG rgdwHelp[] = { IDC_UNINSTALL, IDH_UNINSTALL, IDC_UNINSTALLTEXT, IDH_UNINSTALL, IDC_UNINSTALLNEW, IDH_UNINSTALLNEW, IDC_LVDELETE, IDH_UNINSTALLDELETE, IDC_UNINSTALLEDIT, IDH_UNINSTALLEDIT, 0, 0, }; #pragma END_CONST_DATA typedef unsigned char ARIFL; /* Random flags */ #define ariflDelPending 2 /* Delete this on apply */ #define ariflEdited 4 /* Rewrite this key */ #define ctchKeyMac 63 /* Maximum length supported by shell32 */ typedef struct ARI { /* ari - add/remove info */ ARIFL arifl; TCH tszKey[MAX_PATH]; TCH tszCmd[MAX_PATH]; } ARI, *PARI; #define iariPlvi(plvi) ((UINT)(plvi)->lParam) #define pariIari(iari) (&padii->pari[iari]) #define pariPlvi(plvi) pariIari(iariPlvi(plvi)) typedef struct ADII { BOOL fDamaged; Declare_Gxa(ARI, ari); } ADII; ADII adii; #define padii (&adii) /***************************************************************************** * * AddRm_AddKey * * Add an add/remove entry. We use a listview control instead of a listbox, * because we need to be able to process right-clicks in order to display a * context menu. * * Returns the resulting item number. * *****************************************************************************/ int PASCAL AddRm_AddKey(HWND hwnd, LPCTSTR ptszDesc) { return LV_AddItem(hwnd, padii->cari++, ptszDesc, -1, -1); } /***************************************************************************** * * AddRm_OnInitDialog * * Create and fill the Add/Remove list box. * *****************************************************************************/ BOOL PASCAL AddRm_OnInitDialog(HWND hwnd) { padii->fDamaged = FALSE; if (Misc_InitPgxa(&padii->gxa, cbX(ARI))) { HKEY hk; if (_RegOpenKey(g_hkLMSMWCV, c_tszUninstall, &hk) == 0) { int ihk; PARI pari; for (ihk = 0; (pari = (PARI)Misc_AllocPx(&padii->gxa)) && (RegEnumKey(hk, ihk, pari->tszKey, cbX(pari->tszKey)) == 0); ihk++) { if (pari->tszKey[0]) { /* Don't want default key */ TCH tszDesc[MAX_PATH]; if (GetRegStr(hk, pari->tszKey, c_tszDisplayName, tszDesc, cbX(tszDesc)) && GetRegStr(hk, pari->tszKey, c_tszUninstallString, pari->tszCmd, cbX(pari->tszCmd))) { pari->arifl = 0; AddRm_AddKey(hwnd, tszDesc); if (lstrlen(pari->tszKey) > ctchKeyMac) { padii->fDamaged = TRUE; } } } } RegCloseKey(hk); } return 1; } else { return 0; } } /***************************************************************************** * * AddRm_Dirtify * * Mark an entry as dirty. Used when somebody does an in-place edit * of an entry. * *****************************************************************************/ void PASCAL AddRm_Dirtify(LPARAM iari) { pariIari(iari)->arifl |= ariflEdited; } /***************************************************************************** * * DIGRESSION * * The Edit dialog box. * *****************************************************************************/ typedef struct AREI { /* Add/Remove Edit Info */ HWND hwnd; /* List view */ int iItem; /* Item number being edited (-1 if add) */ } AREI, *PAREI; /***************************************************************************** * * AddRm_Edit_IsDlgItemPresent * * Leading and trailing spaces are ignored. * *****************************************************************************/ BOOL PASCAL AddRm_Edit_IsDlgItemPresent(HWND hdlg, int id) { TCH tsz[MAX_PATH]; GetDlgItemText(hdlg, id, tsz, cA(tsz)); return Misc_Trim(tsz)[0]; } /***************************************************************************** * * AddRm_Edit_OnCommand_OnEditChange * * Enable/disable the OK button based on whether the texts are present. * *****************************************************************************/ void PASCAL AddRm_Edit_OnCommand_OnEditChange(HWND hdlg) { EnableDlgItem(hdlg, IDOK, AddRm_Edit_IsDlgItemPresent(hdlg, IDC_UNINSTALLDESC) && AddRm_Edit_IsDlgItemPresent(hdlg, IDC_UNINSTALLCMD)); } /***************************************************************************** * * AddRm_Edit_OnInitDialog * * Fill in the fields with stuff. * *****************************************************************************/ void PASCAL AddRm_Edit_OnInitDialog(HWND hdlg, PAREI parei) { SetWindowLongPtr(hdlg, DWLP_USER, (LPARAM)parei); if (parei->iItem != -1) { LV_ITEM lvi; TCH tszDesc[MAX_PATH]; lvi.pszText = tszDesc; lvi.cchTextMax = cA(tszDesc); Misc_LV_GetItemInfo(parei->hwnd, &lvi, parei->iItem, LVIF_PARAM | LVIF_TEXT); SetDlgItemTextLimit(hdlg, IDC_UNINSTALLDESC, lvi.pszText, MAX_PATH); SetDlgItemTextLimit(hdlg, IDC_UNINSTALLCMD, pariPlvi(&lvi)->tszCmd, cA(pariPlvi(&lvi)->tszCmd)); } AddRm_Edit_OnCommand_OnEditChange(hdlg); } /***************************************************************************** * * AddRm_Edit_OnOk * * Save the information back out. * *****************************************************************************/ void PASCAL AddRm_Edit_OnOk(HWND hdlg) { PAREI parei = (PAREI)GetWindowLongPtr(hdlg, DWLP_USER); LV_ITEM lvi; TCH tszDesc[MAX_PATH]; /* * Need to get the description early, so that the new entry sorts * into the right place. */ lvi.pszText = tszDesc; GetDlgItemText(hdlg, IDC_UNINSTALLDESC, lvi.pszText, cA(tszDesc)); if (parei->iItem == -1) { PARI pari = (PARI)Misc_AllocPx(&padii->gxa); if (pari) { pari->arifl = 0; pari->tszKey[0] = TEXT('\0'); parei->iItem = AddRm_AddKey(parei->hwnd, lvi.pszText); Misc_LV_SetCurSel(parei->hwnd, parei->iItem); } else { goto failed; } } lvi.iItem = parei->iItem; Misc_LV_GetItemInfo(parei->hwnd, &lvi, parei->iItem, LVIF_PARAM); pariPlvi(&lvi)->arifl |= ariflEdited; GetDlgItemText(hdlg, IDC_UNINSTALLCMD, pariPlvi(&lvi)->tszCmd, cA(pariPlvi(&lvi)->tszCmd)); lvi.mask ^= LVIF_TEXT ^ LVIF_PARAM; ListView_SetItem(parei->hwnd, &lvi); Common_SetDirty(GetParent(parei->hwnd)); failed:; } /***************************************************************************** * * AddRm_Edit_OnCommand * *****************************************************************************/ void PASCAL AddRm_Edit_OnCommand(HWND hdlg, int id, UINT codeNotify) { switch (id) { case IDCANCEL: EndDialog(hdlg, 0); break; case IDOK: AddRm_Edit_OnOk(hdlg); EndDialog(hdlg, 0); break; case IDC_UNINSTALLDESC: case IDC_UNINSTALLCMD: if (codeNotify == EN_CHANGE) AddRm_Edit_OnCommand_OnEditChange(hdlg); break; } } /***************************************************************************** * * AddRm_Edit_DlgProc * * Dialog procedure. * *****************************************************************************/ #pragma BEGIN_CONST_DATA const static DWORD CODESEG rgdwHelpEdit[] = { IDC_UNINSTALLDESCTEXT, IDH_UNINSTALLEDITDESC, IDC_UNINSTALLCMDTEXT, IDH_UNINSTALLEDITCOMMAND, 0, 0, }; #pragma END_CONST_DATA /* * 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 AddRm_Edit_DlgProc(HWND hdlg, UINT wm, WPARAM wParam, LPARAM lParam) { switch (wm) { case WM_INITDIALOG: AddRm_Edit_OnInitDialog(hdlg, (PAREI)lParam); break; case WM_COMMAND: AddRm_Edit_OnCommand(hdlg, (int)GET_WM_COMMAND_ID(wParam, lParam), (UINT)GET_WM_COMMAND_CMD(wParam, lParam)); break; case WM_CONTEXTMENU: Common_OnContextMenu(wParam, &rgdwHelpEdit[0]); break; default: return 0; /* Unhandled */ } return 1; /* Handled */ } /***************************************************************************** * * END DIGRESSION * *****************************************************************************/ /***************************************************************************** * * AddRm_OnEdit * *****************************************************************************/ void PASCAL AddRm_OnEdit(HWND hwnd, int iItem) { AREI arei = { hwnd, iItem }; DialogBoxParam(hinstCur, MAKEINTRESOURCE(IDD_UNINSTALLEDIT), hwnd, AddRm_Edit_DlgProc, (LPARAM)&arei); } /***************************************************************************** * * AddRm_OnNew * *****************************************************************************/ #define AddRm_OnNew(hdlg) AddRm_OnEdit(GetDlgItem(hdlg, IDC_UNINSTALL), -1) /***************************************************************************** * * AddRm_OnDelete * * Mark it for deletion, but don't actually nuke it until later. * *****************************************************************************/ void PASCAL AddRm_OnDelete(HWND hwnd, int iItem) { LV_ITEM lvi; TCH tszDesc[MAX_PATH]; lvi.pszText = tszDesc; lvi.cchTextMax = cA(tszDesc); Misc_LV_GetItemInfo(hwnd, &lvi, iItem, LVIF_PARAM | LVIF_TEXT); if (MessageBoxId(GetParent(hwnd), IDS_ADDRMWARN, lvi.pszText, MB_YESNO | MB_DEFBUTTON2) == IDYES) { pariPlvi(&lvi)->arifl |= ariflDelPending; ListView_DeleteItem(hwnd, iItem); Misc_LV_EnsureSel(hwnd, iItem); Common_SetDirty(GetParent(hwnd)); } } /***************************************************************************** * * AddRm_CreateUniqueKeyName * * Find a name that doesn't yet exist. * * Search numerically until something works, or a weird error occurs. * #if 0 * To avoid O(n^2) behavior, we start with the number of keys. #endif * *****************************************************************************/ BOOL PASCAL AddRm_CreateUniqueKeyName(HKEY hkUninst, LPTSTR ptsz) { BOOL fRc; int i; for (i = 0; ; i++) { LONG cb; wsprintf(ptsz, c_tszPercentU, i); cb = 0; switch (RegQueryValue(hkUninst, ptsz, 0, &cb)) { case ERROR_SUCCESS: break; case ERROR_FILE_NOT_FOUND: fRc = 1; goto done; default: fRc = 0; goto done; /* Unknown error */ } } done:; return fRc; } /***************************************************************************** * * AddRm_OnApply_DoDeletes * * Delete the keys that are marked as "delete pending". * * pari->tszKey[0] is TEXT('\0') if the key was never in the registry. * (E.g., you "New" it, and then delete it.) * *****************************************************************************/ void PASCAL AddRm_OnApply_DoDeletes(HKEY hkUninst) { int iari; for (iari = 0; iari < padii->cari; iari++) { if (pariIari(iari)->arifl & ariflDelPending) { if (pariIari(iari)->tszKey[0]) { RegDeleteTree(hkUninst, pariIari(iari)->tszKey); } } } } /***************************************************************************** * * AddRm_OnApply_DoEdits * * Apply all the edits. * *****************************************************************************/ void PASCAL AddRm_OnApply_DoEdits(HWND hdlg, HKEY hkUninst) { HWND hwnd = GetDlgItem(hdlg, IDC_UNINSTALL); int cItems = ListView_GetItemCount(hwnd); LV_ITEM lvi; for (lvi.iItem = 0; lvi.iItem < cItems; lvi.iItem++) { TCH tsz[MAX_PATH]; PARI pari; lvi.pszText = tsz; lvi.cchTextMax = cA(tsz); Misc_LV_GetItemInfo(hwnd, &lvi, lvi.iItem, LVIF_PARAM | LVIF_TEXT); pari = pariPlvi(&lvi); if (pari->arifl & ariflEdited) { HKEY hk; if (pari->tszKey[0] == TEXT('\0')) { if (AddRm_CreateUniqueKeyName(hkUninst, pari->tszKey)) { } else { break; /* Error! */ } } if (RegCreateKey(hkUninst, pari->tszKey, &hk) == 0) { RegSetValuePtsz(hk, c_tszDisplayName, tsz); RegSetValuePtsz(hk, c_tszUninstallString, pari->tszCmd); RegCloseKey(hk); } } } } /***************************************************************************** * * AddRm_OnApply * *****************************************************************************/ void PASCAL AddRm_OnApply(HWND hdlg) { HKEY hkUninst; if (RegCreateKey(g_hkLMSMWCV, c_tszUninstall, &hkUninst) == 0) { AddRm_OnApply_DoDeletes(hkUninst); AddRm_OnApply_DoEdits(hdlg, hkUninst); RegCloseKey(hkUninst); } } /***************************************************************************** * * AddRm_OnDestroy * * Free the memory we allocated. * *****************************************************************************/ void PASCAL AddRm_OnDestroy(HWND hdlg) { Misc_FreePgxa(&padii->gxa); } /***************************************************************************** * * AddRm_OnRepair * * Take all the keys that are too long and shorten them. * *****************************************************************************/ void PASCAL AddRm_OnRepair(HWND hdlg) { if (padii->fDamaged && MessageBoxId(hdlg, IDS_ASKREPAIRADDRM, g_tszName, MB_YESNO)) { HKEY hkUninst; if (RegCreateKey(g_hkLMSMWCV, c_tszUninstall, &hkUninst) == 0) { int iari; for (iari = 0; iari < padii->cari; iari++) { TCHAR tszNewKey[MAX_PATH]; PARI pari = pariIari(iari); if (lstrlen(pari->tszKey) > ctchKeyMac && AddRm_CreateUniqueKeyName(hkUninst, tszNewKey) && Misc_RenameReg(hkUninst, 0, pari->tszKey, tszNewKey)) { lstrcpy(pari->tszKey, tszNewKey); } } RegCloseKey(hkUninst); } } padii->fDamaged = FALSE; /* Ask only once */ } /***************************************************************************** * * AddRm_OnCommand * *****************************************************************************/ void PASCAL AddRm_OnCommand(HWND hdlg, int id, UINT codeNotify) { switch (id) { case IDC_UNINSTALLNEW: if (codeNotify == BN_CLICKED) { AddRm_OnNew(hdlg); } break; case IDC_UNINSTALLCHECK: AddRm_OnRepair(hdlg); break; } } /***************************************************************************** * * Oh yeah, we need this too. * *****************************************************************************/ #pragma BEGIN_CONST_DATA LVCI lvciAddRm[] = { { IDC_UNINSTALLEDIT, AddRm_OnEdit }, { IDC_LVDELETE, AddRm_OnDelete }, { 0, 0 }, }; LVV lvvAddRm = { AddRm_OnCommand, 0, /* AddRm_OnInitContextMenu */ AddRm_Dirtify, 0, /* AddRm_GetIcon */ AddRm_OnInitDialog, AddRm_OnApply, AddRm_OnDestroy, 0, /* AddRm_OnSelChange */ 2, /* iMenu */ rgdwHelp, IDC_UNINSTALLEDIT, /* Double-click action */ lvvflCanDelete | lvvflCanRename, lvciAddRm, }; #pragma END_CONST_DATA /***************************************************************************** * * Our window procedure. * *****************************************************************************/ INT_PTR EXPORT AddRm_DlgProc(HWND hdlg, UINT wm, WPARAM wParam, LPARAM lParam) { switch (wm) { case WM_SHOWWINDOW: if (wParam) { FORWARD_WM_COMMAND(hdlg, IDC_UNINSTALLCHECK, 0, 0, PostMessage); } break; } return LV_DlgProc(&lvvAddRm, hdlg, wm, wParam, lParam); }