|
1
|
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 }
|