Mercurial > foo_out_sdl
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 } |
