#include #ifdef UNICODE #include "nsis_unicode/pluginapi.h" #else #include "nsis_ansi/pluginapi.h" #endif #include "resource.h" #include /** EmbeddedLists v1.4 RC2 by Afrow UK A plugin dll for NSIS that shows list controls. See Docs\EmbeddedLists\Readme.txt Last modified: 8th July 2010 */ #define TEXT_CHECKALL TEXT("Check all") #define TEXT_UNCHECKALL TEXT("Uncheck all") #define TEXT_SELECTALL TEXT("Select all") #define TEXT_UNSELECTALL TEXT("Unselect all") // Common keys. #define KEY_TEXT TEXT("Text") // Settings. #define SEC_SETTINGS TEXT("Settings") #define KEY_TYPE TEXT("Type") // ListView|TreeView #define KEY_HEADINGTEXT TEXT("HeadingText") #define KEY_GROUPTEXT TEXT("GroupText") #define KEY_CAPTION TEXT("Caption") #define KEY_NEXTTOGGLE TEXT("ToggleNextButton") // 0|1 #define KEY_CHECKBOXES TEXT("CheckBoxes") #define KEY_SORT TEXT("Sort") // none|ascending|descending #define KEY_SORTBYCOLUMNCLICK TEXT("SortByColumnClick") // 0|1 #define KEY_SORTBYCOLUMN TEXT("SortByColumn") #define KEY_COLHEADER TEXT("ColumnHeader") // 0|1 #define KEY_SINGLESELECT TEXT("SingleSelect") // 0|1 #define KEY_LABELEDIT TEXT("LabelEdit") // 0|1 #define KEY_NOITEMSELECT TEXT("NoItemSelection") // 0|1 #define KEY_PARENTCHECK TEXT("ParentCheck") // 0|1 #define KEY_RECT TEXT("Rect") #define KEY_VIEWCONTROLONLY TEXT("ViewListOnly") // 0|1 #define KEY_RETURNITEMTEXT TEXT("ReturnItemText") // 0|1 #define KEY_USECHECKBITMAP TEXT("UseCheckBitmap") // 0|1 // Icon lists. #define SEC_ICONLIST TEXT("Icons") #define KEY_ICONCOUNT TEXT("IconCount") #define KEY_ICONFILE TEXT("Icon") // Icon#= // ListView Columns. #define SEC_COLUMNS TEXT("Columns") #define KEY_COLUMN TEXT("Column") #define KEY_WIDTH TEXT("Width") // Items/Nodes. #define SEC_ITEM TEXT("Item") #define KEY_SUBITEM TEXT("SubItem") // SubItem#= #define KEY_ICONINDEX TEXT("IconIndex") #define KEY_SELECTED TEXT("Selected") // 0|1 #define KEY_CHECKED TEXT("Checked") // 0|1 #define KEY_POSITION TEXT("Position") #define KEY_EXPANDED TEXT("Expanded") // 0|1 #define KEY_BOLDTEXT TEXT("BoldText") // 0|1 #define KEY_DISABLECHECK TEXT("DisableCheck") // 0|1|2 #define KEY_DISABLEEDIT TEXT("DisableLabelEdit") // 0|1 // Uses KEY_TEXT #define DEFAULT_RECT 1018 // Nothing selected return value. #define OUT_ERROR TEXT("ERROR") #define OUT_NEXT TEXT("NEXT") #define OUT_BACK TEXT("BACK") #define OUT_CANCEL TEXT("CANCEL") #define OUT_ENDSTACK TEXT("/END") #define DLG_LISTVIEW TEXT("ListView") #define DLG_TREEVIEW TEXT("TreeView") #define LVIS_UNCHECKED INDEXTOSTATEIMAGEMASK(1) #define LVIS_CHECKED INDEXTOSTATEIMAGEMASK(2) #define TVIS_UNCHECKED INDEXTOSTATEIMAGEMASK(1) #define TVIS_CHECKED INDEXTOSTATEIMAGEMASK(2) #define LVIS_IMAGE_BLANK INDEXTOSTATEIMAGEMASK(0) #define LVIS_IMAGE_UNCHECKED INDEXTOSTATEIMAGEMASK(2) #define LVIS_IMAGE_CHECKED INDEXTOSTATEIMAGEMASK(3) #define LVIS_IMAGE_PCHECKED INDEXTOSTATEIMAGEMASK(4) #define LVIS_IMAGE_DISUNCHECKED INDEXTOSTATEIMAGEMASK(5) #define LVIS_IMAGE_DISCHECKED INDEXTOSTATEIMAGEMASK(6) #define TVIS_IMAGE_BLANK INDEXTOSTATEIMAGEMASK(0) #define TVIS_IMAGE_UNCHECKED INDEXTOSTATEIMAGEMASK(1) #define TVIS_IMAGE_CHECKED INDEXTOSTATEIMAGEMASK(2) #define TVIS_IMAGE_PCHECKED INDEXTOSTATEIMAGEMASK(3) #define TVIS_IMAGE_DISUNCHECKED INDEXTOSTATEIMAGEMASK(4) #define TVIS_IMAGE_DISCHECKED INDEXTOSTATEIMAGEMASK(5) #ifndef GetWindowLongPtr #define GetWindowLongPtr GetWindowLong #endif #ifndef SetWindowLongPtr #define SetWindowLongPtr SetWindowLong #endif #ifndef GWLP_WNDPROC #define GWLP_WNDPROC GWL_WNDPROC #endif #ifndef DWLP_DLGPROC #define DWLP_DLGPROC DWL_DLGPROC #endif #ifndef LVM_SORTITEMSEX #define LVM_SORTITEMSEX (LVM_FIRST + 81) #endif #ifndef ListView_SortItemsEx #define ListView_SortItemsEx(hwndLV, _pfnCompare, _lPrm) \ (BOOL)SendMessage((hwndLV), LVM_SORTITEMSEX, (WPARAM)(LPARAM)(_lPrm), (LPARAM)(PFNLVCOMPARE)(_pfnCompare)) #endif #ifndef LVS_EX_LABELTIP #define LVS_EX_LABELTIP 0x00004000 #endif #define ReadINIStr(ReturnStr,Section,Key,DefaultVal) \ GetPrivateProfileString(Section,Key,DefaultVal,ReturnStr,g_stringsize,g_szINIFilePath) #define ReadINIInt(ReturnInt,Section,Key,DefaultVal) \ (ReturnInt = GetPrivateProfileInt(Section,Key,DefaultVal,g_szINIFilePath)) #define WriteINIStr(Section,Key,Val) \ WritePrivateProfileString(Section,Key,Val,g_szINIFilePath) #ifndef GET_X_LPARAM #define GET_X_LPARAM(lp) ((int)(short)LOWORD(lp)) #endif #ifndef GET_Y_LPARAM #define GET_Y_LPARAM(lp) ((int)(short)HIWORD(lp)) #endif HINSTANCE g_hInstance; HWND g_hWndParent; HWND g_hDialog; HWND g_hCtl; TCHAR g_szINIFilePath[MAX_PATH]; WNDPROC ParentDlgProcOld; BOOL g_done = FALSE, g_is_cancel = FALSE, g_is_back = FALSE, g_bInitDialog = FALSE; /* Check boxes or not? 0 = no 1 = yes 2 = partialy checked supported*/ int g_iCheckBoxes = FALSE; // Allow label editing? BOOL g_bLabelEdit = FALSE; BOOL g_bParentCheck = FALSE; // Allow item selection? BOOL g_bNoItemSelect = FALSE; // Only display tree view / list view control on dialog. BOOL g_bViewControlOnly = FALSE; // Return the item text rather than the item number. BOOL g_bReturnItemText = FALSE; // Which dialog... int g_iDialog = 0; /* 0 = list view 1 = tree view */ long g_iListItemCount = 0; // Stores info for item sorting. struct SORTBY { int iColumn; // Column number (1 based). int iSort; // 0 = ascending, 1 = descending. }; typedef SORTBY* PSORTBY; // Stores old item information. struct OLDITEM { int iChecked; // 0 = unchecked, 1 = checked, 2 = partially unchecked, 3 = partially checked int iDisableCheck; // 0 = no disable, 2 = disabled, 3 = hidden BOOL bDisableEdit; int iOldIndex; PTCHAR pszOldText; }; typedef OLDITEM* POLDITEM; BOOL CALLBACK ListView_ParentDlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); BOOL CALLBACK TreeView_ParentDlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); #define NSISFUNC(name) extern "C" void __declspec(dllexport) name(HWND hWndParent, int string_size, PTCHAR variables, stack_t** stacktop, extra_parameters* extra) BOOL g_bInited; #define DLL_INIT() \ { \ if (!g_bInited) \ { \ g_hWndParent = hWndParent; \ EXDLL_INIT(); \ extra->RegisterPluginCallback(g_hInstance, PluginCallback); \ g_bInited = TRUE; \ } \ } // Creates an OLDITEM. POLDITEM CreateOldItem(int iSectionCount, PTCHAR pszSectionName, PTCHAR pszValue) { int iValue; POLDITEM poi = (POLDITEM)GlobalAlloc(GPTR, sizeof(OLDITEM)); { // Store original item info. poi->iOldIndex = iSectionCount; poi->pszOldText = (PTCHAR)GlobalAlloc(GPTR, sizeof(TCHAR)*(lstrlen(pszValue)+1)); lstrcpy(poi->pszOldText, pszValue); if (g_iCheckBoxes) { // Is this item checked. if (ReadINIInt(iValue, pszSectionName, KEY_CHECKED, 0)) poi->iChecked = TRUE; else poi->iChecked = FALSE; // Is this item check state changable? if (ReadINIInt(iValue, pszSectionName, KEY_DISABLECHECK, 0) != 0) { if (iValue == 2) poi->iDisableCheck = 2; else poi->iDisableCheck = TRUE; } else poi->iDisableCheck = FALSE; } // Is this item label editable? if (ReadINIInt(iValue, pszSectionName, KEY_DISABLEEDIT, 0) != 0) poi->bDisableEdit = TRUE; } return poi; } // Sets common control texts. void SetCommonTexts(HWND hWndDlg) { PTCHAR pszValue = (PTCHAR)LocalAlloc(LPTR, sizeof(TCHAR)*g_stringsize); // Get heading label text. ReadINIStr(pszValue, SEC_SETTINGS, KEY_HEADINGTEXT, TEXT("")); SetWindowText(GetDlgItem(hWndDlg, IDC_HEADINGTEXT), pszValue); // Get group box label text. ReadINIStr(pszValue, SEC_SETTINGS, KEY_GROUPTEXT, TEXT("")); SetWindowText(GetDlgItem(hWndDlg, IDC_GROUPBOX), pszValue); // Set dialog caption. if (ReadINIStr(pszValue, SEC_SETTINGS, KEY_CAPTION, TEXT(""))) SetWindowText(g_hWndParent, pszValue); LocalFree(pszValue); } // Displays our dialog. void ShowDialog() { if (g_hDialog) { HWND hRect; RECT dialog_r; int mainWndWidth, mainWndHeight; int iRect; // Set dialog font to that of the parent window font. SendMessage(g_hDialog, WM_SETFONT, (WPARAM)SendMessage(g_hWndParent, WM_GETFONT, 0, 0), TRUE); // Get which rect to place our dialog on. ReadINIInt(iRect, SEC_SETTINGS, KEY_RECT, DEFAULT_RECT); hRect = GetDlgItem(g_hWndParent, iRect); // Check rect exists. if (!hRect) { pushstring(OUT_ERROR); return; } // Get the sizes of the UI. GetWindowRect(hRect, &dialog_r); MapWindowPoints(0, g_hWndParent, (LPPOINT)&dialog_r, 2); mainWndWidth = dialog_r.right - dialog_r.left; mainWndHeight = dialog_r.bottom - dialog_r.top; // Set our window size to fit the UI size. iRect = MoveWindow( g_hDialog, dialog_r.left, dialog_r.top, mainWndWidth, mainWndHeight, FALSE ); // Do we only want to display the list / tree view in dialog? if (ReadINIInt(iRect, SEC_SETTINGS, KEY_VIEWCONTROLONLY, 0) == 1) { // Remove unwanted controls. DestroyWindow(GetDlgItem(g_hDialog, IDC_HEADINGTEXT)); DestroyWindow(GetDlgItem(g_hDialog, IDC_GROUPBOX)); // Set our window size to fit the UI size. MoveWindow( g_hCtl, 0, 0, mainWndWidth, mainWndHeight, FALSE ); } else { HWND hCtl; int iTop; // Get the size of the heading label. hCtl = GetDlgItem(g_hDialog, IDC_HEADINGTEXT); GetWindowRect(hCtl, &dialog_r); iTop = dialog_r.bottom - dialog_r.top; // Resize heading label. MoveWindow( hCtl, 0, 0, mainWndWidth, iTop, FALSE ); // Get the size of the group box. hCtl = GetDlgItem(g_hDialog, IDC_GROUPBOX); // Resize group box. MoveWindow( hCtl, 0, iTop, mainWndWidth, mainWndHeight - iTop - 5, FALSE ); iTop += 15; // Resize list view / tree view. MoveWindow( g_hCtl, 8, iTop, mainWndWidth - 15, mainWndHeight - iTop - 15, FALSE ); } // Sub-class parent window procedure. if (g_iDialog == 0) // For list view dialog. ParentDlgProcOld = (WNDPROC)SetWindowLongPtr(g_hWndParent, DWLP_DLGPROC, (LONG)ListView_ParentDlgProc); else // For tree view dialog. ParentDlgProcOld = (WNDPROC)SetWindowLongPtr(g_hWndParent, DWLP_DLGPROC, (LONG)TreeView_ParentDlgProc); // Sets the font of IO window to be the same as the main window. SendMessage(g_hDialog, WM_SETFONT, (WPARAM)SendMessage(g_hWndParent, WM_GETFONT, 0, 0), (LPARAM)TRUE); // Tell NSIS to remove old inner dialog and pass handle of the new inner dialog. SendMessage(g_hWndParent, WM_NOTIFY_CUSTOM_READY, (WPARAM)g_hDialog, 0); ShowWindow(g_hDialog, SW_SHOW); g_done = FALSE; // Loop until the user clicks on a button. while (!g_done) { MSG msg; int nResult = GetMessage(&msg, NULL, 0, 0); if (!IsDialogMessage(g_hDialog, &msg) && !IsDialogMessage(g_hWndParent, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } // Set window dialog procedure back to NSIS's. SetWindowLongPtr(g_hWndParent, DWL_DLGPROC, (long)ParentDlgProcOld); DestroyWindow(g_hDialog); // Return page button result. pushstring(g_is_cancel ? OUT_CANCEL : g_is_back ? OUT_BACK : OUT_NEXT); } else pushstring(OUT_ERROR); } // Get icons from icon list in INI file. HIMAGELIST GetIcons() { static TCHAR szSectionName[32], szIconPath[MAX_PATH]; int iIcons, iSectionCount; HICON hIcon; HIMAGELIST himlIcons = NULL; wsprintf(szSectionName, TEXT("%s%i"), KEY_ICONFILE, iSectionCount = 1); while (ReadINIStr(szIconPath, SEC_ICONLIST, szSectionName, TEXT(""))) { if (!himlIcons) himlIcons = ImageList_Create(16, 16, ILC_MASK | ILC_COLOR32, ReadINIInt(iIcons, SEC_ICONLIST, KEY_ICONCOUNT, 8), 0); hIcon = (HICON)LoadImage(g_hInstance, szIconPath, IMAGE_ICON, 16, 16, LR_LOADFROMFILE | LR_LOADTRANSPARENT | LR_SHARED); if (hIcon) ImageList_AddIcon(himlIcons, hIcon); DestroyIcon(hIcon); wsprintf(szSectionName, TEXT("%s%i"), KEY_ICONFILE, ++iSectionCount); } return himlIcons; } // Get check state image from installer exe header. HIMAGELIST GetStateImage(PTCHAR pszPath) { HBITMAP hStateImage; HIMAGELIST himlState; if (lstrcmp(pszPath, TEXT("1")) == 0) hStateImage = (HBITMAP)LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_BITMAP), IMAGE_BITMAP, 96, 16, LR_SHARED); else hStateImage = (HBITMAP)LoadImage(g_hInstance, pszPath, IMAGE_BITMAP, 96, 16, LR_LOADFROMFILE | LR_SHARED); if (!hStateImage) return NULL; himlState = ImageList_Create(16, 16, ILC_MASK | ILC_COLOR32, 6, 0); ImageList_AddMasked(himlState, hStateImage, RGB(255, 0, 255)); DeleteObject(hStateImage); g_iCheckBoxes = 2; return himlState; } // Frees memory allocated for each list view item. void ListView_FreeParams() { static LVITEM lvi; static POLDITEM poi; lvi.mask = TVIF_PARAM; lvi.iItem = 0; // Get lParam. while (ListView_GetItem(g_hCtl, &lvi)) { // Get OLDITEM data. poi = (POLDITEM)lvi.lParam; // Free memory allocated. GlobalFree(poi->pszOldText); GlobalFree(poi); // Next item. lvi.iItem += 1; } } // Get number of checked items. BOOL ListView_IsItemChecked() { LVITEM lvi; for (int i=0; iiChecked == TRUE) return TRUE; } return FALSE; } // Compare items in list view. static int CALLBACK ListView_CompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) { static PSORTBY psb; static LVITEM lvi; PTCHAR pszText1 = (PTCHAR)LocalAlloc(LPTR, sizeof(TCHAR)*g_stringsize); PTCHAR pszText2 = (PTCHAR)LocalAlloc(LPTR, sizeof(TCHAR)*g_stringsize); psb = (PSORTBY)lParamSort; lvi.mask = LVIF_TEXT; lvi.iSubItem = psb->iColumn-1; lvi.iItem = lParam1; lvi.pszText = pszText1; lvi.cchTextMax = g_stringsize; ListView_GetItem(g_hCtl, &lvi); lvi.iItem = lParam2; lvi.pszText = pszText2; lvi.cchTextMax = g_stringsize; ListView_GetItem(g_hCtl, &lvi); int result; if (psb->iSort == 0) result = lstrcmpi(pszText1, pszText2); else result = lstrcmpi(pszText2, pszText1); LocalFree(pszText1); LocalFree(pszText2); return result; } // Handles the parent dialog for list view dialog. static BOOL CALLBACK ListView_ParentDlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { static LVITEM lvi; static POLDITEM poi; static TCHAR szSectionName[32], szNumber[4]; static BOOL bRes = FALSE; static int i; PTCHAR pszValue = (PTCHAR)LocalAlloc(LPTR, sizeof(TCHAR)*g_stringsize); if (uMsg == WM_NOTIFY_OUTER_NEXT && wParam == 1) { pushstring(OUT_ENDSTACK); lvi.mask = LVIF_TEXT | LVIF_PARAM | LVIF_STATE; lvi.iSubItem = 0; lvi.pszText = pszValue; lvi.cchTextMax = g_stringsize; // No check boxes. if (!g_iCheckBoxes) { for (i=0; iiOldIndex); // Is item selected? if (lvi.state & LVIS_SELECTED) { // Add item text to stack. if (g_bReturnItemText) pushstring(lvi.pszText); // Add item number to stack. else { wsprintf(szNumber, TEXT("%i"), poi->iOldIndex); pushstring(szNumber); } // Save selected state in INI file. WriteINIStr(szSectionName, KEY_SELECTED, TEXT("1")); } else // Save selected state in INI file. WriteINIStr(szSectionName, KEY_SELECTED, TEXT("0")); // Save list item text if it has been modified. if (g_bLabelEdit) if (!poi->bDisableEdit) if (lstrcmp(poi->pszOldText, lvi.pszText) != 0) WriteINIStr(szSectionName, KEY_TEXT, lvi.pszText); } } // With check boxes. else { for (i=0; iiOldIndex); // Is item checked? if (poi->iChecked) { // Add item text to stack. if (g_bReturnItemText) pushstring(lvi.pszText); // Add item number to stack. else { wsprintf(szNumber, TEXT("%i"), poi->iOldIndex); pushstring(szNumber); } // Save checked state in INI file, unless its // state cannot be changed. if (!poi->iDisableCheck) WriteINIStr(szSectionName, KEY_CHECKED, TEXT("1")); } else // Save checked state in INI file, unless its // state cannot be changed. if (!poi->iDisableCheck) WriteINIStr(szSectionName, KEY_CHECKED, TEXT("0")); // Save list item text if it has been modified. if (g_bLabelEdit) if (!poi->bDisableEdit) if (lstrcmp(poi->pszOldText, lvi.pszText) != 0) WriteINIStr(szSectionName, KEY_TEXT, lvi.pszText); } } } LocalFree(pszValue); bRes = CallWindowProc(ParentDlgProcOld, hWnd, uMsg, wParam, lParam); if (uMsg == WM_NOTIFY_OUTER_NEXT && !bRes) { if (wParam == -1) g_is_back = TRUE; else if (wParam == NOTIFY_BYE_BYE) g_is_cancel = TRUE; g_done = TRUE; PostMessage(g_hDialog, WM_CLOSE, 0, 0); } return bRes; } // Handles ListView dialog. static LRESULT CALLBACK ListView_DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { static HWND hNext = GetDlgItem(g_hWndParent, IDC_NEXT); static BOOL bToggleNext = TRUE, bItemSelected = FALSE, bSingleSelect = FALSE, bDialogShown = FALSE; static TCHAR szSectionName[32], szKeyName[32]; static int iValue, iSectionCount, iKeyCount; static HIMAGELIST himlIcons, himlState; static LVITEM lvi; static LVCOLUMN lvcNew; static DWORD lStyle = 0; static SORTBY sb; PTCHAR pszValue = (PTCHAR)LocalAlloc(LPTR, sizeof(TCHAR)*g_stringsize); BOOL bResult = FALSE; switch (uMsg) { case WM_SHOWWINDOW: { if (g_iCheckBoxes && !bDialogShown) { // Check all items to be checked (iChecked == TRUE). for (int i=0; iiDisableCheck && (g_iCheckBoxes == 2)) { if (poi->iDisableCheck == 2) lvi.state = LVIS_IMAGE_BLANK; else { if (poi->iChecked) lvi.state = LVIS_IMAGE_DISCHECKED; else lvi.state = LVIS_IMAGE_DISUNCHECKED; } } else { if (poi->iChecked) lvi.state = (g_iCheckBoxes == 2 ? LVIS_IMAGE_CHECKED : LVIS_CHECKED); else lvi.state = (g_iCheckBoxes == 2 ? LVIS_IMAGE_UNCHECKED : LVIS_UNCHECKED); } lvi.mask |= LVIF_STATE; lvi.stateMask = LVIS_STATEIMAGEMASK; ListView_SetItem(g_hCtl, &lvi); } } // Disable next button. if (bToggleNext) if (!ListView_IsItemChecked()) EnableWindow(hNext, FALSE); bDialogShown = TRUE; } break; case WM_INITDIALOG: g_hCtl = GetDlgItem(hWndDlg, IDC_LIST); lStyle = GetWindowLongPtr(g_hCtl, GWL_STYLE); lvcNew.iSubItem = 0; sb.iColumn = 1; sb.iSort = -1; { // Full row select always. ListView_SetExtendedListViewStyle(g_hCtl, LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP); // Disable the next button and re-enable when a item is selected or checked? if (ReadINIInt(iValue, SEC_SETTINGS, KEY_NEXTTOGGLE, 0) == 0) bToggleNext = FALSE; // Do not allow item selection. if (ReadINIInt(iValue, SEC_SETTINGS, KEY_NOITEMSELECT, 0) == 1) g_bNoItemSelect = TRUE; // Return selected/checked items by item text rather than item number. if (ReadINIInt(iValue, SEC_SETTINGS, KEY_RETURNITEMTEXT, 0) == 1) g_bReturnItemText = TRUE; // Allow column clicking. If sorting is enabled, will sort list by column. if (ReadINIInt(iValue, SEC_SETTINGS, KEY_SORTBYCOLUMNCLICK, 0) == 0) lStyle |= LVS_NOSORTHEADER; // Add icon files to image list. if (himlIcons = GetIcons()) ListView_SetImageList(g_hCtl, himlIcons, LVSIL_SMALL); // Add check boxes to the list view. if (ReadINIInt(iValue, SEC_SETTINGS, KEY_CHECKBOXES, 0)) { g_iCheckBoxes = TRUE; // Use NSIS check bitmap for state image list. if (ReadINIStr(pszValue, SEC_SETTINGS, KEY_USECHECKBITMAP, TEXT("1")) && lstrcmp(pszValue, TEXT("0")) != 0) { if (himlState = GetStateImage(pszValue)) ListView_SetImageList(g_hCtl, himlState, LVSIL_STATE); else ListView_SetExtendedListViewStyle(g_hCtl, LVS_EX_FULLROWSELECT | LVS_EX_CHECKBOXES | LVS_EX_LABELTIP); } else ListView_SetExtendedListViewStyle(g_hCtl, LVS_EX_FULLROWSELECT | LVS_EX_CHECKBOXES | LVS_EX_LABELTIP); } // Hide column headers. if (ReadINIInt(iValue, SEC_SETTINGS, KEY_COLHEADER, 0) == 0) { RECT r; GetClientRect(g_hCtl, &r); lStyle |= LVS_NOCOLUMNHEADER; lvcNew.mask = LVCF_TEXT | LVCF_WIDTH; lvcNew.pszText = TEXT(""); lvcNew.cx = r.right-6; ListView_InsertColumn(g_hCtl, lvcNew.iSubItem, &lvcNew); } // List view is single select. if (ReadINIInt(iValue, SEC_SETTINGS, KEY_SINGLESELECT, 0)) { bSingleSelect = TRUE; lStyle |= LVS_SINGLESEL; } // Items are editable. if (ReadINIInt(iValue, SEC_SETTINGS, KEY_LABELEDIT, 0)) { g_bLabelEdit = TRUE; lStyle |= LVS_EDITLABELS; } // Sort list view items alphabetically in ascending order. if (ReadINIStr(pszValue, SEC_SETTINGS, KEY_SORT, TEXT("none"))) { if (lstrcmpi(pszValue, TEXT("ascending")) == 0) { sb.iSort = 0; ReadINIInt(sb.iColumn, SEC_SETTINGS, KEY_SORTBYCOLUMN, 1); } else if (lstrcmpi(pszValue, TEXT("descending")) == 0) { sb.iSort = 1; ReadINIInt(sb.iColumn, SEC_SETTINGS, KEY_SORTBYCOLUMN, 1); } } // Add column headings to list view. if ((lStyle & LVS_NOCOLUMNHEADER) != LVS_NOCOLUMNHEADER) { wsprintf(szKeyName, TEXT("%s%i"), KEY_COLUMN, iKeyCount = 1); while (ReadINIStr(pszValue, SEC_COLUMNS, szKeyName, TEXT(""))) { // Set column text. lvcNew.mask = LVCF_TEXT | LVCF_WIDTH; lvcNew.pszText = pszValue; lvcNew.cchTextMax = g_stringsize; // Set column width. wsprintf(szKeyName, TEXT("%s%s"), szKeyName, KEY_WIDTH); ReadINIInt(iValue, SEC_COLUMNS, szKeyName, 100); lvcNew.cx = iValue; // Add column. ListView_InsertColumn(g_hCtl, ++lvcNew.iSubItem, &lvcNew); // Next column. wsprintf(szKeyName, TEXT("%s%i"), KEY_COLUMN, ++iKeyCount); } } // Add list items. g_iListItemCount = 0; wsprintf(szSectionName, TEXT("%s %i"), SEC_ITEM, iSectionCount = 1); while (ReadINIStr(pszValue, szSectionName, KEY_TEXT, TEXT(""))) { // Set general item properties. lvi.iSubItem = 0; lvi.mask = LVIF_TEXT | LVIF_PARAM; lvi.pszText = pszValue; lvi.cchTextMax = g_stringsize; lvi.iItem = g_iListItemCount; // Store original item info in item tag. lvi.lParam = (LPARAM)CreateOldItem(iSectionCount, szSectionName, pszValue); // Increment item count. g_iListItemCount++; // Have an image list present? if (himlIcons) { // Setting an icon on the item? if (ReadINIInt(iValue, szSectionName, KEY_ICONINDEX, 0) != 0) { lvi.mask |= LVIF_IMAGE; lvi.iImage = iValue-1; // Conversion to zero-based. } } // Select item if required. if (ReadINIInt(iValue, szSectionName, KEY_SELECTED, 0)) { if ((lStyle & LVS_SINGLESEL) == LVS_SINGLESEL) { // If we're using single selection, only // select one item. if (!bItemSelected) { lvi.mask |= LVIF_STATE; lvi.stateMask |= LVIS_SELECTED; lvi.state |= LVIS_SELECTED; bItemSelected = TRUE; } } // Using multiple selection. else { lvi.mask |= LVIF_STATE; lvi.stateMask |= LVIS_SELECTED; lvi.state |= LVIS_SELECTED; } } // Add list view item. ListView_InsertItem(g_hCtl, &lvi); // Set sub item texts. wsprintf(szKeyName, TEXT("%s%i"), KEY_SUBITEM, iKeyCount = 1); while (ReadINIStr(pszValue, szSectionName, szKeyName, TEXT(""))) { // Set sub item text. lvi.iSubItem++; ListView_SetItemText(g_hCtl, lvi.iItem, lvi.iSubItem, pszValue); // Next sub item. wsprintf(szKeyName, TEXT("%s%i"), KEY_SUBITEM, ++iKeyCount); } // Next item. wsprintf(szSectionName, TEXT("%s %i"), SEC_ITEM, ++iSectionCount); } } SetCommonTexts(hWndDlg); SetWindowLongPtr(g_hCtl, GWL_STYLE, lStyle); // Sort list view items. if (sb.iSort != -1) ListView_SortItemsEx(g_hCtl, ListView_CompareFunc, &sb); // Set sub item to 1 now we're done using it. lvi.iSubItem = 0; // Focus list view control. SetFocus(g_hCtl); break; case WM_CONTEXTMENU: { // Display a popup menu with check/uncheck all or select/unselect all options. if ((wParam == (WPARAM)g_hCtl)) { // Only display the menu if check boxes are enabled, // multiple selection is supported, // there is 1 or more items in the list. if ((g_iCheckBoxes || !bSingleSelect) && (iValue = ListView_GetItemCount(g_hCtl))) { HMENU hMenu = CreatePopupMenu(); POINT pt; int iSelection; if (g_iCheckBoxes) { AppendMenu(hMenu, MF_STRING, 1, TEXT_CHECKALL); AppendMenu(hMenu, MF_STRING, 2, TEXT_UNCHECKALL); } if (!bSingleSelect && !g_bNoItemSelect) { AppendMenu(hMenu, MF_STRING, 3, TEXT_SELECTALL); AppendMenu(hMenu, MF_STRING, 4, TEXT_UNSELECTALL); } // Get the position to display the popup menu. if (lParam == ((UINT)-1)) { RECT r; GetWindowRect(g_hCtl, &r); pt.x = r.left; pt.y = r.top; } else { pt.x = GET_X_LPARAM(lParam); pt.y = GET_Y_LPARAM(lParam); } // Display the menu and detect which item was selected. iSelection = TrackPopupMenu(hMenu, TPM_NONOTIFY|TPM_RETURNCMD, pt.x, pt.y, 0, g_hCtl, 0); if (iSelection) { for (int i=0; iiDisableCheck) { if (iSelection == 1) { if (g_iCheckBoxes == 2) lvi.state = LVIS_IMAGE_CHECKED; else lvi.state = LVIS_CHECKED; poi->iChecked = TRUE; } else { if (g_iCheckBoxes == 2) lvi.state = LVIS_IMAGE_UNCHECKED; else lvi.state = LVIS_UNCHECKED; poi->iChecked = FALSE; } lvi.mask |= LVIF_STATE; lvi.stateMask = LVIS_STATEIMAGEMASK; ListView_SetItem(g_hCtl, &lvi); } } else { lvi.mask = LVIF_STATE; lvi.stateMask = LVIS_SELECTED; if (iSelection == 3) lvi.state = LVIS_SELECTED; else lvi.state = 0; ListView_SetItem(g_hCtl, &lvi); } } } } } } break; case WM_NOTIFY: { NMHDR* pnmh = (NMHDR*)lParam; switch (pnmh->code) { case LVN_BEGINLABELEDIT: { NMLVDISPINFO* pdi = (NMLVDISPINFO*)lParam; POLDITEM poi; // Get OLDITEM info. pdi->item.mask = LVIF_PARAM; ListView_GetItem(g_hCtl, &pdi->item); poi = (POLDITEM)pdi->item.lParam; // Find out if the item label can be edited. if (poi->bDisableEdit) { // No, so press enter to cancel edit. keybd_event(VK_RETURN, 0, 0, 0); keybd_event(VK_RETURN, 0, KEYEVENTF_KEYUP, 0); } bResult = TRUE; } break; case LVN_ENDLABELEDIT: { NMLVDISPINFO* pdi = (NMLVDISPINFO*)lParam; // Label is being edited. if (pdi->item.pszText != NULL) ListView_SetItemText(g_hCtl, pdi->item.iItem, 0, pdi->item.pszText); bResult = TRUE; } break; case LVN_COLUMNCLICK: { NMLISTVIEW* pnmv = (NMLISTVIEW*)lParam; // Sort list view by selected column. if (sb.iSort == 0) { // Clicking the same column twice? // Will reverse sort order. if (pnmv->iSubItem == sb.iColumn-1) sb.iSort = 1; else sb.iColumn = pnmv->iSubItem+1; // Sort them! ListView_SortItemsEx(g_hCtl, ListView_CompareFunc, &sb); } else if (sb.iSort == 1) { // Clicking the same column twice? // Will reverse sort order. if (pnmv->iSubItem == sb.iColumn-1) sb.iSort = 0; else sb.iColumn = pnmv->iSubItem+1; // Sort them! ListView_SortItemsEx(g_hCtl, ListView_CompareFunc, &sb); } } break; case LVN_ITEMCHANGED: { NMLISTVIEW* pnmv = (NMLISTVIEW*)lParam; // There is a bug where double clicking on the left or right margin of an item // will change the state image incorrectly. This corrects the effects of the bug. if (g_iCheckBoxes) { POLDITEM poi = (POLDITEM)pnmv->lParam; if (g_iCheckBoxes == 2) { if (poi->iDisableCheck) { if (poi->iDisableCheck == 2) lvi.state = LVIS_IMAGE_BLANK; else { if (poi->iChecked) lvi.state = LVIS_IMAGE_DISCHECKED; else lvi.state = LVIS_IMAGE_DISUNCHECKED; } } else { if (poi->iChecked) lvi.state = LVIS_IMAGE_CHECKED; else lvi.state = LVIS_IMAGE_UNCHECKED; } } else { if (poi->iChecked) lvi.state = LVIS_CHECKED; else lvi.state = LVIS_UNCHECKED; } lvi.mask = LVIF_STATE; lvi.stateMask = LVIS_STATEIMAGEMASK; ListView_SetItem(g_hCtl, &lvi); } // Prevent selection of an item. if (g_bNoItemSelect) { if (pnmv->iItem != -1) if (pnmv->uNewState & LVIS_SELECTED) { lvi.mask = LVIF_STATE; lvi.iItem = pnmv->iItem; lvi.stateMask = LVIS_SELECTED; lvi.state = 0; ListView_SetItem(g_hCtl, &lvi); } } else if (bToggleNext && !g_iCheckBoxes) { // Enable Next if there are one or more // selected items. if (ListView_GetSelectedCount(g_hCtl)) EnableWindow(hNext, TRUE); // Otherwise, disable Next button. else EnableWindow(hNext, FALSE); } } break; // List view was clicked on. case NM_DBLCLK: case NM_CLICK: { // This contains the sub item index if it was clicked on. // We can ignore those clicks on sub items because they have no // effect on the check boxes. NMITEMACTIVATE *nmlvi = (NMITEMACTIVATE*)lParam; // The point at which the click took place // so we can perform a 'hit test'. POINT p; LVHITTESTINFO lvhti; // Do not continue if check boxes are not enabled. if (!g_iCheckBoxes) break; // Get mouse cursor position in tree view. GetCursorPos(&p); ScreenToClient(g_hCtl, &p); lvhti.pt.x = p.x; lvhti.pt.y = p.y; // Get check box user just clicked on. if ((lvi.iItem = ListView_HitTest(g_hCtl, &lvhti)) != -1) { POLDITEM poi; // Get item. lvi.mask = LVIF_PARAM | LVIF_STATE; ListView_GetItem(g_hCtl, &lvi); // Get OLDITEM info. poi = (POLDITEM)lvi.lParam; lvi.mask = LVIF_STATE; lvi.stateMask = LVIS_STATEIMAGEMASK; // Clicked on a check box? if ((lvhti.flags & LVHT_ONITEMSTATEICON) && !nmlvi->iSubItem) { // Don't do anything if we are using the check bitmap // and the this check box is disabled. if (!poi->iDisableCheck && (g_iCheckBoxes == 2)) { // Set check state. if (poi->iChecked == TRUE) { lvi.state = LVIS_IMAGE_UNCHECKED; poi->iChecked = FALSE; } else { lvi.state = LVIS_IMAGE_CHECKED; poi->iChecked = TRUE; } ListView_SetItem(g_hCtl, &lvi); // Disable next button. if (bToggleNext) { if (ListView_IsItemChecked()) EnableWindow(hNext, TRUE); else EnableWindow(hNext, FALSE); } } // If we aren't using the check bitamp... else if (g_iCheckBoxes == 1) { // The node check state cannot be changed... if (poi->iDisableCheck) { // Check box must stay checked. if (poi->iChecked) lvi.state = LVIS_UNCHECKED; // Check box must stay unchecked. else lvi.state = LVIS_CHECKED; } else { // Check box is to be unchecked. if (poi->iChecked) poi->iChecked = FALSE; // Check box is to be checked. else poi->iChecked = TRUE; lvi.mask = LVIF_PARAM; } ListView_SetItem(g_hCtl, &lvi); } } } } break; } } break; case WM_DESTROY: { ListView_FreeParams(); } break; case WM_CTLCOLORSTATIC: case WM_CTLCOLOREDIT: case WM_CTLCOLORDLG: case WM_CTLCOLORBTN: case WM_CTLCOLORLISTBOX: // Let the NSIS window handle colours, it knows best. bResult = SendMessage(g_hWndParent, uMsg, wParam, lParam); } LocalFree(pszValue); return bResult; } // Search for all checked nodes and push them onto NSIS stack. void TreeView_SaveState(HTREEITEM htvi) { static HTREEITEM htvic, htvicc; static TVITEM tvi; static POLDITEM poi; static TCHAR szSectionName[32], szNumber[4]; PTCHAR pszValue = (PTCHAR)LocalAlloc(LPTR, sizeof(TCHAR)*g_stringsize); do { // Get node. tvi.mask = TVIF_HANDLE | TVIF_TEXT | TVIF_PARAM | TVIF_STATE; tvi.stateMask = TVIS_STATEIMAGEMASK; tvi.hItem = htvi; tvi.pszText = pszValue; tvi.cchTextMax = g_stringsize; TreeView_GetItem(g_hCtl, &tvi); // Get old node info. poi = (POLDITEM)tvi.lParam; // Get INI file section. wsprintf(szSectionName, TEXT("%s %i"), SEC_ITEM, poi->iOldIndex); // Can check box state be changed? // If not, then no state changes will have occured. if (!poi->iDisableCheck) { // Is item checked? if (poi->iChecked) { // Push checked item number or text onto stack. if (!g_bNoItemSelect) { // Add item text to stack. if (g_bReturnItemText) pushstring(tvi.pszText); // Add item number to stack. else { wsprintf(szNumber, TEXT("%i"), poi->iOldIndex); pushstring(szNumber); } } // Save checked state in INI file. WriteINIStr(szSectionName, KEY_CHECKED, TEXT("1")); } else // Save checked state in INI file. WriteINIStr(szSectionName, KEY_CHECKED, TEXT("0")); } // Save list item text if it has been modified. if (g_bLabelEdit) if (!poi->bDisableEdit) if (lstrcmp(poi->pszOldText, tvi.pszText) != 0) WriteINIStr(szSectionName, KEY_TEXT, tvi.pszText); // Next child node. if (htvic = TreeView_GetChild(g_hCtl, htvi)) { // Check if this node is expanded. if (tvi.state & TVIS_EXPANDED) WriteINIStr(szSectionName, KEY_EXPANDED, TEXT("1")); else WriteINIStr(szSectionName, KEY_EXPANDED, TEXT("0")); TreeView_SaveState(htvic); } } while (htvi = TreeView_GetNextSibling(g_hCtl, htvi)); LocalFree(pszValue); } // Recursively check all child nodes. void TreeView_CheckChildNodes(HTREEITEM htvi, BOOL state) { HTREEITEM htvic; TVITEM tvi; POLDITEM poi; do { // Get node. tvi.mask = TVIF_HANDLE | TVIF_PARAM; tvi.hItem = htvi; TreeView_GetItem(g_hCtl, &tvi); poi = (POLDITEM)tvi.lParam; // Check this node state is allowed to be changed... if (!poi->iDisableCheck) { poi->iChecked = state; if (state) tvi.state = (g_iCheckBoxes == 2 ? TVIS_IMAGE_CHECKED : TVIS_CHECKED); else tvi.state = (g_iCheckBoxes == 2 ? TVIS_IMAGE_UNCHECKED : TVIS_UNCHECKED); tvi.mask |= TVIF_STATE; tvi.stateMask = TVIS_STATEIMAGEMASK; TreeView_SetItem(g_hCtl, &tvi); } // Next child node. if (htvic = TreeView_GetChild(g_hCtl, htvi)) TreeView_CheckChildNodes(htvic, state); } while (htvi = TreeView_GetNextSibling(g_hCtl, htvi)); } // Counts the total number of siblings of a node (including the node itself). // The first child node in a tree must be passed. void TreeView_GetSiblingCount(HTREEITEM htvi, int &iCheckedCount, int &iDisabledCheckedCount, int &iCount) { TVITEM tvi; do { (int*)iCount++; // Get node. tvi.mask = TVIF_HANDLE | TVIF_PARAM; tvi.hItem = htvi; TreeView_GetItem(g_hCtl, &tvi); // Is item checked... if (((POLDITEM)tvi.lParam)->iChecked) { if (((POLDITEM)tvi.lParam)->iDisableCheck) (int*)iDisabledCheckedCount++; else (int*)iCheckedCount++; } } while (htvi = TreeView_GetNextSibling(g_hCtl, htvi)); } // Loops through parent nodes backwards to set their checked state. void TreeView_SetParentNodeCheckState(HTREEITEM htvi) { TVITEM tvi; POLDITEM poi; while (htvi = TreeView_GetParent(g_hCtl, htvi)) { // Get number of siblings. int iCheckedCount = 0, iDisabledCheckedCount = 0, iCount = 0; TreeView_GetSiblingCount(TreeView_GetChild(g_hCtl, htvi), iCheckedCount, iDisabledCheckedCount, iCount); tvi.mask = TVIF_HANDLE | TVIF_PARAM; tvi.hItem = htvi; TreeView_GetItem(g_hCtl, &tvi); poi = (POLDITEM)tvi.lParam; // Manage parent node states when using the check bitmap. if (g_iCheckBoxes == 2) { // Uncheck parent node. if ((iCheckedCount + iDisabledCheckedCount) == 0) { if (poi->iDisableCheck == 1) tvi.state = TVIS_IMAGE_DISUNCHECKED; else if (poi->iDisableCheck == 2) tvi.state = TVIS_IMAGE_BLANK; else tvi.state = TVIS_IMAGE_UNCHECKED; poi->iChecked = FALSE; } // Partially check parent node. else if ((iCheckedCount + iDisabledCheckedCount) != iCount) { if (poi->iDisableCheck == 1) tvi.state = TVIS_IMAGE_DISCHECKED; else if (poi->iDisableCheck == 2) tvi.state = TVIS_IMAGE_BLANK; else tvi.state = TVIS_IMAGE_PCHECKED; if (iCheckedCount) poi->iChecked = 3; else poi->iChecked = 2; } // Check parent node. else { if (poi->iDisableCheck == 1) tvi.state = TVIS_IMAGE_DISCHECKED; else if (poi->iDisableCheck == 2) tvi.state = TVIS_IMAGE_BLANK; else tvi.state = TVIS_IMAGE_CHECKED; poi->iChecked = TRUE; } } // Manage parent node states when not using the check bitmap. else { // Uncheck parent node. if (iCheckedCount != iCount) { tvi.state = TVIS_UNCHECKED; poi->iChecked = FALSE; } // Check parent node. else { tvi.state = TVIS_CHECKED; poi->iChecked = TRUE; } } tvi.mask |= TVIF_STATE; tvi.stateMask = TVIS_STATEIMAGEMASK; TreeView_SetItem(g_hCtl, &tvi); } } // Loops through all nodes to set the check state of all parent nodes. void TreeView_SetAllParentNodeCheckState(HTREEITEM htvi) { HTREEITEM htvic; do { if (htvic = TreeView_GetChild(g_hCtl, htvi)) TreeView_SetAllParentNodeCheckState(htvic); htvic = htvi; } while (htvi = TreeView_GetNextSibling(g_hCtl, htvi)); TreeView_SetParentNodeCheckState(htvic); } // Check or uncheck all nodes in the tree view. void TreeView_SetAllNodesCheckState(HTREEITEM htvi, BOOL state) { HTREEITEM htvic; TVITEM tvi; POLDITEM poi; do { htvic = TreeView_GetChild(g_hCtl, htvi); if ((g_bParentCheck && !htvic) || !g_bParentCheck) { tvi.mask = TVIF_HANDLE | TVIF_PARAM; tvi.hItem = htvi; TreeView_GetItem(g_hCtl, &tvi); poi = (POLDITEM)tvi.lParam; if (!poi->iDisableCheck) { tvi.mask |= TVIF_STATE; tvi.stateMask = TVIS_STATEIMAGEMASK; poi->iChecked = state; if (state) tvi.state = (g_iCheckBoxes == 2 ? TVIS_IMAGE_CHECKED : TVIS_CHECKED); else tvi.state = (g_iCheckBoxes == 2 ? TVIS_IMAGE_UNCHECKED : TVIS_UNCHECKED); TreeView_SetItem(g_hCtl, &tvi); } } if (htvic) TreeView_SetAllNodesCheckState(htvic, state); } while (htvi = TreeView_GetNextSibling(g_hCtl, htvi)); } // Check all nodes that have iChecked = TRUE set. void TreeView_CheckNodesToBeChecked(HTREEITEM htvi) { HTREEITEM htvic; TVITEM tvi; POLDITEM poi; do { htvic = TreeView_GetChild(g_hCtl, htvi); if ((g_bParentCheck && !htvic) || !g_bParentCheck) { tvi.mask = TVIF_PARAM | TVIF_HANDLE; tvi.hItem = htvi; TreeView_GetItem(g_hCtl, &tvi); poi = (POLDITEM)tvi.lParam; if (poi->iDisableCheck && (g_iCheckBoxes == 2)) { if (poi->iDisableCheck == 2) tvi.state = TVIS_IMAGE_BLANK; else { if (poi->iChecked == TRUE) tvi.state = TVIS_IMAGE_DISCHECKED; else tvi.state = TVIS_IMAGE_DISUNCHECKED; } } else { if (poi->iChecked == TRUE) tvi.state = (g_iCheckBoxes == 2 ? TVIS_IMAGE_CHECKED : TVIS_CHECKED); else tvi.state = (g_iCheckBoxes == 2 ? TVIS_IMAGE_UNCHECKED : TVIS_UNCHECKED); } tvi.mask = TVIF_STATE | TVIF_HANDLE; tvi.stateMask = TVIS_STATEIMAGEMASK; TreeView_SetItem(g_hCtl, &tvi); } if (htvic) TreeView_CheckNodesToBeChecked(htvic); } while (htvi = TreeView_GetNextSibling(g_hCtl, htvi)); } // Get number of checked nodes. int TreeView_IsNodeChecked(HTREEITEM htvi) { HTREEITEM htvic; TVITEM tvi; do { tvi.mask = TVIF_PARAM | TVIF_HANDLE; tvi.hItem = htvi; TreeView_GetItem(g_hCtl, &tvi); if (((POLDITEM)tvi.lParam)->iChecked == TRUE) return TRUE; if (htvic = TreeView_GetChild(g_hCtl, htvi)) if (TreeView_IsNodeChecked(htvic)) return TRUE; } while (htvi = TreeView_GetNextSibling(g_hCtl, htvi)); return FALSE; } // Frees memory allocated for each tree view node. void TreeView_FreeParams(HTREEITEM htvi) { HTREEITEM htvic; TVITEM tvi; POLDITEM poi; tvi.mask = TVIF_PARAM; do { // Get lParam. tvi.hItem = htvi; TreeView_GetItem(g_hCtl, &tvi); // Get OLDITEM data. poi = (POLDITEM)tvi.lParam; // Free memory allocated. GlobalFree(poi->pszOldText); GlobalFree(poi); // Next child node. if (htvic = TreeView_GetChild(g_hCtl, htvi)) TreeView_FreeParams(htvic); } while (htvi = TreeView_GetNextSibling(g_hCtl, htvi)); } // Handles the parent dialog for list view dialog. static BOOL CALLBACK TreeView_ParentDlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { static HTREEITEM htvi; static TVITEM tvi; static POLDITEM poi; static BOOL bRes = FALSE; static int i; PTCHAR pszValue = (PTCHAR)LocalAlloc(LPTR, sizeof(TCHAR)*g_stringsize); if (uMsg == WM_NOTIFY_OUTER_NEXT && wParam == 1) { pushstring(OUT_ENDSTACK); // Save the tree view state. if (htvi = TreeView_GetRoot(g_hCtl)) TreeView_SaveState(htvi); // If items can be selected, return // the selected item on the stack. if (!g_bNoItemSelect && !g_iCheckBoxes) { if ((tvi.hItem = TreeView_GetSelection(g_hCtl))) { // Add item text to stack. if (g_bReturnItemText) { tvi.mask = TVIF_TEXT | TVIF_HANDLE; tvi.pszText = pszValue; tvi.cchTextMax = g_stringsize; TreeView_GetItem(g_hCtl, &tvi); pushstring(tvi.pszText); } // Add item number to stack. else { tvi.mask = TVIF_PARAM | TVIF_HANDLE; TreeView_GetItem(g_hCtl, &tvi); poi = (POLDITEM)tvi.lParam; wsprintf(pszValue, TEXT("%i"), poi->iOldIndex); pushstring(pszValue); } } } } LocalFree(pszValue); bRes = CallWindowProc((long (__stdcall *)(struct HWND__ *,unsigned int,unsigned int,long))ParentDlgProcOld,hWnd,uMsg,wParam,lParam); if (uMsg == WM_NOTIFY_OUTER_NEXT && !bRes) { if (wParam == -1) g_is_back = TRUE; else if (wParam == NOTIFY_BYE_BYE) g_is_cancel = TRUE; g_done = TRUE; PostMessage(g_hDialog, WM_CLOSE, 0, 0); } return bRes; } // Handles TreeView dialog. static LRESULT CALLBACK TreeView_DlgProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { static HWND hNext = GetDlgItem(g_hWndParent, IDC_NEXT); static BOOL bToggleNext = TRUE, bDialogShown = FALSE; static TCHAR szSectionName[32], szKeyName[32]; static int iValue, iSectionCount, iKeyCount, ihtviListCount, ihtviListIndex; static HIMAGELIST himlIcons, himlState; static TVITEM tvi; static TVITEM tvip; static HTREEITEM htvi; static HTREEITEM htviList[8]; static TVINSERTSTRUCT tviins; static DWORD lStyle = 0; static HICON hIcon; PTCHAR pszValue = (PTCHAR)LocalAlloc(LPTR, sizeof(TCHAR)*g_stringsize); BOOL bResult = FALSE; switch (uMsg) { case WM_SHOWWINDOW: { if (g_iCheckBoxes && !bDialogShown) { htvi = TreeView_GetRoot(g_hCtl); // Check all nodes to be checked (iChecked == TRUE). TreeView_CheckNodesToBeChecked(htvi); // Set the state of all parent nodes. if (g_bParentCheck) TreeView_SetAllParentNodeCheckState(htvi); } // Disable next button. if (bToggleNext) if (!TreeView_IsNodeChecked(TreeView_GetRoot(g_hCtl))) EnableWindow(hNext, FALSE); bDialogShown = TRUE; } break; case WM_INITDIALOG: g_hCtl = GetDlgItem(hWndDlg, IDC_LIST); lStyle = GetWindowLongPtr(g_hCtl, GWL_STYLE); { // Disable the next button and re-enable when a item is selected or checked? if (ReadINIInt(iValue, SEC_SETTINGS, KEY_NEXTTOGGLE, 0) == 0) bToggleNext = FALSE; // Do not allow item selection. if (ReadINIInt(iValue, SEC_SETTINGS, KEY_NOITEMSELECT, 0) == 1) g_bNoItemSelect = TRUE; // Return selected/checked items by item text rather than item number. if (ReadINIInt(iValue, SEC_SETTINGS, KEY_RETURNITEMTEXT, 0) == 1) g_bReturnItemText = TRUE; // Parent nodes uncheck child nodes when checked. if (ReadINIInt(iValue, SEC_SETTINGS, KEY_PARENTCHECK, 0)) g_bParentCheck = TRUE; // Add icon files to image list. if (himlIcons = GetIcons()) TreeView_SetImageList(g_hCtl, himlIcons, TVSIL_NORMAL); // Add check boxes to the list view. if (ReadINIInt(iValue, SEC_SETTINGS, KEY_CHECKBOXES, 0)) { g_iCheckBoxes = TRUE; // Use NSIS check bitmap for state image list. if (ReadINIStr(pszValue, SEC_SETTINGS, KEY_USECHECKBITMAP, TEXT("1")) && lstrcmp(pszValue, TEXT("0")) != 0) { if (himlState = GetStateImage(pszValue)) { TreeView_SetImageList(g_hCtl, himlState, TVSIL_STATE); TreeView_SetItemHeight(g_hCtl, 16); } else lStyle |= TVS_CHECKBOXES; } else lStyle |= TVS_CHECKBOXES; } // All tree nodes have editable labels. if (ReadINIInt(iValue, SEC_SETTINGS, KEY_LABELEDIT, 0)) { g_bLabelEdit = TRUE; lStyle |= TVS_EDITLABELS; } // Add tree nodes. ihtviListCount = 1; wsprintf(szSectionName, TEXT("%s %i"), SEC_ITEM, iSectionCount = 1); while (ReadINIStr(pszValue, szSectionName, KEY_TEXT, TEXT(""))) { // Set general item properties. tvi.mask = TVIF_HANDLE | TVIF_TEXT | TVIF_PARAM | TVIF_STATE; tvi.pszText = pszValue; tvi.cchTextMax = g_stringsize; tvi.state = 0; tvi.stateMask = 0; // Store original item info in item tag. tvi.lParam = (LPARAM)CreateOldItem(iSectionCount, szSectionName, pszValue); // Have an image list present? if (himlIcons) { // Setting an icon on the node? if (ReadINIInt(iValue, szSectionName, KEY_ICONINDEX, 0) != 0) { tvi.mask |= TVIF_IMAGE | TVIF_SELECTEDIMAGE; tvi.iImage = iValue-1; // Conversion to zero-based. tvi.iSelectedImage = iValue-1; // Conversion to zero-based. } } // No position set, just insert under last inserted node! if (ReadINIInt(ihtviListIndex, szSectionName, KEY_POSITION, 0) != 0) { ihtviListCount++; if (ihtviListIndex > ihtviListCount) ihtviListIndex = ihtviListCount; if (ihtviListIndex-2 < 0) tviins.hParent = TVI_ROOT; else tviins.hParent = htviList[ihtviListIndex-2]; } else ihtviListIndex = ihtviListCount; // Bold tree node? if (ReadINIInt(iValue, szSectionName, KEY_BOLDTEXT, 0) == 1) { tvi.state |= TVIS_BOLD; tvi.stateMask |= TVIS_BOLD; } // Expand tree node? if (ReadINIInt(iValue, szSectionName, KEY_EXPANDED, 0) == 1) { tvi.state |= TVIS_EXPANDED; tvi.stateMask |= TVIS_EXPANDED; } // Insert the new node. tviins.item = tvi; htvi = TreeView_InsertItem(g_hCtl, &tviins); htviList[ihtviListIndex-1] = htvi; // Next item. wsprintf(szSectionName, TEXT("%s %i"), SEC_ITEM, ++iSectionCount); } } SetCommonTexts(hWndDlg); SetWindowLongPtr(g_hCtl, GWL_STYLE, lStyle); break; case WM_CONTEXTMENU: { // Display a popup menu with check/uncheck all options. if ((wParam == (WPARAM)g_hCtl)) { // Only display the menu if there is at least 1 node // and check boxes are enabled. if (g_iCheckBoxes && TreeView_GetCount(g_hCtl)) { HMENU hMenu = CreatePopupMenu(); POINT pt; // Add the two options. AppendMenu(hMenu, MF_STRING, 1, TEXT_CHECKALL); AppendMenu(hMenu, MF_STRING, 2, TEXT_UNCHECKALL); // Get the position to display the popup menu. if (lParam == ((UINT)-1)) { RECT r; GetWindowRect(g_hCtl, &r); pt.x = r.left; pt.y = r.top; } else { pt.x = GET_X_LPARAM(lParam); pt.y = GET_Y_LPARAM(lParam); } // Display the menu and detect which item was selected. switch (TrackPopupMenu(hMenu, TPM_NONOTIFY|TPM_RETURNCMD, pt.x, pt.y, 0, g_hCtl, 0)) { case 1: TreeView_SetAllNodesCheckState(TreeView_GetRoot(g_hCtl), TRUE); break; case 2: TreeView_SetAllNodesCheckState(TreeView_GetRoot(g_hCtl), FALSE); break; } // Set the state of all parent nodes. if (g_bParentCheck) TreeView_SetAllParentNodeCheckState(htvi); } } } break; case WM_NOTIFY: { NMHDR* pnmh = (NMHDR*)lParam; switch (pnmh->code) { case TVN_SELCHANGED: { // Unselect any selected items. if (!g_bLabelEdit && g_bNoItemSelect) TreeView_SelectItem(g_hCtl, NULL); } break; case TVN_BEGINLABELEDIT: { NMTVDISPINFO* ptvdi = (NMTVDISPINFO*)lParam; POLDITEM poi; // Get OLDITEM info. ptvdi->item.mask = TVIF_PARAM | TVIF_HANDLE; TreeView_GetItem(g_hCtl, &ptvdi->item); poi = (POLDITEM)ptvdi->item.lParam; // Find out if the item label can be edited. if (poi->bDisableEdit) { int i; // No, so press tab to cancel edit. for (i=0; i<4; i++) { // Press it 4 times to tab back to TV control. keybd_event(VK_TAB, 0, 0, 0); keybd_event(VK_TAB, 0, KEYEVENTF_KEYUP, 0); } } bResult = TRUE; } break; case TVN_ENDLABELEDIT: { NMTVDISPINFO* ptvdi = (NMTVDISPINFO*)lParam; // Label is being edited. if (ptvdi->item.pszText != NULL) { ptvdi->item.mask = TVIF_TEXT; TreeView_SetItem(g_hCtl, &ptvdi->item); } bResult = TRUE; } break; // Tree view was clicked on. case NM_DBLCLK: case NM_CLICK: { // The point at which the click took place // so we can perform a 'hit test'. POINT p; TVHITTESTINFO tvhti; // Do not continue if check boxes are not enabled. if (!g_iCheckBoxes) break; // Get mouse cursor position in tree view. GetCursorPos(&p); ScreenToClient(g_hCtl, &p); tvhti.pt.x = p.x; tvhti.pt.y = p.y; // Get item user just clicked on. if ((tvi.hItem = TreeView_HitTest(g_hCtl, &tvhti)) != NULL) { POLDITEM poi; // Get item. tvi.mask = TVIF_PARAM | TVIF_HANDLE; TreeView_GetItem(g_hCtl, &tvi); // Get OLDITEM info. poi = (POLDITEM)tvi.lParam; tvi.stateMask = TVIS_STATEIMAGEMASK; // Clicked on a check box? if (tvhti.flags & TVHT_ONITEMSTATEICON) { // Don't do anything if we are using the check bitmap // and the this check box is disabled. if (!poi->iDisableCheck && (g_iCheckBoxes == 2)) { // Manage the parent node. if (g_bParentCheck && (htvi = TreeView_GetChild(g_hCtl, tvi.hItem))) { TreeView_CheckChildNodes(htvi, (poi->iChecked == 3 || poi->iChecked == 1 ? FALSE : TRUE)); TreeView_SetParentNodeCheckState(htvi); } else { // Check box will be unchecked. if (poi->iChecked == TRUE) { tvi.state = TVIS_IMAGE_UNCHECKED; poi->iChecked = FALSE; } else { tvi.state = TVIS_IMAGE_CHECKED; poi->iChecked = TRUE; } tvi.mask |= TVIF_STATE; TreeView_SetItem(g_hCtl, &tvi); TreeView_SetParentNodeCheckState(tvi.hItem); } // Disable next button. if (bToggleNext) { if (TreeView_IsNodeChecked(TreeView_GetRoot(g_hCtl))) EnableWindow(hNext, TRUE); else EnableWindow(hNext, FALSE); } } // If we aren't using the check bitamp... else if (g_iCheckBoxes == 1) { // The node check state cannot be changed... if (poi->iDisableCheck) { // Check box must stay checked. if (poi->iChecked) { if (pnmh->code == NM_DBLCLK) tvi.state = TVIS_CHECKED; else tvi.state = TVIS_UNCHECKED; } // Check box must stay unchecked. else { if (pnmh->code == NM_DBLCLK) tvi.state = TVIS_UNCHECKED; else tvi.state = TVIS_CHECKED; } tvi.mask = TVIF_STATE | TVIF_HANDLE; } else { tvi.mask = TVIF_PARAM | TVIF_HANDLE; // Check box is to be unchecked. if (poi->iChecked) poi->iChecked = FALSE; // Check box is to be checked. else poi->iChecked = TRUE; // Manage the parent node. if (g_bParentCheck) { if (htvi = TreeView_GetChild(g_hCtl, tvi.hItem)) { TreeView_CheckChildNodes(htvi, poi->iChecked); TreeView_SetParentNodeCheckState(htvi); } else TreeView_SetParentNodeCheckState(tvi.hItem); // Correct the parent node state as it is about to be toggled. if (poi->iChecked) { if (pnmh->code == NM_DBLCLK) tvi.state = TVIS_CHECKED; else tvi.state = TVIS_UNCHECKED; } else { if (pnmh->code == NM_DBLCLK) tvi.state = TVIS_UNCHECKED; else tvi.state = TVIS_CHECKED; } tvi.mask |= TVIF_STATE; } } TreeView_SetItem(g_hCtl, &tvi); } } // There is a bug where double clicking on the left margin of the state image // will change the state icon incorrectly. This corrects the effects of the bug. else { if (poi->iDisableCheck && (g_iCheckBoxes == 2)) { if (poi->iDisableCheck == 2) tvi.state = TVIS_IMAGE_BLANK; else { if (poi->iChecked) tvi.state = TVIS_IMAGE_DISCHECKED; else tvi.state = TVIS_IMAGE_DISUNCHECKED; } } else { if (poi->iChecked == TRUE) tvi.state = (g_iCheckBoxes == 2 ? TVIS_IMAGE_CHECKED : TVIS_CHECKED); else if ((poi->iChecked >= 2) && (g_iCheckBoxes == 2)) tvi.state = TVIS_IMAGE_PCHECKED; else tvi.state = TVIS_IMAGE_UNCHECKED; } tvi.mask = TVIF_HANDLE | TVIF_STATE; TreeView_SetItem(g_hCtl, &tvi); } } } break; } } break; case WM_DESTROY: { TreeView_FreeParams(TreeView_GetRoot(g_hCtl)); } break; case WM_CTLCOLORSTATIC: case WM_CTLCOLOREDIT: case WM_CTLCOLORDLG: case WM_CTLCOLORBTN: case WM_CTLCOLORLISTBOX: // Let the NSIS window handle colours, it knows best. bResult = SendMessage(g_hWndParent, uMsg, wParam, lParam); } LocalFree(pszValue); return bResult; } void CreateDialogs() { TCHAR szDialog[16]; popstring(g_szINIFilePath); g_done = TRUE; // Find out which page to use. ReadINIStr(szDialog, SEC_SETTINGS, KEY_TYPE, TEXT("ListView")); // Which page to use? if (lstrcmpi(szDialog, DLG_LISTVIEW) == 0) { g_iDialog = 0; g_hDialog = CreateDialog(g_hInstance, MAKEINTRESOURCE(IDD_LISTVIEW), g_hWndParent, (DLGPROC)ListView_DlgProc); } else { g_iDialog = 1; g_hDialog = CreateDialog(g_hInstance, MAKEINTRESOURCE(IDD_TREEVIEW), g_hWndParent, (DLGPROC)TreeView_DlgProc); } } // Plugin callback for new plugin API. static UINT_PTR PluginCallback(enum NSPIM msg) { return 0; } NSISFUNC(InitDialog) { DLL_INIT(); if (!g_bInitDialog) { TCHAR szHWND[32]; CreateDialogs(); // Return page HWND. wsprintf(szHWND, TEXT("%d"), g_hDialog); pushstring(szHWND); g_bInitDialog = TRUE; } else pushstring(OUT_ERROR); } NSISFUNC(Show) { DLL_INIT(); if (g_bInitDialog) { g_bInitDialog = FALSE; ShowDialog(); } else pushstring(OUT_ERROR); } NSISFUNC(Dialog) { DLL_INIT(); if (!g_bInitDialog) { CreateDialogs(); ShowDialog(); } else pushstring(OUT_ERROR); } // Entry point for DLL. BOOL WINAPI DllMain(HINSTANCE hInst, ULONG ul_reason_for_call, LPVOID lpReserved) { g_hInstance=hInst; return TRUE; }