/* * lv - common dialog proc handler for listview pages */ #include "tweakui.h" #pragma BEGIN_CONST_DATA #pragma END_CONST_DATA /***************************************************************************** * * LV_AddItem * * Add an entry to the listview. * * Returns the resulting item number. * *****************************************************************************/ int PASCAL LV_AddItem(HWND hwnd, int ix, LPCTSTR ptszDesc, int iImage, BOOL fState) { LV_ITEM lvi; lvi.mask = LVIF_TEXT | LVIF_PARAM; lvi.iItem = MAXLONG; lvi.iSubItem = 0; /* Must be zero */ lvi.pszText = (LPTSTR)ptszDesc; lvi.lParam = ix; /* Take it */ if (iImage >= 0) { lvi.iImage = iImage; lvi.mask |= LVIF_IMAGE; } if (fState >= 0) { lvi.state = INDEXTOSTATEIMAGEMASK(fState + 1); lvi.mask |= LVIF_STATE; } return ListView_InsertItem(hwnd, &lvi); } /***************************************************************************** * * LV_Toggle * * Toggle the state icon of the current selection. * *****************************************************************************/ void PASCAL LV_Toggle(HWND hwnd, int iItem) { LV_ITEM lvi; lvi.stateMask = LVIS_STATEIMAGEMASK; Misc_LV_GetItemInfo(hwnd, &lvi, iItem, LVIF_STATE); if (lvi.state & LVIS_STATEIMAGEMASK) { lvi.state ^= INDEXTOSTATEIMAGEMASK(1) ^ INDEXTOSTATEIMAGEMASK(2); /* toggle checkmark */ ListView_SetItem(hwnd, &lvi); /* Set the state */ Common_SetDirty(GetParent(hwnd)); } } /***************************************************************************** * * LV_Rename * * Rename an item in the list view. * *****************************************************************************/ void PASCAL LV_Rename(HWND hwnd, int iItem) { ListView_EditLabel(hwnd, iItem); } /***************************************************************************** * * LV_ResizeReportColumn * * Resize the column in the report to be as large as possible without * colliding with the vertical scrollbar (if any). * *****************************************************************************/ void PASCAL LV_ResizeReportColumn(HWND hwnd) { ListView_SetColumnWidth(hwnd, 0, LVSCW_AUTOSIZE); } /***************************************************************************** * * LV_OnInitDialog * * The callback will initialize the listview. * * Once the callback is happy, we initialize the columns. * * All of our listviews are simple reports with but one column. * *****************************************************************************/ /* New IE5 feature we will use if available */ #define LVS_EX_LABELTIP 0x00004000 #define LVM_SETEXTENDEDLISTVIEWSTYLE (LVM_FIRST + 54) #define ListView_SetExtendedListViewStyle(hwndLV, dw)\ (DWORD)SNDMSG((hwndLV), LVM_SETEXTENDEDLISTVIEWSTYLE, 0, dw) BOOL PASCAL LV_OnInitDialog(PLVV plvv, HWND hdlg) { HWND hwnd = GetDlgItem(hdlg, IDC_LISTVIEW); ListView_SetExtendedListViewStyle(hwnd, LVS_EX_LABELTIP); if (plvv->lvvfl & lvvflCanCheck) { ListView_SetImageList(hwnd, pcdii->himlState, LVSIL_STATE); } if (plvv->lvvfl & lvvflIcons) { ListView_SetImageList(hwnd, GetSystemImageList(SHGFI_SMALLICON), LVSIL_SMALL); } if (plvv->OnInitDialog(hwnd)) { /* Add the column to the report */ LV_COLUMN col; col.mask = 0; ListView_InsertColumn(hwnd, 0, &col); LV_ResizeReportColumn(hwnd); Misc_LV_SetCurSel(hwnd, 0); } return 1; } /***************************************************************************** * * LV_OnLvContextMenu * * The context menu shall appear in the listview. * * Allow the callback to modify the menu before we pop it up. * Then let the window procedure's WM_COMMAND do the rest. * * If lvvflCanCheck is set, we will automatically adjust IDC_LVTOGGLE * to match. * * If lvvflCanDelete is set, we will adjust IDC_LVDELETE to match. * *****************************************************************************/ void PASCAL LV_OnLvContextMenu(PLVV plvv, HWND hdlg, HWND hwnd, POINT pt) { int iItem = Misc_LV_GetCurSel(hwnd); if (iItem != -1) { HMENU hmenu; ClientToScreen(hwnd, &pt); /* Make it screen coordinates */ hmenu = GetSubMenu(pcdii->hmenu, plvv->iMenu); if (plvv->lvvfl & lvvflCanCheck) { MENUITEMINFO mii; LV_ITEM lvi; lvi.stateMask = LVIS_STATEIMAGEMASK; Misc_LV_GetItemInfo(hwnd, &lvi, iItem, LVIF_STATE); mii.cbSize = cbX(mii); mii.fMask = MIIM_STATE; switch (isiPlvi(&lvi)) { case isiUnchecked: mii.fState = MFS_ENABLED | MFS_UNCHECKED; break; case isiChecked: mii.fState = MFS_ENABLED | MFS_CHECKED; break; default: mii.fState = MFS_DISABLED; break; } SetMenuItemInfo(hmenu, IDC_LVTOGGLE, 0, &mii); } if (plvv->lvvfl & lvvflCanDelete) { Misc_EnableMenuFromHdlgId(hmenu, hdlg, IDC_LVDELETE); } if (plvv->OnInitContextMenu) { plvv->OnInitContextMenu(hwnd, iItem, hmenu); } TrackPopupMenuEx(hmenu, TPM_RIGHTBUTTON | TPM_VERTICAL | TPM_LEFTALIGN | TPM_TOPALIGN, pt.x, pt.y, hdlg, 0); } } /***************************************************************************** * * LV_OnContextMenu * * If the context menu came from the listview, figure out which * item got clicked on. If we find an item, pop up its context * menu. Otherwise, just do the standard help thing. * * NOTE! We don't use LVHT_ONITEM because ListView is broken! * Watch: * * #define LVHT_ONITEMSTATEICON 0x0008 * #define LVHT_ABOVE 0x0008 * * Oops. This means that clicks above the item are treated as * clicks on the state icon. * * Fortunately, we reside completely in report view, so you can't * legally click above the item. The only way it can happen is * if the coordinates to OnContextMenu are out of range, so we * catch that up front and munge it accordingly. * *****************************************************************************/ void PASCAL LV_OnContextMenu(PLVV plvv, HWND hdlg, HWND hwnd, LPARAM lp) { if (GetDlgCtrlID(hwnd) == IDC_LISTVIEW) { LV_HITTESTINFO hti; if ((DWORD)lp == 0xFFFFFFFF) { /* Pretend it was on the center of the small icon */ ListView_GetItemPosition(hwnd, Misc_LV_GetCurSel(hwnd), &hti.pt); hti.pt.x += GetSystemMetrics(SM_CXSMICON) / 2; hti.pt.y += GetSystemMetrics(SM_CYSMICON) / 2; LV_OnLvContextMenu(plvv, hdlg, hwnd, hti.pt); } else { Misc_LV_HitTest(hwnd, &hti, lp); if ((hti.flags & LVHT_ONITEM)) { /* Because LV sometimes forgets to move the focus... */ Misc_LV_SetCurSel(hwnd, hti.iItem); LV_OnLvContextMenu(plvv, hdlg, hwnd, hti.pt); } else { Common_OnContextMenu((WPARAM)hwnd, plvv->pdwHelp); } } } else { Common_OnContextMenu((WPARAM)hwnd, plvv->pdwHelp); } } /***************************************************************************** * * LV_OnCommand_Dispatch * * Dispatch a recognized command to the handler. * *****************************************************************************/ void PASCAL LV_OnCommand_Dispatch(void (PASCAL *pfn)(HWND hwnd, int iItem), HWND hdlg) { HWND hwnd = GetDlgItem(hdlg, IDC_LISTVIEW); int iItem = Misc_LV_GetCurSel(hwnd); if (iItem != -1) { pfn(hwnd, iItem); } } /***************************************************************************** * * LV_OnCommand * * Ooh, we got a command. * * See if it's one of ours. If not, pass it through to the handler. * *****************************************************************************/ BOOL PASCAL LV_OnCommand(PLVV plvv, HWND hdlg, int id, UINT codeNotify) { PLVCI plvci; switch (id) { case IDC_LVTOGGLE: LV_OnCommand_Dispatch(LV_Toggle, hdlg); break; case IDC_LVRENAME: if (plvv->Dirtify) LV_OnCommand_Dispatch(LV_Rename, hdlg); break; default: if (plvv->rglvci) { for (plvci = plvv->rglvci; plvci[0].id; plvci++) { if (id == plvci->id) { LV_OnCommand_Dispatch(plvci->pfn, hdlg); goto dispatched; } } } if (plvv->OnCommand) { plvv->OnCommand(hdlg, id, codeNotify); } dispatched:; break; } return 0; } /***************************************************************************** * * LV_OnNotify_OnClick * * Somebody clicked or double-clicked on the listview. * *****************************************************************************/ void PASCAL LV_OnNotify_OnClick(PLVV plvv, HWND hwnd, NMHDR FAR *pnm) { LV_HITTESTINFO hti; Misc_LV_HitTest(hwnd, &hti, (LPARAM)GetMessagePos()); /* * A click/dblclick on the item state icon toggles. * * A click/dblclick anywhere toggles *if* you can't rename. */ if (((hti.flags & LVHT_ONITEMSTATEICON) || ((hti.flags & LVHT_ONITEM) && !(plvv->lvvfl & lvvflCanRename)))) { Misc_LV_SetCurSel(hwnd, hti.iItem); /* LV doesn't do this, oddly */ LV_Toggle(hwnd, hti.iItem); } else if (pnm->code == NM_DBLCLK && plvv->idDblClk) { LV_OnCommand(plvv, GetParent(hwnd), plvv->idDblClk, 0); } else if (pnm->code == NM_DBLCLK && (hti.flags & LVHT_ONITEMLABEL)) { if (plvv->lvvfl & lvvflCanRename) { LV_Rename(hwnd, hti.iItem); } } } /***************************************************************************** * * LV_OnNotify_OnKeyDown * * Somebody pressed a key while focus is on a listview. * * F2 = Rename * Space = Toggle * Del = Delete * *****************************************************************************/ void PASCAL LV_OnNotify_OnKeyDown(PLVV plvv, HWND hwnd, LV_KEYDOWN FAR *lvkd) { int iItem = Misc_LV_GetCurSel(hwnd); if (iItem != -1) { switch (lvkd->wVKey) { case VK_SPACE: /* * But not if the ALT key is down! */ if (GetKeyState(VK_MENU) >= 0) { LV_Toggle(hwnd, iItem); } break; case VK_F2: if (plvv->lvvfl & lvvflCanRename) { LV_Rename(hwnd, iItem); } break; case VK_DELETE: LV_OnCommand(plvv, GetParent(hwnd), IDC_LVDELETE, 0); break; break; } } } /***************************************************************************** * * LV_OnNotify_OnBeginLabelEdit * * Allow it to go through if label editing is permitted. * *****************************************************************************/ BOOL INLINE LV_OnNotify_OnBeginLabelEdit(PLVV plvv, HWND hdlg, HWND hwnd) { if (plvv->lvvfl & lvvflCanRename) { return 0; } else { SetWindowLongPtr(hdlg, DWLP_MSGRESULT, TRUE); return 1; } } /***************************************************************************** * * LV_OnNotify_OnEndLabelEdit * * Trim leading and trailing whitespace. If there's anything left, * then we'll accept it. * *****************************************************************************/ void PASCAL LV_OnNotify_OnEndLabelEdit(PLVV plvv, HWND hwnd, LV_DISPINFO FAR *lpdi) { if (lpdi->item.iItem != -1 && lpdi->item.pszText) { LV_ITEM lvi; lvi.pszText = Misc_Trim(lpdi->item.pszText); if (lvi.pszText[0]) { Misc_LV_GetItemInfo(hwnd, &lvi, lpdi->item.iItem, LVIF_PARAM); lvi.mask ^= LVIF_TEXT ^ LVIF_PARAM; ListView_SetItem(hwnd, &lvi); Common_SetDirty(GetParent(hwnd)); plvv->Dirtify(lvi.lParam); } } } /***************************************************************************** * * LV_OnNotify_OnItemChanged * * If we are being told about a new selection, call the callback. * *****************************************************************************/ void PASCAL LV_OnNotify_OnItemChanged(PLVV plvv, HWND hwnd, NM_LISTVIEW *pnmlv) { if ((pnmlv->uChanged & LVIF_STATE) && (pnmlv->uNewState & LVIS_SELECTED)) { if (plvv->OnSelChange) { plvv->OnSelChange(hwnd, pnmlv->iItem); } } } /***************************************************************************** * * LV_OnNotify * * Ooh, we got a notification. See if it's something we recognize. * * NOTE! We don't support private notifications. * *****************************************************************************/ BOOL PASCAL LV_OnNotify(PLVV plvv, HWND hdlg, NMHDR FAR *pnm) { switch (pnm->idFrom) { case 0: /* Property sheet */ switch (pnm->code) { case PSN_APPLY: plvv->OnApply(hdlg); break; } break; case IDC_LISTVIEW: /* List view */ { HWND hwnd = GetDlgItem(hdlg, IDC_LISTVIEW); switch (pnm->code) { case NM_CLICK: case NM_DBLCLK: LV_OnNotify_OnClick(plvv, hwnd, pnm); break; case LVN_KEYDOWN: LV_OnNotify_OnKeyDown(plvv, hwnd, (LV_KEYDOWN *)pnm); break; case LVN_BEGINLABELEDIT: return LV_OnNotify_OnBeginLabelEdit(plvv, hdlg, hwnd); case LVN_ENDLABELEDIT: LV_OnNotify_OnEndLabelEdit(plvv, hwnd, (LV_DISPINFO *)pnm); break; case LVN_ITEMCHANGED: LV_OnNotify_OnItemChanged(plvv, hwnd, (NM_LISTVIEW *)pnm); break; } } break; } return 0; } /***************************************************************************** * * LV_OnSettingChange * *****************************************************************************/ void PASCAL LV_OnSettingChange(PLVV plvv, HWND hdlg, WPARAM wp, LPARAM lp) { HWND hwnd = GetDlgItem(hdlg, IDC_LISTVIEW); SendMessage(hwnd, WM_SETTINGCHANGE, wp, lp); if (wp == SPI_SETNONCLIENTMETRICS) { /* If we have icons, then go rebuild them. */ if (plvv->GetIcon) { int iItem; int cItem = ListView_GetItemCount(hwnd); for (iItem = 0; iItem < cItem; iItem++) { LV_ITEM lvi; Misc_LV_GetItemInfo(hwnd, &lvi, iItem, LVIF_PARAM); lvi.iImage = plvv->GetIcon(lvi.lParam); lvi.mask |= LVIF_IMAGE; ListView_SetItem(hwnd, &lvi); } } /* In case the scrollbars changed size, resize to accomodate */ LV_ResizeReportColumn(hwnd); /* * HACK AROUND BUG IN COMCTL32.DLL - Explicitly hide and show * the window. This tickles report view into recalculating * its scrollbars. */ ShowWindow(hwnd, SW_HIDE); ShowWindow(hwnd, SW_SHOW); } /* * Note: Do not need to handle WM_SETICONTITLELOGFONT because we * are in a dialog and therefore will use the dialog font, not the * icon title LOGFONT. */ } /***************************************************************************** * * The common listview window procedure. * *****************************************************************************/ BOOL EXPORT LV_DlgProc(PLVV plvv, HWND hdlg, UINT wm, WPARAM wParam, LPARAM lParam) { switch (wm) { case WM_INITDIALOG: return LV_OnInitDialog(plvv, hdlg); case WM_COMMAND: return LV_OnCommand(plvv, hdlg, (int)GET_WM_COMMAND_ID(wParam, lParam), (UINT)GET_WM_COMMAND_CMD(wParam, lParam)); case WM_HELP: Common_OnHelp(lParam, plvv->pdwHelp); break; case WM_NOTIFY: return LV_OnNotify(plvv, hdlg, (NMHDR FAR *)lParam); case WM_SYSCOLORCHANGE: FORWARD_WM_SYSCOLORCHANGE(GetDlgItem(hdlg, IDC_LISTVIEW), SendMessage); break; case WM_DESTROY: if (plvv->OnDestroy) plvv->OnDestroy(hdlg); break; case WM_CONTEXTMENU: LV_OnContextMenu(plvv, hdlg, (HWND)wParam, lParam); break; case WM_SETTINGCHANGE: LV_OnSettingChange(plvv, hdlg, wParam, lParam); break; default: return 0; /* Unhandled */ } return 1; /* Handled */ }