Mercurial > foo_out_sdl
diff foosdk/sdk/libPPUI/CListControl-Subst.cpp @ 1:20d02a178406 default tip
*: check in everything else
yay
| author | Paper <paper@tflc.us> |
|---|---|
| date | Mon, 05 Jan 2026 02:15:46 -0500 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/foosdk/sdk/libPPUI/CListControl-Subst.cpp Mon Jan 05 02:15:46 2026 -0500 @@ -0,0 +1,1503 @@ +#include "stdafx.h" +#include "CListControlComplete.h" +#include "CListControl-Subst.h" +#include "CWindowCreateAndDelete.h" +#include <pfc/string-conv-lite.h> +#include "ImplementOnFinalMessage.h" +#include "CListControl-Cells.h" +#include "windowLifetime.h" + +#define I_IMAGEREALLYNONE (-3) + +namespace { + + static int safeFillText(wchar_t* psz, int cch, pfc::wstringLite const& text) { + int len = (int)text.length(); + int lim = cch - 1; + if (len > lim) len = lim; + for (int i = 0; i < len; ++i) psz[i] = text[i]; + psz[len] = 0; + return len; + } + + class CListControl_ListViewBase : public CListControlComplete { + protected: + const DWORD m_style; + DWORD m_listViewExStyle = 0; + CImageList m_imageLists[4]; + bool m_groupViewEnabled = false; + public: + CListControl_ListViewBase(DWORD style) : m_style(style) { + if (style & LVS_SINGLESEL) this->SetSelectionModeSingle(); + else this->SetSelectionModeMulti(); + } + + BEGIN_MSG_MAP_EX(CListControl_ListViewBase) + MSG_WM_CREATE(OnCreate) + MESSAGE_HANDLER_EX(LVM_INSERTCOLUMN, OnInsertColumn) + MESSAGE_HANDLER_EX(LVM_DELETECOLUMN, OnDeleteColumn) + MESSAGE_HANDLER_EX(LVM_SETCOLUMN, OnSetColumn) + MESSAGE_HANDLER_EX(LVM_SETCOLUMNWIDTH, OnSetColumnWidth) + MESSAGE_HANDLER_EX(LVM_GETCOLUMNWIDTH, OnGetColumnWidth) + MESSAGE_HANDLER_EX(LVM_GETCOLUMNORDERARRAY, OnGetColumnOrderArray) + MESSAGE_HANDLER_EX(LVM_SETCOLUMNORDERARRAY, OnSetColumnOrderArray) + MESSAGE_HANDLER_EX(LVM_GETITEMCOUNT, OnGetItemCount) + MESSAGE_HANDLER_EX(LVM_GETITEMRECT, OnGetItemRect) + MESSAGE_HANDLER_EX(LVM_GETSUBITEMRECT, OnGetSubItemRect) + MESSAGE_HANDLER_EX(LVM_HITTEST, OnHitTest) + MESSAGE_HANDLER_EX(LVM_GETITEMSTATE, OnGetItemState) + MESSAGE_HANDLER_EX(LVM_SETITEMSTATE, OnSetItemState) + MESSAGE_HANDLER_EX(LVM_GETNEXTITEM, OnGetNextItem) + MESSAGE_HANDLER_EX(LVM_GETNEXTITEMINDEX, OnGetNextItemIndex) + MESSAGE_HANDLER_EX(LVM_SUBITEMHITTEST, OnSubItemHitTest) + MESSAGE_HANDLER_EX(LVM_GETSELECTEDCOUNT, OnGetSelCount) + MESSAGE_HANDLER_EX(LVM_GETHEADER, OnGetHeader) + MESSAGE_HANDLER_EX(LVM_SETEXTENDEDLISTVIEWSTYLE, OnSetExtendedListViewStyle) + MESSAGE_HANDLER_EX(LVM_GETEXTENDEDLISTVIEWSTYLE, OnGetExtendedListViewStyle) + MESSAGE_HANDLER_EX(LVM_ENSUREVISIBLE, OnEnsureVisible) + MESSAGE_HANDLER_EX(LVM_SETIMAGELIST, OnSetImageList) + MESSAGE_HANDLER_EX(LVM_GETIMAGELIST, OnGetImageList) + MESSAGE_HANDLER_EX(LVM_EDITLABEL, OnEditLabel) + MESSAGE_HANDLER_EX(LVM_GETSTRINGWIDTH, OnGetStringWidth) + MESSAGE_HANDLER_EX(LVM_ENABLEGROUPVIEW, OnEnableGroupView) + MESSAGE_HANDLER_EX(LVM_SCROLL, OnScroll) + MESSAGE_HANDLER_EX(LVM_REDRAWITEMS, OnRedrawItems) + MSG_WM_KEYDOWN(OnKeyDown) + MSG_WM_SYSKEYDOWN(OnKeyDown) + CHAIN_MSG_MAP(CListControlComplete) + END_MSG_MAP() + + void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { + NMLVKEYDOWN arg = {}; + arg.hdr = this->setupHdr(LVN_KEYDOWN); + arg.wVKey = nChar; + sendNotify(&arg); + SetMsgHandled(FALSE); + } + LRESULT OnCreate(LPCREATESTRUCT) { + SetMsgHandled(FALSE); + // Adopt style flags of the original control to keep various ATL checks happy + DWORD style = GetStyle(); + style = (style & 0xFFFF0000) | (m_style & 0xFFFF); + SetWindowLong(GWL_STYLE, style); + return 0; + } + + LRESULT OnGetColumnWidth(UINT, WPARAM wp, LPARAM) { + size_t idx = (size_t)wp; + return GetSubItemWidth(idx); + } + LRESULT OnSetColumnWidth(UINT, WPARAM wp, LPARAM lp) { + size_t idx = (size_t)wp; + if (idx >= GetColumnCount()) return FALSE; + // undocumented: low 16 bits must be used, or else testing against LVSCW_AUTOSIZE fails hard + // all macros sending LVM_SETCOLUMNWIDTH use low 16 bits of LPARAM + int w = (short)lp; + if (this->IsHeaderless()) { + if (w < 0) { + m_headerlessColumns[idx] = UINT32_MAX; + } else { + m_headerlessColumns[idx] = (uint32_t)w; + this->OnColumnsChanged(); + } + } else { + uint32_t pass = (uint32_t)w; + if (w < 0) { + if (w == LVSCW_AUTOSIZE_USEHEADER) pass = columnWidthAuto; + else pass = columnWidthAutoUseContent; + } + this->ResizeColumn(idx, pass); + } + return TRUE; + } + LRESULT OnGetColumnOrderArray(UINT, WPARAM wp, LPARAM lp) { + int* out = (int*)lp; + auto arr = this->GetColumnOrderArray(); + if ((size_t)wp != arr.size()) return 0; + for (size_t w = 0; w < arr.size(); ++w) out[w] = arr[w]; + return 1; + } + LRESULT OnSetColumnOrderArray(UINT, WPARAM wp, LPARAM lp) { + if (this->IsHeaderless()) { + PFC_ASSERT(!"Implement and test me"); + return 0; + } else { + auto hdr = GetHeaderCtrl(); + WIN32_OP_D(hdr.SetOrderArray((int)wp, (int*)lp)); + } + ReloadData(); + return 1; + } + LRESULT OnInsertColumn(UINT, WPARAM wp, LPARAM lp) { + size_t idx = (size_t)wp; + auto col = reinterpret_cast<LVCOLUMN*>(lp); + + if (IsHeaderless()) { + if (idx > m_headerlessColumns.size()) idx = m_headerlessColumns.size(); + uint32_t width = 0; + if (col->mask & LVCF_WIDTH) width = col->cx; + m_headerlessColumns.insert(m_headerlessColumns.begin() + idx, width); + this->OnColumnsChanged(); + return 0; + } + if (this->GetHeaderCtrl() == NULL) { + if (m_style & LVS_NOSORTHEADER) { + this->InitializeHeaderCtrl(); + } else { + this->InitializeHeaderCtrlSortable(); + } + } + + PFC_ASSERT(this->GetHeaderCtrl().GetItemCount() == (int)this->GetColumnCount()); + + if (idx != this->GetColumnCount()) { + PFC_ASSERT(!"Arbitrary column insert not implemented, please add columns in order"); + return -1; + } + + pfc::string8 label; int width = 0; DWORD format = HDF_LEFT; + if (col->mask & LVCF_TEXT) label = pfc::utf8FromWide(col->pszText); + if (col->mask & LVCF_WIDTH) width = col->cx; + if (col->mask & LVCF_FMT) { + format = col->fmt & LVCFMT_JUSTIFYMASK; + } + this->AddColumn(label, width, format); + + PFC_ASSERT(this->GetHeaderCtrl().GetItemCount() == (int)this->GetColumnCount()); + + return idx; + } + LRESULT OnDeleteColumn(UINT, WPARAM wp, LPARAM) { + size_t idx = (size_t)wp; + if (idx >= this->GetColumnCount()) { + PFC_ASSERT(!"???"); return FALSE; + } + this->DeleteColumn(idx); + return TRUE; + } + LRESULT OnSetColumn(UINT, WPARAM wp, LPARAM lp) { + size_t idx = (size_t)wp; + if (idx >= this->GetColumnCount()) { + PFC_ASSERT(!"???"); return FALSE; + } + auto col = reinterpret_cast<LVCOLUMN*>(lp); + + if (col->mask & (LVCF_TEXT | LVCF_FMT)) { + const char* pText = nullptr; DWORD format = UINT32_MAX; + pfc::string8 strText; + if (col->mask & LVCF_TEXT) { + strText = pfc::utf8FromWide(col->pszText); + pText = strText.c_str(); + } + if (col->mask & LVCF_FMT) { + format = col->fmt & LVCFMT_JUSTIFYMASK; + } + this->SetColumn(idx, pText, format); + } + + if (col->mask & LVCF_WIDTH) { + this->ResizeColumn(idx, col->cx); + } + + return TRUE; + } + LRESULT OnGetItemCount(UINT, WPARAM, LPARAM) { + return (LRESULT)this->GetItemCount(); + } + + LRESULT OnGetItemRect(UINT, WPARAM wp, LPARAM lp) { + size_t idx = (size_t)wp; + RECT* rc = (RECT*)lp; + if (idx < GetItemCount()) { + // LVIR_* not supported + * rc = GetItemRect(idx); + return TRUE; + } + return FALSE; + } + + LRESULT OnGetSubItemRect(UINT, WPARAM wp, LPARAM lp) { + size_t idx = (size_t)wp; + RECT* rc = (RECT*)lp; + if (idx < GetItemCount()) { + size_t subItem = (size_t)rc->top; + // LVIR_* not supported + *rc = GetSubItemRect(idx, subItem); + return TRUE; + } + return FALSE; + } + + LRESULT OnHitTest(UINT, WPARAM, LPARAM lp) { + auto info = (LVHITTESTINFO*)lp; + CPoint pt = this->PointClientToAbs(info->pt); + size_t item = this->ItemFromPointAbs(pt); + size_t subItem = SIZE_MAX; + if (item != SIZE_MAX) { + info->iItem = (int)item; + size_t subItem = this->SubItemFromPointAbs(pt); + info->iSubItem = (subItem != SIZE_MAX) ? (int)subItem : -1; + } + return item != SIZE_MAX ? (LRESULT)item : (LRESULT)-1; + } + LRESULT OnSubItemHitTest(UINT, WPARAM, LPARAM lp) { + auto info = (LVHITTESTINFO*)lp; + CPoint pt = this->PointClientToAbs(info->pt); + size_t item = this->ItemFromPointAbs(pt); + size_t subItem = SIZE_MAX; + if (item != SIZE_MAX) { + info->iItem = (int)item; + subItem = this->SubItemFromPointAbs(pt); + info->iSubItem = (subItem != SIZE_MAX) ? (int)subItem : -1; + } + return subItem != SIZE_MAX ? (LRESULT)subItem : (LRESULT)-1; + } + + virtual void SetItemState(size_t idx, DWORD mask, DWORD state) { + if (idx >= GetItemCount()) return; + if (mask & LVIS_FOCUSED) { + if (state & LVIS_FOCUSED) { + this->SetFocusItem(idx); + } else { + if (this->GetFocusItem() == idx) { + this->SetFocusItem(SIZE_MAX); + } + } + } + if (mask & LVIS_SELECTED) { + if (this->IsSingleSelect()) { + if (state & LVIS_SELECTED) this->SelectSingle(idx); + } else { + this->SetSelectionAt(idx, (state & LVIS_SELECTED) != 0); + } + } + } + LRESULT OnSetItemState(UINT, WPARAM wp, LPARAM lp) { + LVITEM* pItem = (LVITEM*)lp; + if ((LONG_PTR)wp < 0) { + if (pItem->stateMask & LVIS_FOCUSED) { + if (pItem->state & LVIS_FOCUSED) { + // makes no sense + } else { + this->SetFocusItem(SIZE_MAX); + } + } + if (pItem->stateMask & LVIS_SELECTED) { + if (pItem->state & LVIS_SELECTED) { + this->SelectAll(); + } else { + this->SelectNone(); + } + } + } else { + size_t idx = (size_t)wp; + SetItemState(idx, pItem->stateMask, pItem->state); + } + return TRUE; + } + + virtual UINT GetItemState(size_t idx) const { + UINT ret = 0; + if (idx == this->GetFocusItem()) ret |= LVIS_FOCUSED; + if (this->IsItemSelected(idx)) ret |= LVIS_SELECTED; + return ret; + } + LRESULT OnGetItemState(UINT, WPARAM wp, LPARAM lp) { + LRESULT ret = 0; + size_t idx = (size_t)wp; + if (idx < GetItemCount()) { + ret |= (this->GetItemState(idx) & lp); + } + return ret; + } + + LRESULT OnGetNextItemIndex(UINT, WPARAM wp, LPARAM lp) { + LVITEMINDEX* info = (LVITEMINDEX*)wp; + int item = (int)OnGetNextItem(LVM_GETNEXTITEM, info->iItem, lp); + info->iItem = item; + return item >= 0; + } + LRESULT OnGetNextItem(UINT, WPARAM wp, LPARAM lp) { + // GetSelectedIndex() : + // return (int)::SendMessage(this->m_hWnd, LVM_GETNEXTITEM, (WPARAM)-1, MAKELPARAM(LVNI_ALL | LVNI_SELECTED, 0)); + + const bool bVisible = (lp & LVNI_VISIBLEONLY) != 0; + std::pair<size_t, size_t> range; + if ( bVisible ) range = this->GetVisibleRange(); + + if ((lp & LVNI_STATEMASK) == LVNI_FOCUSED) { + size_t ret = this->GetFocusItem(); + if (bVisible) { + if (ret < range.first || ret >= range.second) ret = SIZE_MAX; + } + return (LRESULT)ret; + } + + const auto count = this->GetItemCount(); + + if (wp == (WPARAM)-1 && bVisible) { + // Simplified visible range search + for (size_t idx = range.first; idx < range.second; ++idx) { + if (this->GetItemState(idx) & lp) { + return (LRESULT)idx; + } + } + return -1; + } + + size_t base; + if ((LONG_PTR)wp < 0) base = 0; + else base = wp + 1; + for (size_t idx = base; idx < count; ++idx) { + if (bVisible) { + if (idx < range.first || idx >= range.second) continue; + } + if (this->GetItemState(idx) & lp) { + return (LRESULT)idx; + } + } + return -1; + } + LRESULT OnGetSelCount(UINT, WPARAM, LPARAM) { + return (LRESULT) this->GetSelectedCount(); + } + LRESULT OnGetHeader(UINT, WPARAM, LPARAM) { + return (LRESULT)GetHeaderCtrl().m_hWnd; + } + LRESULT OnSetExtendedListViewStyle(UINT, WPARAM wp, LPARAM lp) { + DWORD ret = m_listViewExStyle; + DWORD set = (DWORD)lp, mask = (DWORD)wp; + if (mask == 0) mask = 0xFFFFFFFF; + m_listViewExStyle = (m_listViewExStyle & ~mask) | (set & mask); + + this->SetRowStyle((m_listViewExStyle & LVS_EX_GRIDLINES) ? rowStyleGrid : rowStyleDefault); + + if (m_listViewExStyle != ret) ReloadData(); + return ret; + } + LRESULT OnGetExtendedListViewStyle(UINT, WPARAM, LPARAM) { + return m_listViewExStyle; + } + LRESULT OnEnsureVisible(UINT, WPARAM wp, LPARAM lp) { + size_t idx = (size_t)wp; + if (idx < this->GetItemCount()) { + (void)lp; // FIX ME partialOK? + this->EnsureItemVisible(idx); + return TRUE; + } + return FALSE; + } + LRESULT OnSetImageList(UINT, WPARAM wp, LPARAM lp) { + size_t idx = (size_t)wp; + LRESULT ret = 0; + if (idx < std::size(m_imageLists)) { + auto& ref = m_imageLists[idx]; + ret = (LRESULT)ref.m_hImageList; + ref = (HIMAGELIST)lp; + } + return ret; + } + LRESULT OnGetImageList(UINT, WPARAM wp, LPARAM lp) { + size_t idx = (size_t)wp; + LRESULT ret = 0; + if (idx < std::size(m_imageLists)) { + ret = (LRESULT)m_imageLists[idx].m_hImageList; + } + return ret; + } + LRESULT OnEditLabel(UINT, WPARAM wp, LPARAM) { + int index = (int)wp; + HWND ret = NULL; + if (index < 0) { + this->TableEdit_Abort(false); + } else { + ret = this->TableEdit_Start((size_t)index, 0); + } + return (LRESULT)ret; + } + LRESULT OnGetStringWidth(UINT, WPARAM, LPARAM lp) { + auto str = (const wchar_t*)lp; + return this->GetOptimalColumnWidthFixed(pfc::utf8FromWide(str), false /* no pad */); + } + LRESULT OnEnableGroupView(UINT, WPARAM wp, LPARAM) { + bool bEnable = (wp != 0); + if (bEnable == m_groupViewEnabled) return 0; + m_groupViewEnabled = bEnable; + ReloadData(); + return 1; + } + LRESULT OnScroll(UINT, WPARAM, LPARAM) { + PFC_ASSERT(!"Implement me"); + return 0; + } + LRESULT OnRedrawItems(UINT, WPARAM wp, LPARAM lp) { + size_t base = wp; + size_t last = lp; + if (last < base) return FALSE; + size_t count = last + 1 - base; + this->UpdateItems(pfc::bit_array_range(base, count)); + return TRUE; + } + bool TableEdit_CanAdvanceHere(size_t item, size_t subItem, uint32_t whatHappened) const override { + // Block line cycling with enter key + auto code = (whatHappened & InPlaceEdit::KEditMaskReason); + if (code == InPlaceEdit::KEditEnter) return false; + return __super::TableEdit_CanAdvanceHere(item, subItem, whatHappened); + } + bool TableEdit_IsColumnEditable(t_size subItem) const override { + return subItem == 0; + } + NMHDR setupHdr(UINT code) const { + return { m_hWnd, (UINT_PTR)this->GetDlgCtrlID(), code }; + } + LRESULT sendNotify(void* data) const { + auto hdr = reinterpret_cast<const NMHDR*>(data); + PFC_ASSERT(hdr->idFrom == (UINT_PTR) GetDlgCtrlID()); + return GetParent().SendMessage(WM_NOTIFY, hdr->idFrom, reinterpret_cast<LPARAM>(data)); + } + int getDispInfoImage(size_t item, size_t subItem) const { + NMLVDISPINFO info = { setupHdr(LVN_GETDISPINFO) }; + info.item.mask = LVIF_IMAGE; + info.item.iItem = (int)item; + info.item.iSubItem = (int)subItem; + sendNotify(&info); + PFC_ASSERT(info.item.iImage != I_IMAGECALLBACK); + return info.item.iImage; + } + pfc::string8 getDispInfoText(size_t item, size_t subItem) const { + NMLVDISPINFO info = { setupHdr(LVN_GETDISPINFO) }; + info.item.mask = LVIF_TEXT; + info.item.iItem = (int)item; + info.item.iSubItem = (int)subItem; + + wchar_t buffer[1024] = {}; + info.item.pszText = buffer; + info.item.cchTextMax = (int) std::size(buffer); + sendNotify(&info); + if (info.item.pszText == LPSTR_TEXTCALLBACK || info.item.pszText == nullptr) { + PFC_ASSERT(!"WTF??"); return ""; + } + return pfc::utf8FromWide(info.item.pszText); + } + virtual LPARAM GetItemParam(size_t) { return 0; } + void ExecuteDefaultAction(size_t idx) override { + NMITEMACTIVATE info = { setupHdr(NM_DBLCLK) }; + info.iItem = (int) idx; + info.lParam = GetItemParam(idx); + sendNotify(&info); + } + void OnSubItemClicked(t_size item, t_size subItem, CPoint pt) override { + __super::OnSubItemClicked(item, subItem, pt); + NMITEMACTIVATE info = { setupHdr(NM_CLICK) }; + info.iItem = (int)item; + info.iSubItem = (int)subItem; + info.ptAction = pt; + sendNotify(&info); + } + void OnFocusChanged(size_t oldFocus, size_t newFocus) override { + __super::OnFocusChanged(oldFocus, newFocus); + const auto count = this->GetItemCount(); + + if (oldFocus < count) { + auto idx = oldFocus; + NMLISTVIEW info = { setupHdr(LVN_ITEMCHANGED) }; + info.iItem = (int)idx; + info.lParam = this->GetItemParam(idx); + auto base = this->GetItemState(idx); + info.uOldState = base | LVIS_FOCUSED; + info.uNewState = base; + info.uChanged = LVIF_STATE; + this->sendNotify(&info); + } + if (newFocus < count) { + auto idx = newFocus; + NMLISTVIEW info = { setupHdr(LVN_ITEMCHANGED) }; + info.iItem = (int)idx; + info.lParam = this->GetItemParam(idx); + auto base = this->GetItemState(idx); + info.uOldState = base & ~LVIS_FOCUSED; + info.uNewState = base; + info.uChanged = LVIF_STATE; + this->sendNotify(&info); + } + } + + void OnSelectionChanged(pfc::bit_array const& affected, pfc::bit_array const& status) override { + + __super::OnSelectionChanged(affected, status); + const auto count = this->GetItemCount(); + + const size_t focus = this->GetFocusItem(); + + // LVN_ITEMCHANGING not supported, CListControl currently doesn't hand this info + + affected.for_each(true, 0, count, [&](size_t idx) { + bool sel_new = status[idx]; + bool sel_old = !sel_new; + NMLISTVIEW info = { setupHdr(LVN_ITEMCHANGED) }; + info.iItem = (int)idx; + info.lParam = this->GetItemParam(idx); + info.uOldState = (sel_old ? LVIS_SELECTED : 0) | (idx == focus ? LVIS_FOCUSED : 0); + info.uNewState = (sel_new ? LVIS_SELECTED : 0) | (idx == focus ? LVIS_FOCUSED : 0); + info.uChanged = LVIF_STATE; + this->sendNotify(&info); + }); + } + void RequestReorder(size_t const* order, size_t count) override {} + void RequestRemoveSelection() override {} + + void OnColumnHeaderClick(t_size index) override { + NMLISTVIEW info = { setupHdr(LVN_COLUMNCLICK) }; + info.iItem = -1; + info.iSubItem = (int)index; + this->sendNotify(&info); + } + + bool IsHeaderless() const { + return (m_style & LVS_NOCOLUMNHEADER) != 0; + } + + std::vector< uint32_t > m_headerlessColumns; + + size_t GetColumnCount() const override { + if (IsHeaderless()) { + return m_headerlessColumns.size(); + } + return __super::GetColumnCount(); + } + uint32_t GetSubItemWidth(size_t subItem) const override { + if (IsHeaderless()) { + if (subItem < m_headerlessColumns.size()) { + auto v = m_headerlessColumns[subItem]; + if (v == UINT32_MAX) { + uint32_t nAuto = 0, wNormal = 0; + for (auto walk : m_headerlessColumns) { + if (walk == UINT32_MAX) ++nAuto; + else wNormal += walk; + } + PFC_ASSERT(nAuto > 0); + uint32_t wTotal = this->GetClientRectHook().Width(); + if (wTotal > wNormal) { + uint32_t autoTotal = wTotal - wNormal; + v = autoTotal / nAuto; + } else { + v = 0; + } + } + return v; + } + else return 0; + } + return __super::GetSubItemWidth(subItem); + } + + bool GetCellTypeSupported() const override { + return useCheckBoxes(); + } + + cellType_t GetCellType(size_t item, size_t subItem) const override { + if (useCheckBoxes() && subItem == 0) { + return &PFC_SINGLETON(CListCell_Checkbox); + } + return __super::GetCellType(item, subItem); + } + + bool useCheckBoxes() const { + return (m_listViewExStyle & LVS_EX_CHECKBOXES) != 0; + } + + void TableEdit_SetField(t_size item, t_size subItem, const char* value) override { + if (subItem != 0) { + PFC_ASSERT(!"subItem should be zero"); + return; + } + auto textW = pfc::wideFromUTF8(value); + NMLVDISPINFO info = { setupHdr(LVN_ENDLABELEDIT) }; + info.item.iItem = (int)item; + info.item.mask = LVIF_TEXT; + info.item.pszText = const_cast<wchar_t*>( textW.c_str() ); + + if (sendNotify(&info) != 0) { + SetSubItemText(item, subItem, value); + } + } + + virtual void SetSubItemText(size_t item, size_t subItem, const char* text) {} + + virtual int GetItemImage(size_t item, size_t subItem) const { + return I_IMAGEREALLYNONE; + } + CImageList GetImageList() const { + return m_imageLists[LVSIL_SMALL]; + } + bool RenderCellImageTest(size_t item, size_t subItem) const override { + return GetImageList() != NULL && GetItemImage(item, subItem) != I_IMAGEREALLYNONE; + } + void RenderCellImage(size_t item, size_t subItem, CDCHandle dc, const CRect& rc) const override { + auto img = GetItemImage(item, subItem); + auto imgList = GetImageList(); + if (img >= 0 && imgList) { + CSize size; + WIN32_OP_D(imgList.GetIconSize(size)); + CRect rc2 = rc; + if (size.cx <= rc.Width() && size.cy <= rc.Height()) { + auto cp = rc.CenterPoint(); + rc2.left = cp.x - size.cx / 2; + rc2.top = cp.y - size.cy / 2; + rc2.right = rc2.left + size.cx; + rc2.bottom = rc2.top + size.cy; + } + imgList.DrawEx(img, dc, rc2, CLR_NONE, CLR_NONE, ILD_SCALE); + } + } + + bool m_notifyItemDraw = false; + + UINT GetItemCDState(size_t which) const { + UINT ret = 0; + DWORD state = GetItemState(which); + if (state & LVIS_FOCUSED) ret |= CDIS_FOCUS; + if (state & LVIS_SELECTED) ret |= CDIS_SELECTED; + return ret; + } + + void RenderRect(const CRect& p_rect, CDCHandle p_dc) override { + NMCUSTOMDRAW cd = { setupHdr(NM_CUSTOMDRAW) }; + cd.dwDrawStage = CDDS_PREPAINT; + cd.hdc = p_dc; + cd.rc = p_rect; + cd.dwItemSpec = UINT32_MAX; + cd.uItemState = 0; + cd.lItemlParam = 0; + LRESULT status = sendNotify(&cd); + m_notifyItemDraw = (status & CDRF_NOTIFYITEMDRAW) != 0; + + if ((status & CDRF_SKIPDEFAULT) != 0) { + return; + } + __super::RenderRect(p_rect, p_dc); + + cd.dwDrawStage = CDDS_POSTPAINT; + sendNotify(&cd); + } + void RenderItem(t_size item, const CRect& itemRect, const CRect& updateRect, CDCHandle dc) override { + NMCUSTOMDRAW cd = {}; + if (m_notifyItemDraw) { + cd = { setupHdr(NM_CUSTOMDRAW) }; + cd.dwDrawStage = CDDS_ITEMPREPAINT; + cd.hdc = dc; + cd.rc = itemRect; + cd.dwItemSpec = (DWORD)item; + cd.uItemState = GetItemCDState(item); + cd.lItemlParam = GetItemParam(item); + LRESULT status = sendNotify(&cd); + if (status & CDRF_SKIPDEFAULT) return; + } + + __super::RenderItem(item, itemRect, updateRect, dc); + + + if (m_notifyItemDraw) { + cd.dwDrawStage = CDDS_ITEMPOSTPAINT; + sendNotify(&cd); + } + } +#if 0 + void RenderSubItemText(t_size item, t_size subItem, const CRect& subItemRect, const CRect& updateRect, CDCHandle dc, bool allowColors) override { + + __super::RenderSubItemText(item, subItem, subItemRect, updateRect, dc, allowColors); + } +#endif + + }; + + class CListControl_ListViewOwnerData : public CListControl_ListViewBase { + public: + CListControl_ListViewOwnerData(DWORD style) : CListControl_ListViewBase(style) {} + + BEGIN_MSG_MAP_EX(CListControl_ListViewOwnerData) + MESSAGE_HANDLER_EX(LVM_SETITEMCOUNT, OnSetItemCount) + CHAIN_MSG_MAP(CListControl_ListViewBase) + END_MSG_MAP() + private: + size_t m_itemCount = 0; + LRESULT OnSetItemCount(UINT, WPARAM wp, LPARAM) { + auto count = (size_t)wp; + if (m_itemCount != count) { + m_itemCount = count; ReloadData(); + } + return 0; + } + + t_size GetItemCount() const override { + return m_itemCount; + } + bool GetSubItemText(t_size item, t_size subItem, pfc::string_base& out) const override { + if (item < m_itemCount) { + out = this->getDispInfoText(item, subItem); + return true; + } + return false; + } + int GetItemImage(size_t item, size_t subItem) const override { + int ret = I_IMAGEREALLYNONE; + if (subItem == 0) { + ret = this->getDispInfoImage(item, subItem); + } + return ret; + } + void SetSubItemText(size_t item, size_t subItem, const char* text) override { + auto textW = pfc::wideFromUTF8(text); + NMLVDISPINFO info = { setupHdr(LVN_SETDISPINFO) }; + info.item.iItem = (int)item; + info.item.iSubItem = (int)subItem; + info.item.mask = LVIF_TEXT; + info.item.pszText = const_cast<wchar_t*>(textW.c_str()); + sendNotify(&info); + ReloadItem(item); + } + }; + + class CListControl_ListView : public CListControl_ListViewBase { + public: + CListControl_ListView(DWORD style) : CListControl_ListViewBase(style) {} + + BEGIN_MSG_MAP_EX(CListControl_ListView) + MESSAGE_HANDLER_EX(LVM_INSERTITEM, OnInsertItem) + MESSAGE_HANDLER_EX(LVM_SETITEM, OnSetItem) + MESSAGE_HANDLER_EX(LVM_SETITEMCOUNT, OnSetItemCount) + MESSAGE_HANDLER_EX(LVM_GETITEM, OnGetItem) + MESSAGE_HANDLER_EX(LVM_GETITEMTEXT, OnGetItemText) + MESSAGE_HANDLER_EX(LVM_INSERTGROUP, OnInsertGroup) + MESSAGE_HANDLER_EX(LVM_REMOVEGROUP, OnRemoveGroup) + MESSAGE_HANDLER_EX(LVM_GETGROUPCOUNT, OnGetGroupCount) + MESSAGE_HANDLER_EX(LVM_GETGROUPINFO, OnGetGroupInfo) + MESSAGE_HANDLER_EX(LVM_SETGROUPINFO, OnSetGroupInfo) + MESSAGE_HANDLER_EX(LVM_GETGROUPINFOBYINDEX, OnGetGroupInfoByIndex) + MESSAGE_HANDLER_EX(LVM_REMOVEALLGROUPS, OnRemoveAllGroups) + MESSAGE_HANDLER_EX(LVM_DELETEITEM, OnDeleteItem) + MESSAGE_HANDLER_EX(LVM_DELETEALLITEMS, OnDeleteAllItems) + CHAIN_MSG_MAP(CListControl_ListViewBase) + END_MSG_MAP() + private: + struct text_t { + pfc::string8 text; + bool callback = false; + }; + struct rec_t { + std::vector< text_t > text; + LPARAM param = 0; + int image = I_IMAGEREALLYNONE; + bool checked = false; + groupID_t groupID = 0; + int LVGroupID = 0; + }; + + std::vector<rec_t> m_content; + + groupID_t m_groupIDGen = 0; + groupID_t groupIDGen() { return ++m_groupIDGen; } + struct group_t { + int LVGroupID = 0; + groupID_t GroupID = 0; + pfc::string8 header; + }; + std::vector<group_t> m_groups; + + groupID_t groupFromLV(int LV) const { + for (auto& g : m_groups) { + if (g.LVGroupID == LV) return g.GroupID; + } + return 0; + } + + t_size GetItemCount() const override { + return m_content.size(); + } + bool GetSubItemText(t_size item, t_size subItem, pfc::string_base& out) const override { + if (item < m_content.size()) { + auto& r = m_content[item]; + if (subItem < r.text.size()) { + auto& t = r.text[subItem]; + if (t.callback) { + out = getDispInfoText(item, subItem); + } else { + out = t.text; + } + return true; + } + } + return false; + } + void SetSubItemText(size_t item, size_t subItem, const char* text) override { + if (item < m_content.size()) { + auto& r = m_content[item]; + if (subItem < r.text.size()) { + auto& t = r.text[subItem]; + t.callback = false; t.text = text; + ReloadItem(item); + } + } + } + + int GetItemImage(size_t item, size_t subItem) const override { + int ret = I_IMAGEREALLYNONE; + if (subItem == 0 && item < m_content.size()) { + auto& r = m_content[item]; + ret = r.image; + if (ret == I_IMAGECALLBACK) ret = this->getDispInfoImage(item, subItem); + } + return ret; + } + LPARAM GetItemParam(size_t item) override { + LPARAM ret = 0; + if (item < m_content.size()) { + ret = m_content[item].param; + } + return ret; + } + + static text_t makeText(const wchar_t* p) { + text_t text; + if (p == LPSTR_TEXTCALLBACK) { + text.callback = true; + } else { + text.text = pfc::utf8FromWide(p); + } + return text; + } + LRESULT OnSetItem(UINT, WPARAM, LPARAM lp) { + auto pItem = reinterpret_cast<LVITEM*>(lp); + size_t item = (size_t)pItem->iItem; + const size_t total = GetItemCount(); + if (item >= total) return FALSE; + size_t subItem = (size_t)pItem->iSubItem; + if (subItem > 1024) return FALSE; // quick sanity + auto& rec = m_content[item]; + size_t width = subItem + 1; + if (rec.text.size() < width) rec.text.resize(width); + bool bReload = false, bReloadAll = false; + if (pItem->mask & LVIF_TEXT) { + rec.text[subItem] = makeText(pItem->pszText); + bReload = true; + } + if (pItem->mask & LVIF_IMAGE) { + rec.image = pItem->iImage; + bReload = true; + } + if (pItem->mask & LVIF_PARAM) { + rec.param = pItem->lParam; + } + if (pItem->mask & LVIF_GROUPID) { + rec.LVGroupID = pItem->iGroupId; + rec.groupID = groupFromLV(rec.LVGroupID); + if (m_groupViewEnabled) bReloadAll = true; + } + if (bReloadAll) { + this->ReloadData(); + } else if (bReload) { + this->ReloadItem(item); + } + if (pItem->mask & LVIF_STATE) { + this->SetItemState(item, pItem->stateMask, pItem->state); + } + return TRUE; + } + LRESULT OnInsertItem(UINT, WPARAM, LPARAM lp) { + auto pItem = reinterpret_cast<LVITEM*>(lp); + + size_t item = (size_t)pItem->iItem; + const size_t total = GetItemCount(); + if (item > total) item = total; + + rec_t r; + if (pItem->mask & LVIF_TEXT) { + r.text = { makeText(pItem->pszText) }; + } + if (pItem->mask & LVIF_GROUPID) { + r.LVGroupID = pItem->iGroupId; + r.groupID = groupFromLV(r.LVGroupID); + } + if (pItem->mask & LVIF_IMAGE) { + r.image = pItem->iImage; + } + if (pItem->mask & LVIF_PARAM) { + r.param = pItem->lParam; + } + + m_content.insert(m_content.begin() + item, std::move(r)); + if (m_groupViewEnabled) ReloadData(); + else this->OnItemsInserted(item, 1, false); + + return (LRESULT)item; + } + LRESULT OnSetItemCount(UINT, WPARAM wp, LPARAM) { + // It's not actually supposed to be called like this, LVM_SETITEMCOUNT is meant for ownerdata listviews + auto count = (size_t)wp; + if (count != m_content.size()) { + m_content.resize(wp); + ReloadData(); + } + return 0; + } + + LRESULT OnInsertGroup(UINT, WPARAM wp, LPARAM lp) { + LVGROUP* arg = (LVGROUP*)lp; + if ((arg->mask & (LVGF_GROUPID | LVGF_HEADER)) != (LVGF_GROUPID | LVGF_HEADER)) return -1; + + auto LVGroupID = arg->iGroupId; + for (auto& existing : m_groups) { + if (existing.LVGroupID == LVGroupID) return -1; + } + + size_t idx = (size_t)wp; + if (idx > m_groups.size()) idx = m_groups.size(); + + group_t grp; + grp.LVGroupID = LVGroupID; + grp.GroupID = this->groupIDGen(); + grp.header = pfc::utf8FromWide(arg->pszHeader); + m_groups.insert(m_groups.begin() + idx, std::move(grp)); + // No reload data - adding of items does it + return (LRESULT)idx; + } + LRESULT OnRemoveGroup(UINT, WPARAM wp, LPARAM) { + int LVGroupID = (int)wp; + for (size_t walk = 0; walk < m_groups.size(); ++walk) { + if (m_groups[walk].LVGroupID == LVGroupID) { + m_groups.erase(m_groups.begin() + walk); + return walk; + } + } + return -1; + } + void getGroupInfo(group_t const& in, LVGROUP* out) { + if (out->mask & LVGF_HEADER) { + auto text = pfc::wideFromUTF8(in.header.c_str()); + safeFillText(out->pszHeader, out->cchHeader, text); + } + if (out->mask & LVGF_GROUPID) { + out->iGroupId = in.LVGroupID; + } + } + void setGroupInfo(group_t& grp, LVGROUP* in) { + bool bReload = false; + if (in->mask & LVGF_HEADER) { + grp.header = pfc::utf8FromWide(in->pszHeader); + bReload = true; + } + if (in->mask & LVGF_GROUPID) { + grp.LVGroupID = in->iGroupId; + bReload = true; + } + + if (bReload) this->ReloadData(); + } + LRESULT OnGetGroupCount(UINT, WPARAM, LPARAM) { + return (LRESULT)m_groups.size(); + } + LRESULT OnGetGroupInfo(UINT, WPARAM wp, LPARAM lp) { + int LVGroupID = (int)wp; + auto out = (LVGROUP*)lp; + for (auto& grp : m_groups) { + if (grp.LVGroupID == LVGroupID) { + getGroupInfo(grp, out); + return LVGroupID; + } + } + return -1; + } + LRESULT OnSetGroupInfo(UINT, WPARAM wp, LPARAM lp) { + int LVGroupID = (int)wp; + auto in = (LVGROUP*)lp; + int ret = -1; + for (auto& grp : m_groups) { + if (grp.LVGroupID == LVGroupID) { + setGroupInfo(grp, in); + ret = grp.LVGroupID; + } + } + return ret; + } + LRESULT OnGetGroupInfoByIndex(UINT, WPARAM wp, LPARAM lp) { + size_t idx = (size_t)wp; + LVGROUP* out = (LVGROUP*)lp; + if (idx < m_groups.size()) { + getGroupInfo(m_groups[idx], out); + } + return FALSE; + } + LRESULT OnRemoveAllGroups(UINT, WPARAM, LPARAM) { + m_groups.clear(); return 0; + } + LRESULT OnDeleteItem(UINT, WPARAM wp, LPARAM) { + size_t idx = (size_t)wp; + LRESULT rv = FALSE; + const size_t oldCount = m_content.size(); + if (idx < oldCount) { + m_content.erase(m_content.begin() + idx); + this->OnItemsRemoved(pfc::bit_array_one(idx), oldCount); + rv = TRUE; + } + return rv; + } + LRESULT OnDeleteAllItems(UINT, WPARAM, LPARAM) { + m_content.clear(); ReloadData(); + return TRUE; + } + int fillText(size_t item, size_t subItem, LVITEM* pItem) const { + pfc::wstringLite text; + if (item < m_content.size()) { + auto& r = m_content[item]; + if (subItem < r.text.size()) { + auto& t = r.text[subItem]; + if (!t.callback) { + text = pfc::wideFromUTF8(t.text); + } + } + } + return safeFillText(pItem->pszText, pItem->cchTextMax, text); + } + LRESULT OnGetItem(UINT, WPARAM, LPARAM lp) { + auto pItem = reinterpret_cast<LVITEM*>(lp); + if (pItem->mask & LVIF_TEXT) { + fillText(pItem->iItem, pItem->iSubItem, pItem); + } + if (pItem->mask & LVIF_IMAGE) { + size_t idx = pItem->iItem; + if (idx < m_content.size()) { + auto& line = m_content[idx]; + pItem->iImage = line.image; + } + } + if (pItem->mask & LVIF_PARAM) { + pItem->lParam = GetItemParam(pItem->iItem); + } + + return TRUE; + } + LRESULT OnGetItemText(UINT, WPARAM wp, LPARAM lp) { + auto item = (size_t)wp; + auto pItem = reinterpret_cast<LVITEM*>(lp); + size_t subItem = (size_t)pItem->iSubItem; + return fillText(item, subItem, pItem); + } + + bool GetCellCheckState(size_t item, size_t sub) const override { + bool rv = false; + if (sub == 0 && item < m_content.size()) { + rv = m_content[item].checked; + } + return rv; + } + void SetCellCheckState(size_t item, size_t sub, bool value) override { + if (sub == 0 && item < m_content.size()) { + auto& rec = m_content[item]; + if (rec.checked != value) { + auto oldState = this->GetItemState(item); + rec.checked = value; + __super::SetCellCheckState(item, sub, value); + + NMLISTVIEW info = { this->setupHdr(LVN_ITEMCHANGED) }; + info.iItem = (int)item; + info.lParam = this->GetItemParam(item); + info.uChanged = LVIF_STATE; + info.uOldState = oldState; + info.uNewState = this->GetItemState(item); + this->sendNotify(&info); + } + } + } + UINT GetItemState(size_t idx) const override { + UINT ret = __super::GetItemState(idx); + if (useCheckBoxes() && idx < m_content.size()) { + int nCheck = m_content[idx].checked ? 2 : 1; + ret |= INDEXTOSTATEIMAGEMASK(nCheck); + } + return ret; + } + void SetItemState(size_t idx, DWORD mask, DWORD state) override { + __super::SetItemState(idx, mask, state); + if (useCheckBoxes() && (mask & LVIS_STATEIMAGEMASK) != 0) { + int nCheck = ((state & LVIS_STATEIMAGEMASK) >> 12); + this->SetCellCheckState(idx, 0, nCheck == 2); + } + } + groupID_t GetItemGroup(t_size p_item) const override { + if (m_groupViewEnabled && p_item < m_content.size()) { + return m_content[p_item].groupID; + } + return 0; + } + bool GetGroupHeaderText2(size_t baseItem, pfc::string_base& out) const override { + auto id = GetItemGroup(baseItem); + if (id != 0) { + for (auto& g : m_groups) { + if (id == g.GroupID) { + out = g.header; + return true; + } + } + } + return false; + } + }; +} + +HWND CListControl_ReplaceListView(HWND wndReplace) { + CListViewCtrl src(wndReplace); + const auto style = src.GetStyle(); + HWND ret = NULL; + if (style & LVS_REPORT) { + const auto ctrlID = src.GetDlgCtrlID(); + CWindow parent = src.GetParent(); + DWORD headerStyle = 0; + if ((style & LVS_NOCOLUMNHEADER) == 0) { + auto header = src.GetHeader(); + if (header) { + headerStyle = header.GetStyle(); + } + } + if (style & LVS_OWNERDATA) { + auto obj = PP::newWindowObj<CListControl_ListViewOwnerData>(style); + ret = obj->CreateInDialog(parent, ctrlID, src); + PFC_ASSERT(ret != NULL); + if (headerStyle != 0 && obj->GetHeaderCtrl() == NULL) { + obj->InitializeHeaderCtrl((headerStyle&(HDS_FULLDRAG | HDS_BUTTONS))); + } + } else { + PFC_ASSERT(src.GetItemCount() == 0); // transferring of items not yet implemented + auto obj = PP::newWindowObj<CListControl_ListView>(style); + ret = obj->CreateInDialog(parent, ctrlID, src); + PFC_ASSERT(ret != NULL); + if (headerStyle != 0 && obj->GetHeaderCtrl() == NULL) { + obj->InitializeHeaderCtrl((headerStyle & (HDS_FULLDRAG | HDS_BUTTONS))); + } + } + } + return ret; +} + +namespace { + // FIX ME WM_DELETEITEM + + class CListControl_ListBoxBase : public CListControlReadOnly { + protected: + const DWORD m_style; + public: + CListControl_ListBoxBase(DWORD style) : m_style(style) { + + // owner data not implemented + PFC_ASSERT((m_style & LBS_NODATA) == 0); + + if (m_style & LBS_NOSEL) { + this->SetSelectionModeNone(); + } else if (m_style & LBS_MULTIPLESEL) { + this->SetSelectionModeMulti(); + } else { + this->SetSelectionModeSingle(); + } + } + + BEGIN_MSG_MAP_EX(CListControl_ListBoxBase) + MSG_WM_SETFOCUS(OnSetFocus) + MSG_WM_KILLFOCUS(OnKillFocus) + MESSAGE_HANDLER_EX(LB_SETCURSEL, OnSetCurSel) + MESSAGE_HANDLER_EX(LB_GETCURSEL, OnGetCurSel) + MESSAGE_HANDLER_EX(LB_GETITEMRECT, OnGetItemRect) + MESSAGE_HANDLER_EX(LB_SELECTSTRING, OnSelectString) + MESSAGE_HANDLER_EX(LB_ITEMFROMPOINT, OnItemFromPoint) + MESSAGE_HANDLER_EX(LB_GETSEL, OnGetSel) + MESSAGE_HANDLER_EX(LB_SETSEL, OnSetSel) + MESSAGE_HANDLER_EX(LB_GETSELCOUNT, OnGetSelCount) + MESSAGE_HANDLER_EX(LB_GETSELITEMS, OnGetSelItems) + MESSAGE_HANDLER_EX(LB_SETCARETINDEX, OnSetCaretIndex) + MESSAGE_HANDLER_EX(LB_GETCARETINDEX, OnGetCaretIndex) + MSG_WM_CREATE(OnCreate) + CHAIN_MSG_MAP(CListControlReadOnly) + END_MSG_MAP() + + LRESULT OnCreate(LPCREATESTRUCT) { + SetMsgHandled(FALSE); + // Adopt style flags of the original control to keep various ATL checks happy + DWORD style = GetStyle(); + style = (style & 0xFFFF0000) | (m_style & 0xFFFF); + SetWindowLong(GWL_STYLE, style); + return 0; + } + void notifyParent(WORD code) { + if (m_style & LBS_NOTIFY) { + GetParent().PostMessage(WM_COMMAND, MAKEWPARAM(GetDlgCtrlID(), code), (LPARAM)m_hWnd); + } + } + LRESULT OnGetCurSel(UINT, WPARAM, LPARAM) { + return (LRESULT)this->GetSingleSel(); + } + LRESULT OnSetCurSel(UINT, WPARAM wp, LPARAM) { + this->SelectSingle((size_t)wp); + return LB_OKAY; + } + LRESULT OnGetItemRect(UINT, WPARAM wp, LPARAM lp) { + size_t idx = (size_t)wp; + if (idx < this->GetItemCount()) { + *reinterpret_cast<RECT*>(lp) = this->GetItemRect(idx); + return LB_OKAY; + } else { + return LB_ERR; + } + } + LRESULT OnSelectString(UINT, WPARAM wp, LPARAM lp) { + auto idx = this->SendMessage(LB_FINDSTRING, wp, lp); + if (idx != LB_ERR) this->SelectSingle((size_t)idx); + return idx; + } + LRESULT OnItemFromPoint(UINT, WPARAM, LPARAM lp) { + CPoint pt(lp); + size_t ret; + if (!this->ItemFromPoint(pt, ret)) return LB_ERR; + return (LRESULT)ret; + } + LRESULT OnGetSel(UINT, WPARAM wp, LPARAM) { + size_t idx = (size_t)wp; + if (idx < this->GetItemCount()) { + return this->IsItemSelected(idx) ? 1 : 0; + } else { + return LB_ERR; + } + + } + LRESULT OnSetSel(UINT, WPARAM wp, LPARAM lp) { + // Contrary to the other methods, index comes in LPARAM + if (lp < 0) { + if (wp) this->SelectAll(); + else this->SelectNone(); + } else { + this->SetSelection(pfc::bit_array_one((size_t)lp), pfc::bit_array_val(wp != 0)); + } + return LB_OKAY; + } + LRESULT OnGetSelItems(UINT, WPARAM wp, LPARAM lp) { + int* out = reinterpret_cast<int*>(lp); + size_t outSize = (size_t)wp; + size_t outWalk = 0; + const size_t total = this->GetItemCount(); + for (size_t walk = 0; walk < total && outWalk < outSize; ++walk) { + if (this->IsItemSelected(walk)) { + out[outWalk++] = (int)walk; + } + } + return outWalk; + } + LRESULT OnGetSelCount(UINT, WPARAM, LPARAM) { + return (LRESULT)this->GetSelectedCount(); + } + LRESULT OnGetCaretIndex(UINT, WPARAM, LPARAM) { + size_t focus = this->GetFocusItem(); + if (focus == SIZE_MAX) return LB_ERR; + return (LRESULT)focus; + } + LRESULT OnSetCaretIndex(UINT, WPARAM wp, LPARAM) { + size_t idx = (size_t)wp; + if (idx < this->GetItemCount()) { + this->SetFocusItem(idx); return LB_OKAY; + } else { + return LB_ERR; + } + } + + void OnSetFocus(HWND) { + notifyParent(LBN_SETFOCUS); + SetMsgHandled(FALSE); + } + void OnKillFocus(HWND) { + notifyParent(LBN_KILLFOCUS); + SetMsgHandled(FALSE); + } + + void OnSelectionChanged(pfc::bit_array const& affected, pfc::bit_array const& status) override { + __super::OnSelectionChanged(affected, status); + notifyParent(LBN_SELCHANGE); + } + void ExecuteDefaultAction(size_t idx) override { + notifyParent(LBN_DBLCLK); + } + + void RequestReorder(size_t const* order, size_t count) override {} + void RequestRemoveSelection() override {} + }; + class CListControl_ListBox : public CListControl_ListBoxBase { + public: + CListControl_ListBox(DWORD style) : CListControl_ListBoxBase(style) {} + + BEGIN_MSG_MAP_EX(CListControl_ListBox) + MESSAGE_HANDLER_EX(LB_INSERTSTRING, OnInsertString) + MESSAGE_HANDLER_EX(LB_ADDSTRING, OnAddString) + MESSAGE_HANDLER_EX(LB_RESETCONTENT, OnResetContent) + MESSAGE_HANDLER_EX(LB_DELETESTRING, OnDeleteString) + MESSAGE_HANDLER_EX(LB_SETITEMDATA, OnSetItemData) + MESSAGE_HANDLER_EX(LB_GETITEMDATA, OnGetItemData) + MESSAGE_HANDLER_EX(LB_GETCOUNT, OnGetCount) + MESSAGE_HANDLER_EX(LB_FINDSTRING, OnFindString) + MESSAGE_HANDLER_EX(LB_FINDSTRINGEXACT, OnFindStringExact) + MESSAGE_HANDLER_EX(LB_SETCOUNT, OnSetCount) + MESSAGE_HANDLER_EX(LB_INITSTORAGE, OnInitStorage) + MESSAGE_HANDLER_EX(LB_GETTEXT, OnGetText) + MESSAGE_HANDLER_EX(LB_GETTEXTLEN, OnGetTextLen) + CHAIN_MSG_MAP(CListControl_ListBoxBase) + END_MSG_MAP() + + struct rec_t { + pfc::string8 text; + LPARAM data = 0; + }; + + std::vector<rec_t> m_content; + t_size GetItemCount() const override { + return m_content.size(); + } + + bool isForceSorted() const { + return (m_style & LBS_SORT) != 0; + } + size_t AddStringSorted(pfc::string8 && str) { + size_t ret = 0; + // FIX ME bsearch?? + // even with bsearch it's still O(n) so it's pointless + while (ret < m_content.size() && pfc::stricmp_ascii(str, m_content[ret].text) > 0) ++ret; + + m_content.insert(m_content.begin() + ret, { std::move(str) }); + + this->OnItemsInserted(ret, 1, false); + return ret; + } + static pfc::string8 importString(LPARAM lp) { + return pfc::utf8FromWide(reinterpret_cast<const wchar_t*>(lp)); + } + LRESULT OnInsertString(UINT, WPARAM wp, LPARAM lp) { + auto str = importString(lp); + if (isForceSorted()) return AddStringSorted(std::move(str)); + size_t at = wp; + if (at > m_content.size()) at = m_content.size(); + m_content.insert(m_content.begin() + at, { std::move(str) }); + this->OnItemsInserted(at, 1, false); + return at; + } + LRESULT OnAddString(UINT, WPARAM wp, LPARAM lp) { + auto str = importString(lp); + if (isForceSorted()) return AddStringSorted(std::move(str)); + size_t ret = m_content.size(); + m_content.push_back({ std::move(str) }); + this->OnItemsInserted(ret, 1, false); + return ret; + } + LRESULT OnResetContent(UINT, WPARAM, LPARAM) { + size_t oldCount = m_content.size(); + if (oldCount > 0) { + m_content.clear(); + this->OnItemsRemoved(pfc::bit_array_true(), oldCount); + } + return LB_OKAY; + } + LRESULT OnDeleteString(UINT, WPARAM wp, LPARAM) { + size_t idx = (size_t) wp; + if (idx < m_content.size()) { + m_content.erase(m_content.begin() + idx); + this->OnItemRemoved(idx); + return m_content.size(); + } else { + return LB_ERR; + } + } + + LRESULT OnSetItemData(UINT, WPARAM wp, LPARAM lp) { + size_t idx = (size_t)wp; + if (idx < m_content.size()) { + m_content[idx].data = (size_t)lp; + return LB_OKAY; + } else { + return LB_ERR; + } + } + LRESULT OnGetItemData(UINT, WPARAM wp, LPARAM lp) { + size_t idx = (size_t)wp; + if (idx < m_content.size()) { + return (LRESULT)m_content[idx].data; + } else { + return LB_ERR; + } + } + LRESULT OnGetCount(UINT, WPARAM, LPARAM) { + return (LRESULT)m_content.size(); + } + LRESULT OnFindString(UINT, WPARAM wp, LPARAM lp) { + auto str = importString(lp); + const auto total = m_content.size(); + size_t first = (size_t)(wp + 1) % total; + for (size_t walk = 0; walk < total; ++walk) { + size_t at = (first + walk) % total; + if (pfc::string_has_prefix_i(m_content[at].text, str)) return (LRESULT)at; + } + return LB_ERR; + } + LRESULT OnFindStringExact(UINT, WPARAM wp, LPARAM lp) { + auto str = importString(lp); + const auto total = m_content.size(); + size_t first = (size_t)(wp + 1) % total; + for (size_t walk = 0; walk < total; ++walk) { + size_t at = (first + walk) % total; + if (pfc::stringEqualsI_utf8(m_content[at].text, str)) return (LRESULT)at; + } + return LB_ERR; + } + + LRESULT OnSetCount(UINT, WPARAM wp, LPARAM) { + m_content.resize((size_t)wp); + ReloadData(); + return LB_OKAY; + } + LRESULT OnInitStorage(UINT, WPARAM wp, LPARAM) { + m_content.reserve(m_content.size() + (size_t)wp); + return LB_OKAY; + } + + LRESULT OnGetText(UINT, WPARAM wp, LPARAM lp) { + size_t idx = (size_t)wp; + if (idx < m_content.size()) { + auto out = reinterpret_cast<wchar_t*>(lp); + wcscpy(out, pfc::wideFromUTF8(m_content[idx].text.c_str()).c_str()); + return LB_OKAY; + } else { + return LB_ERR; + } + } + LRESULT OnGetTextLen(UINT, WPARAM wp, LPARAM) { + size_t idx = (size_t)wp; + if (idx < m_content.size()) { + return pfc::wideFromUTF8(m_content[idx].text.c_str()).length(); + } else { + return LB_ERR; + } + } + + bool GetSubItemText(size_t item, size_t subItem, pfc::string_base& out) const override { + PFC_ASSERT(subItem == 0); (void)subItem; + PFC_ASSERT(item < m_content.size()); + out = m_content[item].text; + return true; + } + }; +} + +HWND CListControl_ReplaceListBox(HWND wndReplace) { + CListBox src(wndReplace); + const auto style = src.GetStyle(); + HWND ret = NULL; + if ((style & LBS_NODATA) == 0) { + PFC_ASSERT(src.GetCount() == 0); // transferring of items not yet implemented + const auto ctrlID = src.GetDlgCtrlID(); + CWindow parent = src.GetParent(); + auto obj = PP::newWindowObj<CListControl_ListBox>(style); + ret = obj->CreateInDialog(parent, ctrlID, src); + } + return ret; +}
