Mercurial > foo_out_sdl
view 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 source
#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; }
