comparison 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
comparison
equal deleted inserted replaced
0:e9bb126753e7 1:20d02a178406
1 #include "stdafx.h"
2 #include "CListControlComplete.h"
3 #include "CListControl-Subst.h"
4 #include "CWindowCreateAndDelete.h"
5 #include <pfc/string-conv-lite.h>
6 #include "ImplementOnFinalMessage.h"
7 #include "CListControl-Cells.h"
8 #include "windowLifetime.h"
9
10 #define I_IMAGEREALLYNONE (-3)
11
12 namespace {
13
14 static int safeFillText(wchar_t* psz, int cch, pfc::wstringLite const& text) {
15 int len = (int)text.length();
16 int lim = cch - 1;
17 if (len > lim) len = lim;
18 for (int i = 0; i < len; ++i) psz[i] = text[i];
19 psz[len] = 0;
20 return len;
21 }
22
23 class CListControl_ListViewBase : public CListControlComplete {
24 protected:
25 const DWORD m_style;
26 DWORD m_listViewExStyle = 0;
27 CImageList m_imageLists[4];
28 bool m_groupViewEnabled = false;
29 public:
30 CListControl_ListViewBase(DWORD style) : m_style(style) {
31 if (style & LVS_SINGLESEL) this->SetSelectionModeSingle();
32 else this->SetSelectionModeMulti();
33 }
34
35 BEGIN_MSG_MAP_EX(CListControl_ListViewBase)
36 MSG_WM_CREATE(OnCreate)
37 MESSAGE_HANDLER_EX(LVM_INSERTCOLUMN, OnInsertColumn)
38 MESSAGE_HANDLER_EX(LVM_DELETECOLUMN, OnDeleteColumn)
39 MESSAGE_HANDLER_EX(LVM_SETCOLUMN, OnSetColumn)
40 MESSAGE_HANDLER_EX(LVM_SETCOLUMNWIDTH, OnSetColumnWidth)
41 MESSAGE_HANDLER_EX(LVM_GETCOLUMNWIDTH, OnGetColumnWidth)
42 MESSAGE_HANDLER_EX(LVM_GETCOLUMNORDERARRAY, OnGetColumnOrderArray)
43 MESSAGE_HANDLER_EX(LVM_SETCOLUMNORDERARRAY, OnSetColumnOrderArray)
44 MESSAGE_HANDLER_EX(LVM_GETITEMCOUNT, OnGetItemCount)
45 MESSAGE_HANDLER_EX(LVM_GETITEMRECT, OnGetItemRect)
46 MESSAGE_HANDLER_EX(LVM_GETSUBITEMRECT, OnGetSubItemRect)
47 MESSAGE_HANDLER_EX(LVM_HITTEST, OnHitTest)
48 MESSAGE_HANDLER_EX(LVM_GETITEMSTATE, OnGetItemState)
49 MESSAGE_HANDLER_EX(LVM_SETITEMSTATE, OnSetItemState)
50 MESSAGE_HANDLER_EX(LVM_GETNEXTITEM, OnGetNextItem)
51 MESSAGE_HANDLER_EX(LVM_GETNEXTITEMINDEX, OnGetNextItemIndex)
52 MESSAGE_HANDLER_EX(LVM_SUBITEMHITTEST, OnSubItemHitTest)
53 MESSAGE_HANDLER_EX(LVM_GETSELECTEDCOUNT, OnGetSelCount)
54 MESSAGE_HANDLER_EX(LVM_GETHEADER, OnGetHeader)
55 MESSAGE_HANDLER_EX(LVM_SETEXTENDEDLISTVIEWSTYLE, OnSetExtendedListViewStyle)
56 MESSAGE_HANDLER_EX(LVM_GETEXTENDEDLISTVIEWSTYLE, OnGetExtendedListViewStyle)
57 MESSAGE_HANDLER_EX(LVM_ENSUREVISIBLE, OnEnsureVisible)
58 MESSAGE_HANDLER_EX(LVM_SETIMAGELIST, OnSetImageList)
59 MESSAGE_HANDLER_EX(LVM_GETIMAGELIST, OnGetImageList)
60 MESSAGE_HANDLER_EX(LVM_EDITLABEL, OnEditLabel)
61 MESSAGE_HANDLER_EX(LVM_GETSTRINGWIDTH, OnGetStringWidth)
62 MESSAGE_HANDLER_EX(LVM_ENABLEGROUPVIEW, OnEnableGroupView)
63 MESSAGE_HANDLER_EX(LVM_SCROLL, OnScroll)
64 MESSAGE_HANDLER_EX(LVM_REDRAWITEMS, OnRedrawItems)
65 MSG_WM_KEYDOWN(OnKeyDown)
66 MSG_WM_SYSKEYDOWN(OnKeyDown)
67 CHAIN_MSG_MAP(CListControlComplete)
68 END_MSG_MAP()
69
70 void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) {
71 NMLVKEYDOWN arg = {};
72 arg.hdr = this->setupHdr(LVN_KEYDOWN);
73 arg.wVKey = nChar;
74 sendNotify(&arg);
75 SetMsgHandled(FALSE);
76 }
77 LRESULT OnCreate(LPCREATESTRUCT) {
78 SetMsgHandled(FALSE);
79 // Adopt style flags of the original control to keep various ATL checks happy
80 DWORD style = GetStyle();
81 style = (style & 0xFFFF0000) | (m_style & 0xFFFF);
82 SetWindowLong(GWL_STYLE, style);
83 return 0;
84 }
85
86 LRESULT OnGetColumnWidth(UINT, WPARAM wp, LPARAM) {
87 size_t idx = (size_t)wp;
88 return GetSubItemWidth(idx);
89 }
90 LRESULT OnSetColumnWidth(UINT, WPARAM wp, LPARAM lp) {
91 size_t idx = (size_t)wp;
92 if (idx >= GetColumnCount()) return FALSE;
93 // undocumented: low 16 bits must be used, or else testing against LVSCW_AUTOSIZE fails hard
94 // all macros sending LVM_SETCOLUMNWIDTH use low 16 bits of LPARAM
95 int w = (short)lp;
96 if (this->IsHeaderless()) {
97 if (w < 0) {
98 m_headerlessColumns[idx] = UINT32_MAX;
99 } else {
100 m_headerlessColumns[idx] = (uint32_t)w;
101 this->OnColumnsChanged();
102 }
103 } else {
104 uint32_t pass = (uint32_t)w;
105 if (w < 0) {
106 if (w == LVSCW_AUTOSIZE_USEHEADER) pass = columnWidthAuto;
107 else pass = columnWidthAutoUseContent;
108 }
109 this->ResizeColumn(idx, pass);
110 }
111 return TRUE;
112 }
113 LRESULT OnGetColumnOrderArray(UINT, WPARAM wp, LPARAM lp) {
114 int* out = (int*)lp;
115 auto arr = this->GetColumnOrderArray();
116 if ((size_t)wp != arr.size()) return 0;
117 for (size_t w = 0; w < arr.size(); ++w) out[w] = arr[w];
118 return 1;
119 }
120 LRESULT OnSetColumnOrderArray(UINT, WPARAM wp, LPARAM lp) {
121 if (this->IsHeaderless()) {
122 PFC_ASSERT(!"Implement and test me");
123 return 0;
124 } else {
125 auto hdr = GetHeaderCtrl();
126 WIN32_OP_D(hdr.SetOrderArray((int)wp, (int*)lp));
127 }
128 ReloadData();
129 return 1;
130 }
131 LRESULT OnInsertColumn(UINT, WPARAM wp, LPARAM lp) {
132 size_t idx = (size_t)wp;
133 auto col = reinterpret_cast<LVCOLUMN*>(lp);
134
135 if (IsHeaderless()) {
136 if (idx > m_headerlessColumns.size()) idx = m_headerlessColumns.size();
137 uint32_t width = 0;
138 if (col->mask & LVCF_WIDTH) width = col->cx;
139 m_headerlessColumns.insert(m_headerlessColumns.begin() + idx, width);
140 this->OnColumnsChanged();
141 return 0;
142 }
143 if (this->GetHeaderCtrl() == NULL) {
144 if (m_style & LVS_NOSORTHEADER) {
145 this->InitializeHeaderCtrl();
146 } else {
147 this->InitializeHeaderCtrlSortable();
148 }
149 }
150
151 PFC_ASSERT(this->GetHeaderCtrl().GetItemCount() == (int)this->GetColumnCount());
152
153 if (idx != this->GetColumnCount()) {
154 PFC_ASSERT(!"Arbitrary column insert not implemented, please add columns in order");
155 return -1;
156 }
157
158 pfc::string8 label; int width = 0; DWORD format = HDF_LEFT;
159 if (col->mask & LVCF_TEXT) label = pfc::utf8FromWide(col->pszText);
160 if (col->mask & LVCF_WIDTH) width = col->cx;
161 if (col->mask & LVCF_FMT) {
162 format = col->fmt & LVCFMT_JUSTIFYMASK;
163 }
164 this->AddColumn(label, width, format);
165
166 PFC_ASSERT(this->GetHeaderCtrl().GetItemCount() == (int)this->GetColumnCount());
167
168 return idx;
169 }
170 LRESULT OnDeleteColumn(UINT, WPARAM wp, LPARAM) {
171 size_t idx = (size_t)wp;
172 if (idx >= this->GetColumnCount()) {
173 PFC_ASSERT(!"???"); return FALSE;
174 }
175 this->DeleteColumn(idx);
176 return TRUE;
177 }
178 LRESULT OnSetColumn(UINT, WPARAM wp, LPARAM lp) {
179 size_t idx = (size_t)wp;
180 if (idx >= this->GetColumnCount()) {
181 PFC_ASSERT(!"???"); return FALSE;
182 }
183 auto col = reinterpret_cast<LVCOLUMN*>(lp);
184
185 if (col->mask & (LVCF_TEXT | LVCF_FMT)) {
186 const char* pText = nullptr; DWORD format = UINT32_MAX;
187 pfc::string8 strText;
188 if (col->mask & LVCF_TEXT) {
189 strText = pfc::utf8FromWide(col->pszText);
190 pText = strText.c_str();
191 }
192 if (col->mask & LVCF_FMT) {
193 format = col->fmt & LVCFMT_JUSTIFYMASK;
194 }
195 this->SetColumn(idx, pText, format);
196 }
197
198 if (col->mask & LVCF_WIDTH) {
199 this->ResizeColumn(idx, col->cx);
200 }
201
202 return TRUE;
203 }
204 LRESULT OnGetItemCount(UINT, WPARAM, LPARAM) {
205 return (LRESULT)this->GetItemCount();
206 }
207
208 LRESULT OnGetItemRect(UINT, WPARAM wp, LPARAM lp) {
209 size_t idx = (size_t)wp;
210 RECT* rc = (RECT*)lp;
211 if (idx < GetItemCount()) {
212 // LVIR_* not supported
213 * rc = GetItemRect(idx);
214 return TRUE;
215 }
216 return FALSE;
217 }
218
219 LRESULT OnGetSubItemRect(UINT, WPARAM wp, LPARAM lp) {
220 size_t idx = (size_t)wp;
221 RECT* rc = (RECT*)lp;
222 if (idx < GetItemCount()) {
223 size_t subItem = (size_t)rc->top;
224 // LVIR_* not supported
225 *rc = GetSubItemRect(idx, subItem);
226 return TRUE;
227 }
228 return FALSE;
229 }
230
231 LRESULT OnHitTest(UINT, WPARAM, LPARAM lp) {
232 auto info = (LVHITTESTINFO*)lp;
233 CPoint pt = this->PointClientToAbs(info->pt);
234 size_t item = this->ItemFromPointAbs(pt);
235 size_t subItem = SIZE_MAX;
236 if (item != SIZE_MAX) {
237 info->iItem = (int)item;
238 size_t subItem = this->SubItemFromPointAbs(pt);
239 info->iSubItem = (subItem != SIZE_MAX) ? (int)subItem : -1;
240 }
241 return item != SIZE_MAX ? (LRESULT)item : (LRESULT)-1;
242 }
243 LRESULT OnSubItemHitTest(UINT, WPARAM, LPARAM lp) {
244 auto info = (LVHITTESTINFO*)lp;
245 CPoint pt = this->PointClientToAbs(info->pt);
246 size_t item = this->ItemFromPointAbs(pt);
247 size_t subItem = SIZE_MAX;
248 if (item != SIZE_MAX) {
249 info->iItem = (int)item;
250 subItem = this->SubItemFromPointAbs(pt);
251 info->iSubItem = (subItem != SIZE_MAX) ? (int)subItem : -1;
252 }
253 return subItem != SIZE_MAX ? (LRESULT)subItem : (LRESULT)-1;
254 }
255
256 virtual void SetItemState(size_t idx, DWORD mask, DWORD state) {
257 if (idx >= GetItemCount()) return;
258 if (mask & LVIS_FOCUSED) {
259 if (state & LVIS_FOCUSED) {
260 this->SetFocusItem(idx);
261 } else {
262 if (this->GetFocusItem() == idx) {
263 this->SetFocusItem(SIZE_MAX);
264 }
265 }
266 }
267 if (mask & LVIS_SELECTED) {
268 if (this->IsSingleSelect()) {
269 if (state & LVIS_SELECTED) this->SelectSingle(idx);
270 } else {
271 this->SetSelectionAt(idx, (state & LVIS_SELECTED) != 0);
272 }
273 }
274 }
275 LRESULT OnSetItemState(UINT, WPARAM wp, LPARAM lp) {
276 LVITEM* pItem = (LVITEM*)lp;
277 if ((LONG_PTR)wp < 0) {
278 if (pItem->stateMask & LVIS_FOCUSED) {
279 if (pItem->state & LVIS_FOCUSED) {
280 // makes no sense
281 } else {
282 this->SetFocusItem(SIZE_MAX);
283 }
284 }
285 if (pItem->stateMask & LVIS_SELECTED) {
286 if (pItem->state & LVIS_SELECTED) {
287 this->SelectAll();
288 } else {
289 this->SelectNone();
290 }
291 }
292 } else {
293 size_t idx = (size_t)wp;
294 SetItemState(idx, pItem->stateMask, pItem->state);
295 }
296 return TRUE;
297 }
298
299 virtual UINT GetItemState(size_t idx) const {
300 UINT ret = 0;
301 if (idx == this->GetFocusItem()) ret |= LVIS_FOCUSED;
302 if (this->IsItemSelected(idx)) ret |= LVIS_SELECTED;
303 return ret;
304 }
305 LRESULT OnGetItemState(UINT, WPARAM wp, LPARAM lp) {
306 LRESULT ret = 0;
307 size_t idx = (size_t)wp;
308 if (idx < GetItemCount()) {
309 ret |= (this->GetItemState(idx) & lp);
310 }
311 return ret;
312 }
313
314 LRESULT OnGetNextItemIndex(UINT, WPARAM wp, LPARAM lp) {
315 LVITEMINDEX* info = (LVITEMINDEX*)wp;
316 int item = (int)OnGetNextItem(LVM_GETNEXTITEM, info->iItem, lp);
317 info->iItem = item;
318 return item >= 0;
319 }
320 LRESULT OnGetNextItem(UINT, WPARAM wp, LPARAM lp) {
321 // GetSelectedIndex() :
322 // return (int)::SendMessage(this->m_hWnd, LVM_GETNEXTITEM, (WPARAM)-1, MAKELPARAM(LVNI_ALL | LVNI_SELECTED, 0));
323
324 const bool bVisible = (lp & LVNI_VISIBLEONLY) != 0;
325 std::pair<size_t, size_t> range;
326 if ( bVisible ) range = this->GetVisibleRange();
327
328 if ((lp & LVNI_STATEMASK) == LVNI_FOCUSED) {
329 size_t ret = this->GetFocusItem();
330 if (bVisible) {
331 if (ret < range.first || ret >= range.second) ret = SIZE_MAX;
332 }
333 return (LRESULT)ret;
334 }
335
336 const auto count = this->GetItemCount();
337
338 if (wp == (WPARAM)-1 && bVisible) {
339 // Simplified visible range search
340 for (size_t idx = range.first; idx < range.second; ++idx) {
341 if (this->GetItemState(idx) & lp) {
342 return (LRESULT)idx;
343 }
344 }
345 return -1;
346 }
347
348 size_t base;
349 if ((LONG_PTR)wp < 0) base = 0;
350 else base = wp + 1;
351 for (size_t idx = base; idx < count; ++idx) {
352 if (bVisible) {
353 if (idx < range.first || idx >= range.second) continue;
354 }
355 if (this->GetItemState(idx) & lp) {
356 return (LRESULT)idx;
357 }
358 }
359 return -1;
360 }
361 LRESULT OnGetSelCount(UINT, WPARAM, LPARAM) {
362 return (LRESULT) this->GetSelectedCount();
363 }
364 LRESULT OnGetHeader(UINT, WPARAM, LPARAM) {
365 return (LRESULT)GetHeaderCtrl().m_hWnd;
366 }
367 LRESULT OnSetExtendedListViewStyle(UINT, WPARAM wp, LPARAM lp) {
368 DWORD ret = m_listViewExStyle;
369 DWORD set = (DWORD)lp, mask = (DWORD)wp;
370 if (mask == 0) mask = 0xFFFFFFFF;
371 m_listViewExStyle = (m_listViewExStyle & ~mask) | (set & mask);
372
373 this->SetRowStyle((m_listViewExStyle & LVS_EX_GRIDLINES) ? rowStyleGrid : rowStyleDefault);
374
375 if (m_listViewExStyle != ret) ReloadData();
376 return ret;
377 }
378 LRESULT OnGetExtendedListViewStyle(UINT, WPARAM, LPARAM) {
379 return m_listViewExStyle;
380 }
381 LRESULT OnEnsureVisible(UINT, WPARAM wp, LPARAM lp) {
382 size_t idx = (size_t)wp;
383 if (idx < this->GetItemCount()) {
384 (void)lp; // FIX ME partialOK?
385 this->EnsureItemVisible(idx);
386 return TRUE;
387 }
388 return FALSE;
389 }
390 LRESULT OnSetImageList(UINT, WPARAM wp, LPARAM lp) {
391 size_t idx = (size_t)wp;
392 LRESULT ret = 0;
393 if (idx < std::size(m_imageLists)) {
394 auto& ref = m_imageLists[idx];
395 ret = (LRESULT)ref.m_hImageList;
396 ref = (HIMAGELIST)lp;
397 }
398 return ret;
399 }
400 LRESULT OnGetImageList(UINT, WPARAM wp, LPARAM lp) {
401 size_t idx = (size_t)wp;
402 LRESULT ret = 0;
403 if (idx < std::size(m_imageLists)) {
404 ret = (LRESULT)m_imageLists[idx].m_hImageList;
405 }
406 return ret;
407 }
408 LRESULT OnEditLabel(UINT, WPARAM wp, LPARAM) {
409 int index = (int)wp;
410 HWND ret = NULL;
411 if (index < 0) {
412 this->TableEdit_Abort(false);
413 } else {
414 ret = this->TableEdit_Start((size_t)index, 0);
415 }
416 return (LRESULT)ret;
417 }
418 LRESULT OnGetStringWidth(UINT, WPARAM, LPARAM lp) {
419 auto str = (const wchar_t*)lp;
420 return this->GetOptimalColumnWidthFixed(pfc::utf8FromWide(str), false /* no pad */);
421 }
422 LRESULT OnEnableGroupView(UINT, WPARAM wp, LPARAM) {
423 bool bEnable = (wp != 0);
424 if (bEnable == m_groupViewEnabled) return 0;
425 m_groupViewEnabled = bEnable;
426 ReloadData();
427 return 1;
428 }
429 LRESULT OnScroll(UINT, WPARAM, LPARAM) {
430 PFC_ASSERT(!"Implement me");
431 return 0;
432 }
433 LRESULT OnRedrawItems(UINT, WPARAM wp, LPARAM lp) {
434 size_t base = wp;
435 size_t last = lp;
436 if (last < base) return FALSE;
437 size_t count = last + 1 - base;
438 this->UpdateItems(pfc::bit_array_range(base, count));
439 return TRUE;
440 }
441 bool TableEdit_CanAdvanceHere(size_t item, size_t subItem, uint32_t whatHappened) const override {
442 // Block line cycling with enter key
443 auto code = (whatHappened & InPlaceEdit::KEditMaskReason);
444 if (code == InPlaceEdit::KEditEnter) return false;
445 return __super::TableEdit_CanAdvanceHere(item, subItem, whatHappened);
446 }
447 bool TableEdit_IsColumnEditable(t_size subItem) const override {
448 return subItem == 0;
449 }
450 NMHDR setupHdr(UINT code) const {
451 return { m_hWnd, (UINT_PTR)this->GetDlgCtrlID(), code };
452 }
453 LRESULT sendNotify(void* data) const {
454 auto hdr = reinterpret_cast<const NMHDR*>(data);
455 PFC_ASSERT(hdr->idFrom == (UINT_PTR) GetDlgCtrlID());
456 return GetParent().SendMessage(WM_NOTIFY, hdr->idFrom, reinterpret_cast<LPARAM>(data));
457 }
458 int getDispInfoImage(size_t item, size_t subItem) const {
459 NMLVDISPINFO info = { setupHdr(LVN_GETDISPINFO) };
460 info.item.mask = LVIF_IMAGE;
461 info.item.iItem = (int)item;
462 info.item.iSubItem = (int)subItem;
463 sendNotify(&info);
464 PFC_ASSERT(info.item.iImage != I_IMAGECALLBACK);
465 return info.item.iImage;
466 }
467 pfc::string8 getDispInfoText(size_t item, size_t subItem) const {
468 NMLVDISPINFO info = { setupHdr(LVN_GETDISPINFO) };
469 info.item.mask = LVIF_TEXT;
470 info.item.iItem = (int)item;
471 info.item.iSubItem = (int)subItem;
472
473 wchar_t buffer[1024] = {};
474 info.item.pszText = buffer;
475 info.item.cchTextMax = (int) std::size(buffer);
476 sendNotify(&info);
477 if (info.item.pszText == LPSTR_TEXTCALLBACK || info.item.pszText == nullptr) {
478 PFC_ASSERT(!"WTF??"); return "";
479 }
480 return pfc::utf8FromWide(info.item.pszText);
481 }
482 virtual LPARAM GetItemParam(size_t) { return 0; }
483 void ExecuteDefaultAction(size_t idx) override {
484 NMITEMACTIVATE info = { setupHdr(NM_DBLCLK) };
485 info.iItem = (int) idx;
486 info.lParam = GetItemParam(idx);
487 sendNotify(&info);
488 }
489 void OnSubItemClicked(t_size item, t_size subItem, CPoint pt) override {
490 __super::OnSubItemClicked(item, subItem, pt);
491 NMITEMACTIVATE info = { setupHdr(NM_CLICK) };
492 info.iItem = (int)item;
493 info.iSubItem = (int)subItem;
494 info.ptAction = pt;
495 sendNotify(&info);
496 }
497 void OnFocusChanged(size_t oldFocus, size_t newFocus) override {
498 __super::OnFocusChanged(oldFocus, newFocus);
499 const auto count = this->GetItemCount();
500
501 if (oldFocus < count) {
502 auto idx = oldFocus;
503 NMLISTVIEW info = { setupHdr(LVN_ITEMCHANGED) };
504 info.iItem = (int)idx;
505 info.lParam = this->GetItemParam(idx);
506 auto base = this->GetItemState(idx);
507 info.uOldState = base | LVIS_FOCUSED;
508 info.uNewState = base;
509 info.uChanged = LVIF_STATE;
510 this->sendNotify(&info);
511 }
512 if (newFocus < count) {
513 auto idx = newFocus;
514 NMLISTVIEW info = { setupHdr(LVN_ITEMCHANGED) };
515 info.iItem = (int)idx;
516 info.lParam = this->GetItemParam(idx);
517 auto base = this->GetItemState(idx);
518 info.uOldState = base & ~LVIS_FOCUSED;
519 info.uNewState = base;
520 info.uChanged = LVIF_STATE;
521 this->sendNotify(&info);
522 }
523 }
524
525 void OnSelectionChanged(pfc::bit_array const& affected, pfc::bit_array const& status) override {
526
527 __super::OnSelectionChanged(affected, status);
528 const auto count = this->GetItemCount();
529
530 const size_t focus = this->GetFocusItem();
531
532 // LVN_ITEMCHANGING not supported, CListControl currently doesn't hand this info
533
534 affected.for_each(true, 0, count, [&](size_t idx) {
535 bool sel_new = status[idx];
536 bool sel_old = !sel_new;
537 NMLISTVIEW info = { setupHdr(LVN_ITEMCHANGED) };
538 info.iItem = (int)idx;
539 info.lParam = this->GetItemParam(idx);
540 info.uOldState = (sel_old ? LVIS_SELECTED : 0) | (idx == focus ? LVIS_FOCUSED : 0);
541 info.uNewState = (sel_new ? LVIS_SELECTED : 0) | (idx == focus ? LVIS_FOCUSED : 0);
542 info.uChanged = LVIF_STATE;
543 this->sendNotify(&info);
544 });
545 }
546 void RequestReorder(size_t const* order, size_t count) override {}
547 void RequestRemoveSelection() override {}
548
549 void OnColumnHeaderClick(t_size index) override {
550 NMLISTVIEW info = { setupHdr(LVN_COLUMNCLICK) };
551 info.iItem = -1;
552 info.iSubItem = (int)index;
553 this->sendNotify(&info);
554 }
555
556 bool IsHeaderless() const {
557 return (m_style & LVS_NOCOLUMNHEADER) != 0;
558 }
559
560 std::vector< uint32_t > m_headerlessColumns;
561
562 size_t GetColumnCount() const override {
563 if (IsHeaderless()) {
564 return m_headerlessColumns.size();
565 }
566 return __super::GetColumnCount();
567 }
568 uint32_t GetSubItemWidth(size_t subItem) const override {
569 if (IsHeaderless()) {
570 if (subItem < m_headerlessColumns.size()) {
571 auto v = m_headerlessColumns[subItem];
572 if (v == UINT32_MAX) {
573 uint32_t nAuto = 0, wNormal = 0;
574 for (auto walk : m_headerlessColumns) {
575 if (walk == UINT32_MAX) ++nAuto;
576 else wNormal += walk;
577 }
578 PFC_ASSERT(nAuto > 0);
579 uint32_t wTotal = this->GetClientRectHook().Width();
580 if (wTotal > wNormal) {
581 uint32_t autoTotal = wTotal - wNormal;
582 v = autoTotal / nAuto;
583 } else {
584 v = 0;
585 }
586 }
587 return v;
588 }
589 else return 0;
590 }
591 return __super::GetSubItemWidth(subItem);
592 }
593
594 bool GetCellTypeSupported() const override {
595 return useCheckBoxes();
596 }
597
598 cellType_t GetCellType(size_t item, size_t subItem) const override {
599 if (useCheckBoxes() && subItem == 0) {
600 return &PFC_SINGLETON(CListCell_Checkbox);
601 }
602 return __super::GetCellType(item, subItem);
603 }
604
605 bool useCheckBoxes() const {
606 return (m_listViewExStyle & LVS_EX_CHECKBOXES) != 0;
607 }
608
609 void TableEdit_SetField(t_size item, t_size subItem, const char* value) override {
610 if (subItem != 0) {
611 PFC_ASSERT(!"subItem should be zero");
612 return;
613 }
614 auto textW = pfc::wideFromUTF8(value);
615 NMLVDISPINFO info = { setupHdr(LVN_ENDLABELEDIT) };
616 info.item.iItem = (int)item;
617 info.item.mask = LVIF_TEXT;
618 info.item.pszText = const_cast<wchar_t*>( textW.c_str() );
619
620 if (sendNotify(&info) != 0) {
621 SetSubItemText(item, subItem, value);
622 }
623 }
624
625 virtual void SetSubItemText(size_t item, size_t subItem, const char* text) {}
626
627 virtual int GetItemImage(size_t item, size_t subItem) const {
628 return I_IMAGEREALLYNONE;
629 }
630 CImageList GetImageList() const {
631 return m_imageLists[LVSIL_SMALL];
632 }
633 bool RenderCellImageTest(size_t item, size_t subItem) const override {
634 return GetImageList() != NULL && GetItemImage(item, subItem) != I_IMAGEREALLYNONE;
635 }
636 void RenderCellImage(size_t item, size_t subItem, CDCHandle dc, const CRect& rc) const override {
637 auto img = GetItemImage(item, subItem);
638 auto imgList = GetImageList();
639 if (img >= 0 && imgList) {
640 CSize size;
641 WIN32_OP_D(imgList.GetIconSize(size));
642 CRect rc2 = rc;
643 if (size.cx <= rc.Width() && size.cy <= rc.Height()) {
644 auto cp = rc.CenterPoint();
645 rc2.left = cp.x - size.cx / 2;
646 rc2.top = cp.y - size.cy / 2;
647 rc2.right = rc2.left + size.cx;
648 rc2.bottom = rc2.top + size.cy;
649 }
650 imgList.DrawEx(img, dc, rc2, CLR_NONE, CLR_NONE, ILD_SCALE);
651 }
652 }
653
654 bool m_notifyItemDraw = false;
655
656 UINT GetItemCDState(size_t which) const {
657 UINT ret = 0;
658 DWORD state = GetItemState(which);
659 if (state & LVIS_FOCUSED) ret |= CDIS_FOCUS;
660 if (state & LVIS_SELECTED) ret |= CDIS_SELECTED;
661 return ret;
662 }
663
664 void RenderRect(const CRect& p_rect, CDCHandle p_dc) override {
665 NMCUSTOMDRAW cd = { setupHdr(NM_CUSTOMDRAW) };
666 cd.dwDrawStage = CDDS_PREPAINT;
667 cd.hdc = p_dc;
668 cd.rc = p_rect;
669 cd.dwItemSpec = UINT32_MAX;
670 cd.uItemState = 0;
671 cd.lItemlParam = 0;
672 LRESULT status = sendNotify(&cd);
673 m_notifyItemDraw = (status & CDRF_NOTIFYITEMDRAW) != 0;
674
675 if ((status & CDRF_SKIPDEFAULT) != 0) {
676 return;
677 }
678 __super::RenderRect(p_rect, p_dc);
679
680 cd.dwDrawStage = CDDS_POSTPAINT;
681 sendNotify(&cd);
682 }
683 void RenderItem(t_size item, const CRect& itemRect, const CRect& updateRect, CDCHandle dc) override {
684 NMCUSTOMDRAW cd = {};
685 if (m_notifyItemDraw) {
686 cd = { setupHdr(NM_CUSTOMDRAW) };
687 cd.dwDrawStage = CDDS_ITEMPREPAINT;
688 cd.hdc = dc;
689 cd.rc = itemRect;
690 cd.dwItemSpec = (DWORD)item;
691 cd.uItemState = GetItemCDState(item);
692 cd.lItemlParam = GetItemParam(item);
693 LRESULT status = sendNotify(&cd);
694 if (status & CDRF_SKIPDEFAULT) return;
695 }
696
697 __super::RenderItem(item, itemRect, updateRect, dc);
698
699
700 if (m_notifyItemDraw) {
701 cd.dwDrawStage = CDDS_ITEMPOSTPAINT;
702 sendNotify(&cd);
703 }
704 }
705 #if 0
706 void RenderSubItemText(t_size item, t_size subItem, const CRect& subItemRect, const CRect& updateRect, CDCHandle dc, bool allowColors) override {
707
708 __super::RenderSubItemText(item, subItem, subItemRect, updateRect, dc, allowColors);
709 }
710 #endif
711
712 };
713
714 class CListControl_ListViewOwnerData : public CListControl_ListViewBase {
715 public:
716 CListControl_ListViewOwnerData(DWORD style) : CListControl_ListViewBase(style) {}
717
718 BEGIN_MSG_MAP_EX(CListControl_ListViewOwnerData)
719 MESSAGE_HANDLER_EX(LVM_SETITEMCOUNT, OnSetItemCount)
720 CHAIN_MSG_MAP(CListControl_ListViewBase)
721 END_MSG_MAP()
722 private:
723 size_t m_itemCount = 0;
724 LRESULT OnSetItemCount(UINT, WPARAM wp, LPARAM) {
725 auto count = (size_t)wp;
726 if (m_itemCount != count) {
727 m_itemCount = count; ReloadData();
728 }
729 return 0;
730 }
731
732 t_size GetItemCount() const override {
733 return m_itemCount;
734 }
735 bool GetSubItemText(t_size item, t_size subItem, pfc::string_base& out) const override {
736 if (item < m_itemCount) {
737 out = this->getDispInfoText(item, subItem);
738 return true;
739 }
740 return false;
741 }
742 int GetItemImage(size_t item, size_t subItem) const override {
743 int ret = I_IMAGEREALLYNONE;
744 if (subItem == 0) {
745 ret = this->getDispInfoImage(item, subItem);
746 }
747 return ret;
748 }
749 void SetSubItemText(size_t item, size_t subItem, const char* text) override {
750 auto textW = pfc::wideFromUTF8(text);
751 NMLVDISPINFO info = { setupHdr(LVN_SETDISPINFO) };
752 info.item.iItem = (int)item;
753 info.item.iSubItem = (int)subItem;
754 info.item.mask = LVIF_TEXT;
755 info.item.pszText = const_cast<wchar_t*>(textW.c_str());
756 sendNotify(&info);
757 ReloadItem(item);
758 }
759 };
760
761 class CListControl_ListView : public CListControl_ListViewBase {
762 public:
763 CListControl_ListView(DWORD style) : CListControl_ListViewBase(style) {}
764
765 BEGIN_MSG_MAP_EX(CListControl_ListView)
766 MESSAGE_HANDLER_EX(LVM_INSERTITEM, OnInsertItem)
767 MESSAGE_HANDLER_EX(LVM_SETITEM, OnSetItem)
768 MESSAGE_HANDLER_EX(LVM_SETITEMCOUNT, OnSetItemCount)
769 MESSAGE_HANDLER_EX(LVM_GETITEM, OnGetItem)
770 MESSAGE_HANDLER_EX(LVM_GETITEMTEXT, OnGetItemText)
771 MESSAGE_HANDLER_EX(LVM_INSERTGROUP, OnInsertGroup)
772 MESSAGE_HANDLER_EX(LVM_REMOVEGROUP, OnRemoveGroup)
773 MESSAGE_HANDLER_EX(LVM_GETGROUPCOUNT, OnGetGroupCount)
774 MESSAGE_HANDLER_EX(LVM_GETGROUPINFO, OnGetGroupInfo)
775 MESSAGE_HANDLER_EX(LVM_SETGROUPINFO, OnSetGroupInfo)
776 MESSAGE_HANDLER_EX(LVM_GETGROUPINFOBYINDEX, OnGetGroupInfoByIndex)
777 MESSAGE_HANDLER_EX(LVM_REMOVEALLGROUPS, OnRemoveAllGroups)
778 MESSAGE_HANDLER_EX(LVM_DELETEITEM, OnDeleteItem)
779 MESSAGE_HANDLER_EX(LVM_DELETEALLITEMS, OnDeleteAllItems)
780 CHAIN_MSG_MAP(CListControl_ListViewBase)
781 END_MSG_MAP()
782 private:
783 struct text_t {
784 pfc::string8 text;
785 bool callback = false;
786 };
787 struct rec_t {
788 std::vector< text_t > text;
789 LPARAM param = 0;
790 int image = I_IMAGEREALLYNONE;
791 bool checked = false;
792 groupID_t groupID = 0;
793 int LVGroupID = 0;
794 };
795
796 std::vector<rec_t> m_content;
797
798 groupID_t m_groupIDGen = 0;
799 groupID_t groupIDGen() { return ++m_groupIDGen; }
800 struct group_t {
801 int LVGroupID = 0;
802 groupID_t GroupID = 0;
803 pfc::string8 header;
804 };
805 std::vector<group_t> m_groups;
806
807 groupID_t groupFromLV(int LV) const {
808 for (auto& g : m_groups) {
809 if (g.LVGroupID == LV) return g.GroupID;
810 }
811 return 0;
812 }
813
814 t_size GetItemCount() const override {
815 return m_content.size();
816 }
817 bool GetSubItemText(t_size item, t_size subItem, pfc::string_base& out) const override {
818 if (item < m_content.size()) {
819 auto& r = m_content[item];
820 if (subItem < r.text.size()) {
821 auto& t = r.text[subItem];
822 if (t.callback) {
823 out = getDispInfoText(item, subItem);
824 } else {
825 out = t.text;
826 }
827 return true;
828 }
829 }
830 return false;
831 }
832 void SetSubItemText(size_t item, size_t subItem, const char* text) override {
833 if (item < m_content.size()) {
834 auto& r = m_content[item];
835 if (subItem < r.text.size()) {
836 auto& t = r.text[subItem];
837 t.callback = false; t.text = text;
838 ReloadItem(item);
839 }
840 }
841 }
842
843 int GetItemImage(size_t item, size_t subItem) const override {
844 int ret = I_IMAGEREALLYNONE;
845 if (subItem == 0 && item < m_content.size()) {
846 auto& r = m_content[item];
847 ret = r.image;
848 if (ret == I_IMAGECALLBACK) ret = this->getDispInfoImage(item, subItem);
849 }
850 return ret;
851 }
852 LPARAM GetItemParam(size_t item) override {
853 LPARAM ret = 0;
854 if (item < m_content.size()) {
855 ret = m_content[item].param;
856 }
857 return ret;
858 }
859
860 static text_t makeText(const wchar_t* p) {
861 text_t text;
862 if (p == LPSTR_TEXTCALLBACK) {
863 text.callback = true;
864 } else {
865 text.text = pfc::utf8FromWide(p);
866 }
867 return text;
868 }
869 LRESULT OnSetItem(UINT, WPARAM, LPARAM lp) {
870 auto pItem = reinterpret_cast<LVITEM*>(lp);
871 size_t item = (size_t)pItem->iItem;
872 const size_t total = GetItemCount();
873 if (item >= total) return FALSE;
874 size_t subItem = (size_t)pItem->iSubItem;
875 if (subItem > 1024) return FALSE; // quick sanity
876 auto& rec = m_content[item];
877 size_t width = subItem + 1;
878 if (rec.text.size() < width) rec.text.resize(width);
879 bool bReload = false, bReloadAll = false;
880 if (pItem->mask & LVIF_TEXT) {
881 rec.text[subItem] = makeText(pItem->pszText);
882 bReload = true;
883 }
884 if (pItem->mask & LVIF_IMAGE) {
885 rec.image = pItem->iImage;
886 bReload = true;
887 }
888 if (pItem->mask & LVIF_PARAM) {
889 rec.param = pItem->lParam;
890 }
891 if (pItem->mask & LVIF_GROUPID) {
892 rec.LVGroupID = pItem->iGroupId;
893 rec.groupID = groupFromLV(rec.LVGroupID);
894 if (m_groupViewEnabled) bReloadAll = true;
895 }
896 if (bReloadAll) {
897 this->ReloadData();
898 } else if (bReload) {
899 this->ReloadItem(item);
900 }
901 if (pItem->mask & LVIF_STATE) {
902 this->SetItemState(item, pItem->stateMask, pItem->state);
903 }
904 return TRUE;
905 }
906 LRESULT OnInsertItem(UINT, WPARAM, LPARAM lp) {
907 auto pItem = reinterpret_cast<LVITEM*>(lp);
908
909 size_t item = (size_t)pItem->iItem;
910 const size_t total = GetItemCount();
911 if (item > total) item = total;
912
913 rec_t r;
914 if (pItem->mask & LVIF_TEXT) {
915 r.text = { makeText(pItem->pszText) };
916 }
917 if (pItem->mask & LVIF_GROUPID) {
918 r.LVGroupID = pItem->iGroupId;
919 r.groupID = groupFromLV(r.LVGroupID);
920 }
921 if (pItem->mask & LVIF_IMAGE) {
922 r.image = pItem->iImage;
923 }
924 if (pItem->mask & LVIF_PARAM) {
925 r.param = pItem->lParam;
926 }
927
928 m_content.insert(m_content.begin() + item, std::move(r));
929 if (m_groupViewEnabled) ReloadData();
930 else this->OnItemsInserted(item, 1, false);
931
932 return (LRESULT)item;
933 }
934 LRESULT OnSetItemCount(UINT, WPARAM wp, LPARAM) {
935 // It's not actually supposed to be called like this, LVM_SETITEMCOUNT is meant for ownerdata listviews
936 auto count = (size_t)wp;
937 if (count != m_content.size()) {
938 m_content.resize(wp);
939 ReloadData();
940 }
941 return 0;
942 }
943
944 LRESULT OnInsertGroup(UINT, WPARAM wp, LPARAM lp) {
945 LVGROUP* arg = (LVGROUP*)lp;
946 if ((arg->mask & (LVGF_GROUPID | LVGF_HEADER)) != (LVGF_GROUPID | LVGF_HEADER)) return -1;
947
948 auto LVGroupID = arg->iGroupId;
949 for (auto& existing : m_groups) {
950 if (existing.LVGroupID == LVGroupID) return -1;
951 }
952
953 size_t idx = (size_t)wp;
954 if (idx > m_groups.size()) idx = m_groups.size();
955
956 group_t grp;
957 grp.LVGroupID = LVGroupID;
958 grp.GroupID = this->groupIDGen();
959 grp.header = pfc::utf8FromWide(arg->pszHeader);
960 m_groups.insert(m_groups.begin() + idx, std::move(grp));
961 // No reload data - adding of items does it
962 return (LRESULT)idx;
963 }
964 LRESULT OnRemoveGroup(UINT, WPARAM wp, LPARAM) {
965 int LVGroupID = (int)wp;
966 for (size_t walk = 0; walk < m_groups.size(); ++walk) {
967 if (m_groups[walk].LVGroupID == LVGroupID) {
968 m_groups.erase(m_groups.begin() + walk);
969 return walk;
970 }
971 }
972 return -1;
973 }
974 void getGroupInfo(group_t const& in, LVGROUP* out) {
975 if (out->mask & LVGF_HEADER) {
976 auto text = pfc::wideFromUTF8(in.header.c_str());
977 safeFillText(out->pszHeader, out->cchHeader, text);
978 }
979 if (out->mask & LVGF_GROUPID) {
980 out->iGroupId = in.LVGroupID;
981 }
982 }
983 void setGroupInfo(group_t& grp, LVGROUP* in) {
984 bool bReload = false;
985 if (in->mask & LVGF_HEADER) {
986 grp.header = pfc::utf8FromWide(in->pszHeader);
987 bReload = true;
988 }
989 if (in->mask & LVGF_GROUPID) {
990 grp.LVGroupID = in->iGroupId;
991 bReload = true;
992 }
993
994 if (bReload) this->ReloadData();
995 }
996 LRESULT OnGetGroupCount(UINT, WPARAM, LPARAM) {
997 return (LRESULT)m_groups.size();
998 }
999 LRESULT OnGetGroupInfo(UINT, WPARAM wp, LPARAM lp) {
1000 int LVGroupID = (int)wp;
1001 auto out = (LVGROUP*)lp;
1002 for (auto& grp : m_groups) {
1003 if (grp.LVGroupID == LVGroupID) {
1004 getGroupInfo(grp, out);
1005 return LVGroupID;
1006 }
1007 }
1008 return -1;
1009 }
1010 LRESULT OnSetGroupInfo(UINT, WPARAM wp, LPARAM lp) {
1011 int LVGroupID = (int)wp;
1012 auto in = (LVGROUP*)lp;
1013 int ret = -1;
1014 for (auto& grp : m_groups) {
1015 if (grp.LVGroupID == LVGroupID) {
1016 setGroupInfo(grp, in);
1017 ret = grp.LVGroupID;
1018 }
1019 }
1020 return ret;
1021 }
1022 LRESULT OnGetGroupInfoByIndex(UINT, WPARAM wp, LPARAM lp) {
1023 size_t idx = (size_t)wp;
1024 LVGROUP* out = (LVGROUP*)lp;
1025 if (idx < m_groups.size()) {
1026 getGroupInfo(m_groups[idx], out);
1027 }
1028 return FALSE;
1029 }
1030 LRESULT OnRemoveAllGroups(UINT, WPARAM, LPARAM) {
1031 m_groups.clear(); return 0;
1032 }
1033 LRESULT OnDeleteItem(UINT, WPARAM wp, LPARAM) {
1034 size_t idx = (size_t)wp;
1035 LRESULT rv = FALSE;
1036 const size_t oldCount = m_content.size();
1037 if (idx < oldCount) {
1038 m_content.erase(m_content.begin() + idx);
1039 this->OnItemsRemoved(pfc::bit_array_one(idx), oldCount);
1040 rv = TRUE;
1041 }
1042 return rv;
1043 }
1044 LRESULT OnDeleteAllItems(UINT, WPARAM, LPARAM) {
1045 m_content.clear(); ReloadData();
1046 return TRUE;
1047 }
1048 int fillText(size_t item, size_t subItem, LVITEM* pItem) const {
1049 pfc::wstringLite text;
1050 if (item < m_content.size()) {
1051 auto& r = m_content[item];
1052 if (subItem < r.text.size()) {
1053 auto& t = r.text[subItem];
1054 if (!t.callback) {
1055 text = pfc::wideFromUTF8(t.text);
1056 }
1057 }
1058 }
1059 return safeFillText(pItem->pszText, pItem->cchTextMax, text);
1060 }
1061 LRESULT OnGetItem(UINT, WPARAM, LPARAM lp) {
1062 auto pItem = reinterpret_cast<LVITEM*>(lp);
1063 if (pItem->mask & LVIF_TEXT) {
1064 fillText(pItem->iItem, pItem->iSubItem, pItem);
1065 }
1066 if (pItem->mask & LVIF_IMAGE) {
1067 size_t idx = pItem->iItem;
1068 if (idx < m_content.size()) {
1069 auto& line = m_content[idx];
1070 pItem->iImage = line.image;
1071 }
1072 }
1073 if (pItem->mask & LVIF_PARAM) {
1074 pItem->lParam = GetItemParam(pItem->iItem);
1075 }
1076
1077 return TRUE;
1078 }
1079 LRESULT OnGetItemText(UINT, WPARAM wp, LPARAM lp) {
1080 auto item = (size_t)wp;
1081 auto pItem = reinterpret_cast<LVITEM*>(lp);
1082 size_t subItem = (size_t)pItem->iSubItem;
1083 return fillText(item, subItem, pItem);
1084 }
1085
1086 bool GetCellCheckState(size_t item, size_t sub) const override {
1087 bool rv = false;
1088 if (sub == 0 && item < m_content.size()) {
1089 rv = m_content[item].checked;
1090 }
1091 return rv;
1092 }
1093 void SetCellCheckState(size_t item, size_t sub, bool value) override {
1094 if (sub == 0 && item < m_content.size()) {
1095 auto& rec = m_content[item];
1096 if (rec.checked != value) {
1097 auto oldState = this->GetItemState(item);
1098 rec.checked = value;
1099 __super::SetCellCheckState(item, sub, value);
1100
1101 NMLISTVIEW info = { this->setupHdr(LVN_ITEMCHANGED) };
1102 info.iItem = (int)item;
1103 info.lParam = this->GetItemParam(item);
1104 info.uChanged = LVIF_STATE;
1105 info.uOldState = oldState;
1106 info.uNewState = this->GetItemState(item);
1107 this->sendNotify(&info);
1108 }
1109 }
1110 }
1111 UINT GetItemState(size_t idx) const override {
1112 UINT ret = __super::GetItemState(idx);
1113 if (useCheckBoxes() && idx < m_content.size()) {
1114 int nCheck = m_content[idx].checked ? 2 : 1;
1115 ret |= INDEXTOSTATEIMAGEMASK(nCheck);
1116 }
1117 return ret;
1118 }
1119 void SetItemState(size_t idx, DWORD mask, DWORD state) override {
1120 __super::SetItemState(idx, mask, state);
1121 if (useCheckBoxes() && (mask & LVIS_STATEIMAGEMASK) != 0) {
1122 int nCheck = ((state & LVIS_STATEIMAGEMASK) >> 12);
1123 this->SetCellCheckState(idx, 0, nCheck == 2);
1124 }
1125 }
1126 groupID_t GetItemGroup(t_size p_item) const override {
1127 if (m_groupViewEnabled && p_item < m_content.size()) {
1128 return m_content[p_item].groupID;
1129 }
1130 return 0;
1131 }
1132 bool GetGroupHeaderText2(size_t baseItem, pfc::string_base& out) const override {
1133 auto id = GetItemGroup(baseItem);
1134 if (id != 0) {
1135 for (auto& g : m_groups) {
1136 if (id == g.GroupID) {
1137 out = g.header;
1138 return true;
1139 }
1140 }
1141 }
1142 return false;
1143 }
1144 };
1145 }
1146
1147 HWND CListControl_ReplaceListView(HWND wndReplace) {
1148 CListViewCtrl src(wndReplace);
1149 const auto style = src.GetStyle();
1150 HWND ret = NULL;
1151 if (style & LVS_REPORT) {
1152 const auto ctrlID = src.GetDlgCtrlID();
1153 CWindow parent = src.GetParent();
1154 DWORD headerStyle = 0;
1155 if ((style & LVS_NOCOLUMNHEADER) == 0) {
1156 auto header = src.GetHeader();
1157 if (header) {
1158 headerStyle = header.GetStyle();
1159 }
1160 }
1161 if (style & LVS_OWNERDATA) {
1162 auto obj = PP::newWindowObj<CListControl_ListViewOwnerData>(style);
1163 ret = obj->CreateInDialog(parent, ctrlID, src);
1164 PFC_ASSERT(ret != NULL);
1165 if (headerStyle != 0 && obj->GetHeaderCtrl() == NULL) {
1166 obj->InitializeHeaderCtrl((headerStyle&(HDS_FULLDRAG | HDS_BUTTONS)));
1167 }
1168 } else {
1169 PFC_ASSERT(src.GetItemCount() == 0); // transferring of items not yet implemented
1170 auto obj = PP::newWindowObj<CListControl_ListView>(style);
1171 ret = obj->CreateInDialog(parent, ctrlID, src);
1172 PFC_ASSERT(ret != NULL);
1173 if (headerStyle != 0 && obj->GetHeaderCtrl() == NULL) {
1174 obj->InitializeHeaderCtrl((headerStyle & (HDS_FULLDRAG | HDS_BUTTONS)));
1175 }
1176 }
1177 }
1178 return ret;
1179 }
1180
1181 namespace {
1182 // FIX ME WM_DELETEITEM
1183
1184 class CListControl_ListBoxBase : public CListControlReadOnly {
1185 protected:
1186 const DWORD m_style;
1187 public:
1188 CListControl_ListBoxBase(DWORD style) : m_style(style) {
1189
1190 // owner data not implemented
1191 PFC_ASSERT((m_style & LBS_NODATA) == 0);
1192
1193 if (m_style & LBS_NOSEL) {
1194 this->SetSelectionModeNone();
1195 } else if (m_style & LBS_MULTIPLESEL) {
1196 this->SetSelectionModeMulti();
1197 } else {
1198 this->SetSelectionModeSingle();
1199 }
1200 }
1201
1202 BEGIN_MSG_MAP_EX(CListControl_ListBoxBase)
1203 MSG_WM_SETFOCUS(OnSetFocus)
1204 MSG_WM_KILLFOCUS(OnKillFocus)
1205 MESSAGE_HANDLER_EX(LB_SETCURSEL, OnSetCurSel)
1206 MESSAGE_HANDLER_EX(LB_GETCURSEL, OnGetCurSel)
1207 MESSAGE_HANDLER_EX(LB_GETITEMRECT, OnGetItemRect)
1208 MESSAGE_HANDLER_EX(LB_SELECTSTRING, OnSelectString)
1209 MESSAGE_HANDLER_EX(LB_ITEMFROMPOINT, OnItemFromPoint)
1210 MESSAGE_HANDLER_EX(LB_GETSEL, OnGetSel)
1211 MESSAGE_HANDLER_EX(LB_SETSEL, OnSetSel)
1212 MESSAGE_HANDLER_EX(LB_GETSELCOUNT, OnGetSelCount)
1213 MESSAGE_HANDLER_EX(LB_GETSELITEMS, OnGetSelItems)
1214 MESSAGE_HANDLER_EX(LB_SETCARETINDEX, OnSetCaretIndex)
1215 MESSAGE_HANDLER_EX(LB_GETCARETINDEX, OnGetCaretIndex)
1216 MSG_WM_CREATE(OnCreate)
1217 CHAIN_MSG_MAP(CListControlReadOnly)
1218 END_MSG_MAP()
1219
1220 LRESULT OnCreate(LPCREATESTRUCT) {
1221 SetMsgHandled(FALSE);
1222 // Adopt style flags of the original control to keep various ATL checks happy
1223 DWORD style = GetStyle();
1224 style = (style & 0xFFFF0000) | (m_style & 0xFFFF);
1225 SetWindowLong(GWL_STYLE, style);
1226 return 0;
1227 }
1228 void notifyParent(WORD code) {
1229 if (m_style & LBS_NOTIFY) {
1230 GetParent().PostMessage(WM_COMMAND, MAKEWPARAM(GetDlgCtrlID(), code), (LPARAM)m_hWnd);
1231 }
1232 }
1233 LRESULT OnGetCurSel(UINT, WPARAM, LPARAM) {
1234 return (LRESULT)this->GetSingleSel();
1235 }
1236 LRESULT OnSetCurSel(UINT, WPARAM wp, LPARAM) {
1237 this->SelectSingle((size_t)wp);
1238 return LB_OKAY;
1239 }
1240 LRESULT OnGetItemRect(UINT, WPARAM wp, LPARAM lp) {
1241 size_t idx = (size_t)wp;
1242 if (idx < this->GetItemCount()) {
1243 *reinterpret_cast<RECT*>(lp) = this->GetItemRect(idx);
1244 return LB_OKAY;
1245 } else {
1246 return LB_ERR;
1247 }
1248 }
1249 LRESULT OnSelectString(UINT, WPARAM wp, LPARAM lp) {
1250 auto idx = this->SendMessage(LB_FINDSTRING, wp, lp);
1251 if (idx != LB_ERR) this->SelectSingle((size_t)idx);
1252 return idx;
1253 }
1254 LRESULT OnItemFromPoint(UINT, WPARAM, LPARAM lp) {
1255 CPoint pt(lp);
1256 size_t ret;
1257 if (!this->ItemFromPoint(pt, ret)) return LB_ERR;
1258 return (LRESULT)ret;
1259 }
1260 LRESULT OnGetSel(UINT, WPARAM wp, LPARAM) {
1261 size_t idx = (size_t)wp;
1262 if (idx < this->GetItemCount()) {
1263 return this->IsItemSelected(idx) ? 1 : 0;
1264 } else {
1265 return LB_ERR;
1266 }
1267
1268 }
1269 LRESULT OnSetSel(UINT, WPARAM wp, LPARAM lp) {
1270 // Contrary to the other methods, index comes in LPARAM
1271 if (lp < 0) {
1272 if (wp) this->SelectAll();
1273 else this->SelectNone();
1274 } else {
1275 this->SetSelection(pfc::bit_array_one((size_t)lp), pfc::bit_array_val(wp != 0));
1276 }
1277 return LB_OKAY;
1278 }
1279 LRESULT OnGetSelItems(UINT, WPARAM wp, LPARAM lp) {
1280 int* out = reinterpret_cast<int*>(lp);
1281 size_t outSize = (size_t)wp;
1282 size_t outWalk = 0;
1283 const size_t total = this->GetItemCount();
1284 for (size_t walk = 0; walk < total && outWalk < outSize; ++walk) {
1285 if (this->IsItemSelected(walk)) {
1286 out[outWalk++] = (int)walk;
1287 }
1288 }
1289 return outWalk;
1290 }
1291 LRESULT OnGetSelCount(UINT, WPARAM, LPARAM) {
1292 return (LRESULT)this->GetSelectedCount();
1293 }
1294 LRESULT OnGetCaretIndex(UINT, WPARAM, LPARAM) {
1295 size_t focus = this->GetFocusItem();
1296 if (focus == SIZE_MAX) return LB_ERR;
1297 return (LRESULT)focus;
1298 }
1299 LRESULT OnSetCaretIndex(UINT, WPARAM wp, LPARAM) {
1300 size_t idx = (size_t)wp;
1301 if (idx < this->GetItemCount()) {
1302 this->SetFocusItem(idx); return LB_OKAY;
1303 } else {
1304 return LB_ERR;
1305 }
1306 }
1307
1308 void OnSetFocus(HWND) {
1309 notifyParent(LBN_SETFOCUS);
1310 SetMsgHandled(FALSE);
1311 }
1312 void OnKillFocus(HWND) {
1313 notifyParent(LBN_KILLFOCUS);
1314 SetMsgHandled(FALSE);
1315 }
1316
1317 void OnSelectionChanged(pfc::bit_array const& affected, pfc::bit_array const& status) override {
1318 __super::OnSelectionChanged(affected, status);
1319 notifyParent(LBN_SELCHANGE);
1320 }
1321 void ExecuteDefaultAction(size_t idx) override {
1322 notifyParent(LBN_DBLCLK);
1323 }
1324
1325 void RequestReorder(size_t const* order, size_t count) override {}
1326 void RequestRemoveSelection() override {}
1327 };
1328 class CListControl_ListBox : public CListControl_ListBoxBase {
1329 public:
1330 CListControl_ListBox(DWORD style) : CListControl_ListBoxBase(style) {}
1331
1332 BEGIN_MSG_MAP_EX(CListControl_ListBox)
1333 MESSAGE_HANDLER_EX(LB_INSERTSTRING, OnInsertString)
1334 MESSAGE_HANDLER_EX(LB_ADDSTRING, OnAddString)
1335 MESSAGE_HANDLER_EX(LB_RESETCONTENT, OnResetContent)
1336 MESSAGE_HANDLER_EX(LB_DELETESTRING, OnDeleteString)
1337 MESSAGE_HANDLER_EX(LB_SETITEMDATA, OnSetItemData)
1338 MESSAGE_HANDLER_EX(LB_GETITEMDATA, OnGetItemData)
1339 MESSAGE_HANDLER_EX(LB_GETCOUNT, OnGetCount)
1340 MESSAGE_HANDLER_EX(LB_FINDSTRING, OnFindString)
1341 MESSAGE_HANDLER_EX(LB_FINDSTRINGEXACT, OnFindStringExact)
1342 MESSAGE_HANDLER_EX(LB_SETCOUNT, OnSetCount)
1343 MESSAGE_HANDLER_EX(LB_INITSTORAGE, OnInitStorage)
1344 MESSAGE_HANDLER_EX(LB_GETTEXT, OnGetText)
1345 MESSAGE_HANDLER_EX(LB_GETTEXTLEN, OnGetTextLen)
1346 CHAIN_MSG_MAP(CListControl_ListBoxBase)
1347 END_MSG_MAP()
1348
1349 struct rec_t {
1350 pfc::string8 text;
1351 LPARAM data = 0;
1352 };
1353
1354 std::vector<rec_t> m_content;
1355 t_size GetItemCount() const override {
1356 return m_content.size();
1357 }
1358
1359 bool isForceSorted() const {
1360 return (m_style & LBS_SORT) != 0;
1361 }
1362 size_t AddStringSorted(pfc::string8 && str) {
1363 size_t ret = 0;
1364 // FIX ME bsearch??
1365 // even with bsearch it's still O(n) so it's pointless
1366 while (ret < m_content.size() && pfc::stricmp_ascii(str, m_content[ret].text) > 0) ++ret;
1367
1368 m_content.insert(m_content.begin() + ret, { std::move(str) });
1369
1370 this->OnItemsInserted(ret, 1, false);
1371 return ret;
1372 }
1373 static pfc::string8 importString(LPARAM lp) {
1374 return pfc::utf8FromWide(reinterpret_cast<const wchar_t*>(lp));
1375 }
1376 LRESULT OnInsertString(UINT, WPARAM wp, LPARAM lp) {
1377 auto str = importString(lp);
1378 if (isForceSorted()) return AddStringSorted(std::move(str));
1379 size_t at = wp;
1380 if (at > m_content.size()) at = m_content.size();
1381 m_content.insert(m_content.begin() + at, { std::move(str) });
1382 this->OnItemsInserted(at, 1, false);
1383 return at;
1384 }
1385 LRESULT OnAddString(UINT, WPARAM wp, LPARAM lp) {
1386 auto str = importString(lp);
1387 if (isForceSorted()) return AddStringSorted(std::move(str));
1388 size_t ret = m_content.size();
1389 m_content.push_back({ std::move(str) });
1390 this->OnItemsInserted(ret, 1, false);
1391 return ret;
1392 }
1393 LRESULT OnResetContent(UINT, WPARAM, LPARAM) {
1394 size_t oldCount = m_content.size();
1395 if (oldCount > 0) {
1396 m_content.clear();
1397 this->OnItemsRemoved(pfc::bit_array_true(), oldCount);
1398 }
1399 return LB_OKAY;
1400 }
1401 LRESULT OnDeleteString(UINT, WPARAM wp, LPARAM) {
1402 size_t idx = (size_t) wp;
1403 if (idx < m_content.size()) {
1404 m_content.erase(m_content.begin() + idx);
1405 this->OnItemRemoved(idx);
1406 return m_content.size();
1407 } else {
1408 return LB_ERR;
1409 }
1410 }
1411
1412 LRESULT OnSetItemData(UINT, WPARAM wp, LPARAM lp) {
1413 size_t idx = (size_t)wp;
1414 if (idx < m_content.size()) {
1415 m_content[idx].data = (size_t)lp;
1416 return LB_OKAY;
1417 } else {
1418 return LB_ERR;
1419 }
1420 }
1421 LRESULT OnGetItemData(UINT, WPARAM wp, LPARAM lp) {
1422 size_t idx = (size_t)wp;
1423 if (idx < m_content.size()) {
1424 return (LRESULT)m_content[idx].data;
1425 } else {
1426 return LB_ERR;
1427 }
1428 }
1429 LRESULT OnGetCount(UINT, WPARAM, LPARAM) {
1430 return (LRESULT)m_content.size();
1431 }
1432 LRESULT OnFindString(UINT, WPARAM wp, LPARAM lp) {
1433 auto str = importString(lp);
1434 const auto total = m_content.size();
1435 size_t first = (size_t)(wp + 1) % total;
1436 for (size_t walk = 0; walk < total; ++walk) {
1437 size_t at = (first + walk) % total;
1438 if (pfc::string_has_prefix_i(m_content[at].text, str)) return (LRESULT)at;
1439 }
1440 return LB_ERR;
1441 }
1442 LRESULT OnFindStringExact(UINT, WPARAM wp, LPARAM lp) {
1443 auto str = importString(lp);
1444 const auto total = m_content.size();
1445 size_t first = (size_t)(wp + 1) % total;
1446 for (size_t walk = 0; walk < total; ++walk) {
1447 size_t at = (first + walk) % total;
1448 if (pfc::stringEqualsI_utf8(m_content[at].text, str)) return (LRESULT)at;
1449 }
1450 return LB_ERR;
1451 }
1452
1453 LRESULT OnSetCount(UINT, WPARAM wp, LPARAM) {
1454 m_content.resize((size_t)wp);
1455 ReloadData();
1456 return LB_OKAY;
1457 }
1458 LRESULT OnInitStorage(UINT, WPARAM wp, LPARAM) {
1459 m_content.reserve(m_content.size() + (size_t)wp);
1460 return LB_OKAY;
1461 }
1462
1463 LRESULT OnGetText(UINT, WPARAM wp, LPARAM lp) {
1464 size_t idx = (size_t)wp;
1465 if (idx < m_content.size()) {
1466 auto out = reinterpret_cast<wchar_t*>(lp);
1467 wcscpy(out, pfc::wideFromUTF8(m_content[idx].text.c_str()).c_str());
1468 return LB_OKAY;
1469 } else {
1470 return LB_ERR;
1471 }
1472 }
1473 LRESULT OnGetTextLen(UINT, WPARAM wp, LPARAM) {
1474 size_t idx = (size_t)wp;
1475 if (idx < m_content.size()) {
1476 return pfc::wideFromUTF8(m_content[idx].text.c_str()).length();
1477 } else {
1478 return LB_ERR;
1479 }
1480 }
1481
1482 bool GetSubItemText(size_t item, size_t subItem, pfc::string_base& out) const override {
1483 PFC_ASSERT(subItem == 0); (void)subItem;
1484 PFC_ASSERT(item < m_content.size());
1485 out = m_content[item].text;
1486 return true;
1487 }
1488 };
1489 }
1490
1491 HWND CListControl_ReplaceListBox(HWND wndReplace) {
1492 CListBox src(wndReplace);
1493 const auto style = src.GetStyle();
1494 HWND ret = NULL;
1495 if ((style & LBS_NODATA) == 0) {
1496 PFC_ASSERT(src.GetCount() == 0); // transferring of items not yet implemented
1497 const auto ctrlID = src.GetDlgCtrlID();
1498 CWindow parent = src.GetParent();
1499 auto obj = PP::newWindowObj<CListControl_ListBox>(style);
1500 ret = obj->CreateInDialog(parent, ctrlID, src);
1501 }
1502 return ret;
1503 }