comparison foosdk/sdk/libPPUI/CListControlWithSelection.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 <vsstyle.h>
3 #include <memory>
4 #include "CListControlWithSelection.h"
5 #include "PaintUtils.h"
6 #include "IDataObjectUtils.h"
7 #include "SmartStrStr.h"
8 #include "CListControl-Cells.h"
9
10 namespace {
11 class bit_array_selection_CListControl : public pfc::bit_array {
12 public:
13 bit_array_selection_CListControl(CListControlWithSelectionBase const & list) : m_list(list) {}
14 bool get(t_size n) const {return m_list.IsItemSelected(n);}
15 private:
16 CListControlWithSelectionBase const & m_list;
17 };
18 }
19
20 bool CListControlWithSelectionBase::SelectAll() {
21 SetSelection(pfc::bit_array_true(), pfc::bit_array_true() );
22 return true;
23 }
24 void CListControlWithSelectionBase::SelectNone() {
25 SetSelection(pfc::bit_array_true(), pfc::bit_array_false() );
26 }
27 void CListControlWithSelectionBase::SetSelectionAt(size_t idx, bool bSel) {
28 if (bSel == this->IsItemSelected(idx)) return;
29 this->SetSelection(pfc::bit_array_one(idx), pfc::bit_array_val(bSel));
30 }
31 void CListControlWithSelectionBase::SelectSingle(size_t which) {
32 this->SetFocusItem( which );
33 SetSelection(pfc::bit_array_true(), pfc::bit_array_one( which ) );
34 }
35
36 LRESULT CListControlWithSelectionBase::OnFocus(UINT,WPARAM,LPARAM,BOOL& bHandled) {
37 UpdateItems(pfc::bit_array_or(bit_array_selection_CListControl(*this), pfc::bit_array_one(GetFocusItem())));
38 bHandled = FALSE;
39 return 0;
40 }
41
42 void CListControlWithSelectionBase::OnKeyDown_SetIndexDeltaPageHelper(int p_delta, int p_keys) {
43 const CRect rcClient = GetClientRectHook();
44 int itemHeight = GetItemHeight();
45 if (itemHeight > 0) {
46 int delta = rcClient.Height() / itemHeight;
47 if (delta < 1) delta = 1;
48 OnKeyDown_SetIndexDeltaHelper(delta * p_delta,p_keys);
49 }
50 }
51
52 void CListControlWithSelectionBase::OnKeyDown_HomeEndHelper(bool isEnd, int p_keys) {
53 if (!isEnd) {
54 //SPECIAL FIX - ensure first group header visibility.
55 MoveViewOrigin(CPoint(GetViewOrigin().x,0));
56 }
57
58 const t_size itemCount = GetItemCount();
59 if (itemCount == 0) return;//don't bother
60
61 if ((p_keys & (MK_SHIFT|MK_CONTROL)) == (MK_SHIFT|MK_CONTROL)) {
62 RequestMoveSelection( isEnd ? (int)itemCount : -(int)itemCount );
63 } else {
64 OnKeyDown_SetIndexHelper(isEnd ? (int)(itemCount-1) : 0,p_keys);
65 }
66 }
67 void CListControlWithSelectionBase::OnKeyDown_SetIndexHelper(int p_index, int p_keys) {
68 const t_size count = GetItemCount();
69 if (count > 0) {
70 t_size idx = (t_size)pfc::clip_t(p_index,0,(int)(count - 1));
71 const t_size oldFocus = GetFocusItem();
72 if (p_keys & MK_CONTROL) {
73 SetFocusItem(idx);
74 } else if ((p_keys & MK_SHIFT) != 0 && this->AllowRangeSelect() ) {
75 t_size selStart = GetSelectionStart();
76 if (selStart == pfc_infinite) selStart = oldFocus;
77 if (selStart == pfc_infinite) selStart = idx;
78 t_size selFirst, selCount;
79 selFirst = pfc::min_t(selStart,idx);
80 selCount = pfc::max_t(selStart,idx) + 1 - selFirst;
81 SetSelection(pfc::bit_array_true(), pfc::bit_array_range(selFirst,selCount));
82 SetFocusItem(idx);
83 SetSelectionStart(selStart);
84 } else {
85 SetFocusItem(idx);
86 SetSelection(pfc::bit_array_true(), pfc::bit_array_one(idx));
87 }
88 }
89 }
90
91 void CListControlWithSelectionBase::SelectGroupHelper2(size_t base,int p_keys) {
92 size_t count = this->ResolveGroupRange2(base);
93 if ( count > 0 ) {
94 if (p_keys & MK_CONTROL) {
95 SetGroupFocusByItem(base);
96 } /*else if (p_keys & MK_SHIFT) {
97 } */else {
98 SetGroupFocusByItem(base);
99 SetSelection(pfc::bit_array_true(), pfc::bit_array_range(base,count));
100 }
101 }
102 }
103
104 void CListControlWithSelectionBase::OnKeyDown_SetIndexDeltaLineHelper(int p_delta, int p_keys) {
105 if ((p_keys & (MK_SHIFT | MK_CONTROL)) == (MK_SHIFT|MK_CONTROL)) {
106 this->RequestMoveSelection(p_delta);
107 return;
108 }
109 const t_size total = GetItemCount();
110 t_size current = GetFocusItem();
111 const size_t focusGroupItem = this->GetGroupFocus2();
112 if (focusGroupItem != SIZE_MAX) current = focusGroupItem;
113
114 if (current == pfc_infinite) {
115 OnKeyDown_SetIndexDeltaHelper(p_delta,p_keys);
116 return;
117 }
118
119 const groupID_t currentGroup = GetItemGroup(current);
120 if (GroupFocusActive()) {
121 if (p_delta < 0 && focusGroupItem > 0) {
122 OnKeyDown_SetIndexHelper((int)(focusGroupItem-1), p_keys);
123 } else if (p_delta > 0) {
124 OnKeyDown_SetIndexHelper((int) current, p_keys);
125 }
126 } else {
127 if ((p_keys & MK_SHIFT) != 0) {
128 OnKeyDown_SetIndexDeltaHelper(p_delta,p_keys);
129 } else if (p_delta < 0) {
130 if (currentGroup == 0 || (current > 0 && currentGroup == GetItemGroup(current - 1))) {
131 OnKeyDown_SetIndexDeltaHelper(p_delta,p_keys);
132 } else {
133 SelectGroupHelper2(current, p_keys);
134 }
135 } else if (p_delta > 0) {
136 if (current + 1 >= total || currentGroup == GetItemGroup(current + 1)) {
137 OnKeyDown_SetIndexDeltaHelper(p_delta,p_keys);
138 } else {
139 SelectGroupHelper2(current + 1, p_keys);
140 }
141 }
142 }
143 }
144
145 LRESULT CListControlWithSelectionBase::OnLButtonDblClk(UINT,WPARAM p_wp,LPARAM p_lp,BOOL& bHandled) {
146 CPoint pt(p_lp);
147 if (OnClickedSpecialHitTest(pt)) {
148 return OnButtonDown(WM_LBUTTONDOWN, p_wp, p_lp, bHandled);
149 }
150 t_size item;
151 if (ItemFromPoint(pt,item)) {
152 ExecuteDefaultAction(item);
153 return 0;
154 } else if (GroupHeaderFromPoint2(pt,item)) {
155 t_size count = this->ResolveGroupRange2(item);
156 if (count > 0) ExecuteDefaultActionGroup(item, count);
157 return 0;
158 } else if (ExecuteCanvasDefaultAction(pt)) {
159 return 0;
160 } else {
161 return OnButtonDown(WM_LBUTTONDOWN,p_wp,p_lp,bHandled);
162 }
163 }
164 void CListControlWithSelectionBase::ExecuteDefaultActionByFocus() {
165 size_t groupFocus = this->GetGroupFocus2();
166 if ( groupFocus != SIZE_MAX ) {
167 size_t count = this->ResolveGroupRange2(groupFocus);
168 if (count > 0) {
169 ExecuteDefaultActionGroup(groupFocus, count);
170 }
171 } else {
172 size_t index = this->GetFocusItem();
173 if (index != SIZE_MAX) this->ExecuteDefaultAction(index);
174 }
175 }
176
177 t_size CListControlWithSelectionBase::GetSingleSel() const {
178 t_size total = GetItemCount();
179 t_size first = SIZE_MAX;
180 for(t_size walk = 0; walk < total; ++walk) {
181 if (IsItemSelected(walk)) {
182 if (first == SIZE_MAX) first = walk;
183 else return SIZE_MAX;
184 }
185 }
186 return first;
187 }
188
189 t_size CListControlWithSelectionBase::GetSelectedCount(pfc::bit_array const & mask,t_size max) const {
190 const t_size itemCount = this->GetItemCount();
191 t_size found = 0;
192 for(t_size walk = mask.find_first(true,0,itemCount); walk < itemCount && found < max; walk = mask.find_next(true,walk,itemCount)) {
193 if (IsItemSelected(walk)) ++found;
194 }
195 return found;
196 }
197 LRESULT CListControlWithSelectionBase::OnButtonDown(UINT p_msg,WPARAM p_wp,LPARAM p_lp,BOOL&) {
198 pfc::vartoggle_t<bool> l_noEnsureVisible(m_noEnsureVisible,true);
199 if (m_selectDragMode) {
200 AbortSelectDragMode();
201 return 0;
202 }
203
204 CPoint pt(p_lp);
205
206 if (OnClickedSpecial( (DWORD) p_wp, pt)) {
207 return 0;
208 }
209
210
211 size_t item;
212 const bool isRightClick = (p_msg == WM_RBUTTONDOWN || p_msg == WM_RBUTTONDBLCLK);
213 const bool gotCtrl = (p_wp & MK_CONTROL) != 0 && !isRightClick;
214 const bool gotShift = (p_wp & MK_SHIFT) != 0 && !isRightClick;
215
216
217
218 const bool bCanSelect = !OnClickedSpecialHitTest( pt );
219
220 const bool instaDrag = false;
221 const bool ddSupported = IsDragDropSupported();
222
223 if (GroupHeaderFromPoint2(pt, item)) {
224 t_size base = item,count = ResolveGroupRange2(base);
225 if (AllowRangeSelect() && count > 0) {
226 SetGroupFocusByItem(base);
227 pfc::bit_array_range groupRange(base,count);
228 bool instaDragOverride = false;
229 if (gotCtrl) {
230 ToggleRangeSelection(groupRange);
231 } else if (gotShift) {
232 SetSelection(groupRange, pfc::bit_array_true());
233 } else {
234 if (GetSelectedCount(groupRange) == count) instaDragOverride = true;
235 else SetSelection(pfc::bit_array_true(),groupRange);
236 }
237 if (ddSupported && (instaDrag || instaDragOverride)) {
238 PrepareDragDrop(pt,isRightClick);
239 } else {
240 InitSelectDragMode(pt, isRightClick);
241 }
242 }
243 } else if (ItemFromPoint(pt,item)) {
244 const t_size oldFocus = GetFocusItem();
245 const t_size selStartBefore = GetSelectionStart();
246 if ( bCanSelect ) SetFocusItem(item);
247 if (gotShift && AllowRangeSelect() ) {
248 if (bCanSelect) {
249 t_size selStart = selStartBefore;
250 if (selStart == pfc_infinite) selStart = oldFocus;
251 if (selStart == pfc_infinite) selStart = item;
252 SetSelectionStart(selStart);
253 t_size selFirst, selCount;
254 selFirst = pfc::min_t(selStart, item);
255 selCount = pfc::max_t(selStart, item) + 1 - selFirst;
256 pfc::bit_array_range rangeMask(selFirst, selCount);
257 pfc::bit_array_true trueMask;
258 SetSelection(gotCtrl ? pfc::implicit_cast<const pfc::bit_array&>(rangeMask) : pfc::implicit_cast<const pfc::bit_array&>(trueMask), rangeMask);
259 //if (!instaDrag) InitSelectDragMode(pt, isRightClick);
260 }
261 } else {
262 if (gotCtrl) {
263 if (bCanSelect) SetSelection(pfc::bit_array_one(item), pfc::bit_array_val(!IsItemSelected(item)));
264 if (!instaDrag) InitSelectDragMode(pt, isRightClick);
265 } else {
266 if (!IsItemSelected(item)) {
267 if (bCanSelect) SetSelection(pfc::bit_array_true(), pfc::bit_array_one(item));
268 if (ddSupported && instaDrag) {
269 PrepareDragDrop(pt,isRightClick);
270 } else {
271 InitSelectDragMode(pt, isRightClick);
272 }
273 } else {
274 if (ddSupported) {
275 PrepareDragDrop(pt,isRightClick);
276 } else {
277 InitSelectDragMode(pt, isRightClick);
278 }
279 }
280 }
281 }
282 } else {
283 if (!gotShift && !gotCtrl && bCanSelect) SelectNone();
284 InitSelectDragMode(pt, isRightClick);
285 }
286 return 0;
287 }
288
289
290 void CListControlWithSelectionBase::ToggleRangeSelection(pfc::bit_array const & mask) {
291 SetSelection(mask, pfc::bit_array_val(GetSelectedCount(mask,1) == 0));
292 }
293 void CListControlWithSelectionBase::ToggleGroupSelection2(size_t base) {
294 size_t count = this->ResolveGroupRange2(base);
295 if (count > 0) {
296 ToggleRangeSelection(pfc::bit_array_range(base, count));
297 }
298 }
299
300 LRESULT CListControlWithSelectionBase::OnRButtonUp(UINT,WPARAM,LPARAM,BOOL& bHandled) {
301 bHandled = FALSE;
302 AbortPrepareDragDropMode();
303 AbortSelectDragMode();
304 return 0;
305 }
306
307 bool CListControlWithSelectionBase::ShouldBeginDrag(CPoint ptRef, CPoint ptNow) const {
308 auto threshold = PP::queryDragThresholdForDPI(this->GetDPI());
309 return abs(ptNow.x - ptRef.x) > threshold.cx || abs(ptNow.y - ptRef.y) > threshold.cy;
310 }
311
312 LRESULT CListControlWithSelectionBase::OnMouseMove(UINT,WPARAM,LPARAM p_lp,BOOL&) {
313 if (m_prepareDragDropMode) {
314 if (ShouldBeginDrag(m_prepareDragDropOrigin, CPoint(p_lp))) {
315 AbortPrepareDragDropMode();
316 if (!m_ownDDActive) {
317 pfc::vartoggle_t<bool> ownDD(m_ownDDActive,true);
318 RunDragDrop( PointClientToAbs( m_prepareDragDropOrigin ),m_prepareDragDropModeRightClick);
319 }
320 }
321 } else if (m_selectDragMode) {
322 HandleDragSel(CPoint(p_lp));
323 }
324 return 0;
325 }
326 LRESULT CListControlWithSelectionBase::OnLButtonUp(UINT,WPARAM p_wp,LPARAM p_lp,BOOL&) {
327 const bool wasPreparingDD = m_prepareDragDropMode;
328 AbortPrepareDragDropMode();
329 CPoint pt(p_lp);
330 const bool gotCtrl = (p_wp & MK_CONTROL) != 0;
331 const bool gotShift = (p_wp & MK_SHIFT) != 0;
332 bool click = false;
333 bool processSel = wasPreparingDD;
334 if (m_selectDragMode) {
335 processSel = !m_selectDragMoved;
336 AbortSelectDragMode();
337 }
338 if (processSel) {
339 click = true;
340 if (!OnClickedSpecialHitTest(pt) ) {
341 size_t item;
342 if (GroupHeaderFromPoint2(pt, item)) {
343 t_size base = item, count = ResolveGroupRange2(base);
344 if ( count > 0 ) {
345 if (gotCtrl) {
346 } else {
347 SetSelection(pfc::bit_array_true(), pfc::bit_array_range(base, count));
348 }
349 }
350 } else if (ItemFromPoint(pt, item)) {
351 const t_size selStartBefore = GetSelectionStart();
352 if (gotCtrl) {
353 } else if (gotShift) {
354 SetSelectionStart(selStartBefore);
355 } else {
356 SetSelection(pfc::bit_array_true(), pfc::bit_array_one(item));
357 }
358 }
359 }
360 }
361 if (click && !gotCtrl && !gotShift) {
362 size_t item;
363 if (GroupHeaderFromPoint2(pt,item)) {
364 OnGroupHeaderClicked(GetItemGroup(item),pt);
365 } else if (ItemFromPoint(pt,item)) {
366 OnItemClicked(item,pt);
367 }
368 }
369 return 0;
370 }
371
372 void CListControlWithSelectionBase::OnKeyDown_SetIndexDeltaHelper(int p_delta, int p_keys) {
373 size_t focus = SIZE_MAX;
374 if (this->GroupFocusActive()) {
375 focus = this->GetGroupFocus2();
376 } else {
377 focus = GetFocusItem();
378 }
379 int target = 0;
380 if (focus != SIZE_MAX) target = (int) focus + p_delta;
381 OnKeyDown_SetIndexHelper(target,p_keys);
382 }
383
384
385 static int _get_keyflags() {
386 int ret = 0;
387 if (IsKeyPressed(VK_CONTROL)) ret |= MK_CONTROL;
388 if (IsKeyPressed(VK_SHIFT)) ret |= MK_SHIFT;
389 return ret;
390 }
391
392 LRESULT CListControlWithSelectionBase::OnKeyDown(UINT,WPARAM p_wp,LPARAM,BOOL& bHandled) {
393 switch(p_wp) {
394 case VK_NEXT:
395 OnKeyDown_SetIndexDeltaPageHelper(1,_get_keyflags());
396 return 0;
397 case VK_PRIOR:
398 OnKeyDown_SetIndexDeltaPageHelper(-1,_get_keyflags());
399 return 0;
400 case VK_DOWN:
401 OnKeyDown_SetIndexDeltaLineHelper(1,_get_keyflags());
402 return 0;
403 case VK_UP:
404 OnKeyDown_SetIndexDeltaLineHelper(-1,_get_keyflags());
405 return 0;
406 case VK_HOME:
407 OnKeyDown_HomeEndHelper(false,_get_keyflags());
408 return 0;
409 case VK_END:
410 OnKeyDown_HomeEndHelper(true,_get_keyflags());
411 return 0;
412 case VK_SPACE:
413 if (!TypeFindCheck()) {
414 ToggleSelectedItems();
415 }
416 return 0;
417 case VK_RETURN:
418 ExecuteDefaultActionByFocus();
419 return 0;
420 case VK_DELETE:
421 if (GetHotkeyModifierFlags() == 0) {
422 RequestRemoveSelection();
423 return 0;
424 }
425 break;
426 case 'A':
427 if (GetHotkeyModifierFlags() == MOD_CONTROL) {
428 if (SelectAll()) {
429 return 0;
430 }
431 // otherwise unhandled
432 }
433 break;
434 }
435
436 bHandled = FALSE;
437 return 0;
438 }
439
440 void CListControlWithSelectionBase::ToggleSelectedItems() {
441 if (ToggleSelectedItemsHook(bit_array_selection_CListControl(*this))) return;
442 if (GroupFocusActive()) {
443 ToggleGroupSelection2(this->GetGroupFocus2());
444 } else {
445 const t_size focus = GetFocusItem();
446 if (focus != pfc_infinite) {
447 ToggleRangeSelection(pfc::bit_array_range(focus, 1));
448 }
449 }
450 }
451
452 LRESULT CListControlWithSelectionBase::OnTimer(UINT,WPARAM p_wp,LPARAM,BOOL& bHandled) {
453 switch((DWORD)p_wp) {
454 case (DWORD)KSelectionTimerID:
455 if (m_selectDragMode) {
456 CPoint pt;
457 if (GetCursorPos(&pt) && ScreenToClient(&pt)) {
458 const CRect client = GetClientRectHook();
459 CPoint delta(0, 0);
460 if (pt.x < client.left) {
461 delta.x = pt.x - client.left;
462 } else if (pt.x > client.right) {
463 delta.x = pt.x - client.right;
464 }
465 if (pt.y < client.top) {
466 delta.y = pt.y - client.top;
467 } else if (pt.y > client.bottom) {
468 delta.y = pt.y - client.bottom;
469 }
470
471 MoveViewOriginDelta(delta);
472 HandleDragSel(pt);
473 }
474 }
475 return 0;
476 case TDDScrollControl::KTimerID:
477 HandleDDScroll();
478 return 0;
479 default:
480 bHandled = FALSE;
481 return 0;
482 }
483 }
484
485 bool CListControlWithSelectionBase::MoveSelectionProbe(int delta) {
486 pfc::array_t<size_t> order; order.set_size(GetItemCount());
487 {
488 bit_array_selection_CListControl sel(*this);
489 pfc::create_move_items_permutation(order.get_ptr(), order.get_size(), sel, delta);
490 }
491 for( size_t w = 0; w < order.get_size(); ++w ) if ( order[w] != w ) return true;
492 return false;
493 }
494 void CListControlWithSelectionBase::RequestMoveSelection(int delta) {
495 pfc::array_t<size_t> order; order.set_size(GetItemCount());
496 {
497 bit_array_selection_CListControl sel(*this);
498 pfc::create_move_items_permutation(order.get_ptr(), order.get_size(), sel, delta);
499 }
500
501 this->RequestReorder(order.get_ptr(), order.get_size());
502
503 if (delta < 0) {
504 size_t idx = GetFirstSelected();
505 if (idx != pfc_infinite) EnsureItemVisible(idx);
506 } else {
507 size_t idx = GetLastSelected();
508 if (idx != pfc_infinite) EnsureItemVisible(idx);
509 }
510 }
511
512 void CListControlWithSelectionBase::ToggleSelection(pfc::bit_array const & mask) {
513 const t_size count = GetItemCount();
514 pfc::bit_array_bittable table(count);
515 for(t_size walk = mask.find_first(true,0,count); walk < count; walk = mask.find_next(true,walk,count)) {
516 table.set(walk,!IsItemSelected(walk));
517 }
518 this->SetSelection(mask,table);
519 }
520
521 static HRGN FrameRectRgn(const CRect & rect) {
522 CRect exterior(rect); exterior.InflateRect(1,1);
523 CRgn rgn; rgn.CreateRectRgnIndirect(exterior);
524 CRect interior(rect); interior.DeflateRect(1,1);
525 if (!interior.IsRectEmpty()) {
526 CRgn rgn2; rgn2.CreateRectRgnIndirect(interior);
527 rgn.CombineRgn(rgn2,RGN_DIFF);
528 }
529 return rgn.Detach();
530 }
531
532 void CListControlWithSelectionBase::HandleDragSel(const CPoint & p_pt) {
533 const CPoint pt = PointClientToAbs(p_pt);
534 if (m_selectDragMoved || ShouldBeginDrag(m_selectDragCurrentAbs, pt)) {
535
536 if (!this->AllowRangeSelect()) {
537 // simplified
538 m_selectDragCurrentAbs = pt;
539 if (pt != m_selectDragOriginAbs) m_selectDragMoved = true;
540 return;
541 }
542
543 CRect rcOld(m_selectDragOriginAbs,m_selectDragCurrentAbs); rcOld.NormalizeRect();
544 m_selectDragCurrentAbs = pt;
545 CRect rcNew(m_selectDragOriginAbs,m_selectDragCurrentAbs); rcNew.NormalizeRect();
546
547
548 {
549 CRgn rgn = FrameRectRgn(rcNew);
550 CRgn rgn2 = FrameRectRgn(rcOld);
551 rgn.CombineRgn(rgn2,RGN_OR);
552 rgn.OffsetRgn( - GetViewOffset() );
553 InvalidateRgn(rgn);
554 }
555
556 if (pt != m_selectDragOriginAbs) m_selectDragMoved = true;
557
558 if (m_selectDragChanged || !IsSameItemOrHeaderAbs(pt,m_selectDragOriginAbs)) {
559 m_selectDragChanged = true;
560 const int keys = _get_keyflags();
561 t_size base,count, baseOld, countOld;
562 if (!GetItemRangeAbs(rcNew,base,count)) base = count = 0;
563 if (!GetItemRangeAbs(rcOld,baseOld,countOld)) baseOld = countOld = 0;
564 {
565 pfc::bit_array_range rangeNew(base,count), rangeOld(baseOld,countOld);
566 if (keys & MK_CONTROL) {
567 ToggleSelection(pfc::bit_array_xor(rangeNew,rangeOld));
568 } else if (keys & MK_SHIFT) {
569 SetSelection(pfc::bit_array_or(rangeNew,rangeOld),rangeNew);
570 } else {
571 SetSelection(pfc::bit_array_true(),rangeNew);
572 }
573 }
574 if (ItemFromPointAbs(pt,base)) {
575 const CRect rcVisible = GetVisibleRectAbs(), rcItem = GetItemRectAbs(base);
576 if (rcItem.top >= rcVisible.top && rcItem.bottom <= rcVisible.bottom) {
577 SetFocusItem(base);
578 }
579 } else if (GroupHeaderFromPointAbs2(pt,base)) {
580 const CRect rcVisible = GetVisibleRectAbs();
581 CRect rcGroup;
582 if (GetGroupHeaderRectAbs2(base,rcGroup)) {
583 if (rcGroup.top >= rcVisible.top && rcGroup.bottom <= rcVisible.bottom) {
584 this->SetGroupFocusByItem(base);
585 }
586 }
587 }
588 }
589 }
590 }
591
592 void CListControlWithSelectionBase::InitSelectDragMode(const CPoint & p_pt,bool p_rightClick) {
593 // Perform the bookkeeping even if multiple selection is disabled, detection of clicks relies on it
594 (void)p_rightClick;
595 SetTimer(KSelectionTimerID,KSelectionTimerPeriod);
596 m_selectDragMode = true;
597 m_selectDragOriginAbs = m_selectDragCurrentAbs = PointClientToAbs(p_pt);
598 m_selectDragChanged = false; m_selectDragMoved = false;
599 SetCapture();
600 }
601
602 void CListControlWithSelectionBase::AbortSelectDragMode(bool p_lostCapture) {
603 if (m_selectDragMode) {
604 m_selectDragMode = false;
605 CRect rcSelect(m_selectDragOriginAbs,m_selectDragCurrentAbs); rcSelect.NormalizeRect();
606 rcSelect.OffsetRect( - GetViewOffset() );
607 if (!p_lostCapture) ::SetCapture(NULL);
608 rcSelect.InflateRect(1,1);
609 InvalidateRect(rcSelect);
610 KillTimer(KSelectionTimerID);
611 }
612 }
613
614
615 LRESULT CListControlWithSelectionBase::OnCaptureChanged(UINT,WPARAM,LPARAM,BOOL&) {
616 AbortPrepareDragDropMode(true);
617 AbortSelectDragMode(true);
618 return 0;
619 }
620
621 void CListControlWithSelectionBase::RenderOverlay2(const CRect & p_updaterect,CDCHandle p_dc) {
622 if (m_selectDragMode && this->AllowRangeSelect() ) {
623 CRect rcSelect(m_selectDragOriginAbs,m_selectDragCurrentAbs);
624 rcSelect.NormalizeRect();
625 rcSelect.OffsetRect(-GetViewOffset());
626 PaintUtils::FocusRect(p_dc,rcSelect);
627 }
628 if (m_dropMark != SIZE_MAX) {
629 RenderDropMarkerClipped2(p_dc, p_updaterect, m_dropMark, m_dropMarkInside);
630 }
631 TParent::RenderOverlay2(p_updaterect,p_dc);
632 }
633
634 void CListControlWithSelectionBase::SetDropMark(size_t mark, bool inside) {
635 if (mark != m_dropMark || inside != m_dropMarkInside) {
636 CRgn updateRgn; updateRgn.CreateRectRgn(0, 0, 0, 0);
637 AddDropMarkToUpdateRgn(updateRgn, m_dropMark, m_dropMarkInside);
638 m_dropMark = mark;
639 m_dropMarkInside = inside;
640 AddDropMarkToUpdateRgn(updateRgn, m_dropMark, m_dropMarkInside);
641 RedrawWindow(NULL, updateRgn);
642 }
643 }
644
645 static int transformDDScroll(int p_value,int p_width, int p_dpi) {
646 if (p_dpi <= 0) p_dpi = 96;
647 const double dpiMul = 96.0 / (double) p_dpi;
648 double val = (double)(p_width - p_value);
649 val *= dpiMul;
650 val = pow(val,1.1) * 0.33;
651 val /= dpiMul;
652 return pfc::rint32(val);
653 }
654
655 void CListControlWithSelectionBase::HandleDDScroll() {
656 CPoint position;
657 if (m_ddScroll.m_timerActive && GetCursorPos(&position)) {
658 CRect client = GetClientRectHook();
659 CPoint delta (0,0);
660 if (ClientToScreen(client)) {
661 const CSize DPI = QueryScreenDPIEx();
662 const int scrollZoneWidthBase = GetItemHeight() * 2;
663 const int scrollZoneHeight = pfc::min_t<int>( scrollZoneWidthBase, client.Height() / 4 );
664 const int scrollZoneWidth = pfc::min_t<int>( scrollZoneWidthBase, client.Width() / 4 );
665
666 if (position.y >= client.top && position.y < client.top + scrollZoneHeight) {
667 delta.y -= transformDDScroll(position.y - client.top, scrollZoneHeight, DPI.cy);
668 } else if (position.y >= client.bottom - scrollZoneHeight && position.y < client.bottom) {
669 delta.y += transformDDScroll(client.bottom - position.y, scrollZoneHeight, DPI.cy);
670 }
671
672 if (position.x >= client.left && position.x < client.left + scrollZoneWidth) {
673 delta.x -= transformDDScroll(position.x - client.left, scrollZoneWidth, DPI.cx);
674 } else if (position.x >= client.right - scrollZoneWidth && position.x < client.right) {
675 delta.x += transformDDScroll(client.right - position.x, scrollZoneWidth, DPI.cx);
676 }
677 }
678
679 if (delta != CPoint(0,0)) MoveViewOriginDelta(delta);
680 }
681 }
682
683 void CListControlWithSelectionBase::ToggleDDScroll(bool p_state) {
684 if (p_state != m_ddScroll.m_timerActive) {
685 if (p_state) {
686 SetTimer(m_ddScroll.KTimerID,m_ddScroll.KTimerPeriod);
687 } else {
688 KillTimer(m_ddScroll.KTimerID);
689 }
690 m_ddScroll.m_timerActive = p_state;
691 }
692 }
693
694 void CListControlWithSelectionBase::PrepareDragDrop(const CPoint & p_point,bool p_isRightClick) {
695 m_prepareDragDropMode = true;
696 m_prepareDragDropOrigin = p_point;
697 m_prepareDragDropModeRightClick = p_isRightClick;
698 SetCapture();
699 }
700 void CListControlWithSelectionBase::AbortPrepareDragDropMode(bool p_lostCapture) {
701 if (m_prepareDragDropMode) {
702 m_prepareDragDropMode = false;
703 if (!p_lostCapture) ::SetCapture(NULL);
704 }
705 }
706
707
708 void CListControlWithSelectionBase::RenderItem(t_size p_item,const CRect & p_itemRect,const CRect & p_updateRect,CDCHandle p_dc) {
709 //console::formatter() << "RenderItem: " << p_item;
710 const bool weHaveFocus = ::GetFocus() == m_hWnd;
711 const bool isSelected = this->IsItemSelected(p_item);
712
713 const t_uint32 bkColor = GetSysColorHook(colorBackground);
714 const t_uint32 hlColor = GetSysColorHook(colorSelection);
715 const t_uint32 bkColorUsed = isSelected ? (weHaveFocus ? hlColor : PaintUtils::BlendColor(hlColor,bkColor)) : bkColor;
716
717 bool alternateTextColor = false, dtt = false;
718 auto & m_theme = theme();
719 CRect rcSelection = p_itemRect;
720 this->AdjustSelectionRect(p_item, rcSelection);
721 if (m_theme != NULL && isSelected && hlColor == GetSysColor(COLOR_HIGHLIGHT) && /*bkColor == GetSysColor(COLOR_WINDOW) && */ IsThemePartDefined(m_theme, LVP_LISTITEM, 0)) {
722 //PaintUtils::RenderItemBackground(p_dc,p_itemRect,p_item+GetItemGroup(p_item),bkColor);
723 DrawThemeBackground(m_theme, p_dc, LVP_LISTITEM, weHaveFocus ? LISS_SELECTED : LISS_SELECTEDNOTFOCUS, rcSelection, p_updateRect);
724 // drawthemetext is acting whacky with dark mode
725 if (!this->GetDarkMode()) dtt = true;
726 } else {
727 this->RenderItemBackground(p_dc, rcSelection, p_item, bkColorUsed );
728 // PaintUtils::RenderItemBackground(p_dc,p_itemRect,p_item+GetItemGroup(p_item),bkColorUsed);
729 if (isSelected) alternateTextColor = true;
730 }
731
732 {
733 DCStateScope backup(p_dc);
734 p_dc.SetBkMode(TRANSPARENT);
735 p_dc.SetBkColor(bkColorUsed);
736 p_dc.SetTextColor(alternateTextColor ? PaintUtils::DetermineTextColor(bkColorUsed) : this->GetSysColorHook(colorText));
737 pfc::vartoggle_t<bool> toggle(m_drawThemeText, dtt);
738 RenderItemText(p_item,p_itemRect,p_updateRect,p_dc, !alternateTextColor);
739 }
740
741 if (IsItemFocused(p_item) && weHaveFocus) {
742 PaintUtils::FocusRect2(p_dc,rcSelection, bkColorUsed);
743 }
744 }
745
746 void CListControlWithSelectionBase::RenderSubItemText(t_size item, t_size subItem,const CRect & subItemRect,const CRect & updateRect,CDCHandle dc, bool allowColors) {
747 #if 0
748 auto ct = GetCellType(item, subItem);
749 if ( ct == nullptr ) return;
750
751 if (m_drawThemeText && ct->AllowDrawThemeText() && !this->IsSubItemGrayed(item, subItem)) for(;;) {
752 pfc::string_formatter label;
753 if (!GetSubItemText(item,subItem,label)) return;
754 const bool weHaveFocus = ::GetFocus() == m_hWnd;
755 // const bool isSelected = this->IsItemSelected(item);
756 pfc::stringcvt::string_os_from_utf8 cvt(label);
757 if (PaintUtils::TextContainsCodes(cvt)) break;
758 CRect clip = GetItemTextRect(subItemRect);
759 const t_uint32 format = PaintUtils::DrawText_TranslateHeaderAlignment(GetColumnFormat(subItem));
760 DrawThemeText(theme(), dc, LVP_LISTITEM, weHaveFocus ? LISS_SELECTED : LISS_SELECTEDNOTFOCUS, cvt, (int)cvt.length(), DT_NOPREFIX | DT_END_ELLIPSIS | DT_SINGLELINE | DT_VCENTER | format, 0, clip);
761 return;
762 }
763 #endif
764 __super::RenderSubItemText(item, subItem, subItemRect, updateRect, dc, allowColors);
765 }
766
767 void CListControlWithSelectionBase::RenderGroupHeader2(size_t baseItem,const CRect & p_headerRect,const CRect & p_updateRect,CDCHandle p_dc) {
768 TParent::RenderGroupHeader2(baseItem,p_headerRect,p_updateRect,p_dc);
769 if (IsGroupHeaderFocused2(baseItem)) {
770 PaintUtils::FocusRect(p_dc,p_headerRect);
771 }
772 }
773
774 CRect CListControlWithSelectionBase::DropMarkerUpdateRect(t_size index,bool bInside) const {
775 if (index != SIZE_MAX) {
776 CRect rect;
777 if (bInside) {
778 rect = GetItemRect(index);
779 rect.InflateRect(DropMarkerMargin());
780 } else {
781 rect = DropMarkerRect(DropMarkerOffset(index));
782 }
783 return rect;
784 } else {
785 return CRect(0,0,0,0);
786 }
787 }
788 void CListControlWithSelectionBase::AddDropMarkToUpdateRgn(HRGN p_rgn, t_size p_index, bool bInside) const {
789 CRect rect = DropMarkerUpdateRect(p_index,bInside);
790 if (!rect.IsRectEmpty()) PaintUtils::AddRectToRgn(p_rgn,rect);
791 }
792
793 CRect CListControlWithSelectionBase::DropMarkerRect(int offset) const {
794 const int delta = MulDiv(5,m_dpi.cy,96);
795 CRect rc(0,offset - delta,GetViewAreaWidth(), offset + delta);
796 rc.InflateRect(DropMarkerMargin());
797 return rc;
798 }
799
800 int CListControlWithSelectionBase::DropMarkerOffset(t_size marker) const {
801
802 return (marker > 0 ? this->GetItemBottomOffsetAbs(marker-1): 0) - GetViewOffset().y;
803 }
804
805 bool CListControlWithSelectionBase::RenderDropMarkerClipped2(CDCHandle dc, const CRect & update, t_size item, bool bInside) {
806 CRect markerRect = DropMarkerUpdateRect(item,bInside);
807 CRect affected;
808 if (affected.IntersectRect(markerRect,update)) {
809 DCStateScope state(dc);
810 if (dc.IntersectClipRect(affected)) {
811 RenderDropMarker2(dc,item,bInside);
812 return true;
813 }
814 }
815 return false;
816 }
817 void CListControlWithSelectionBase::RenderDropMarker2(CDCHandle dc, t_size item, bool bInside) {
818 if (item != SIZE_MAX) {
819 if (bInside) {
820 CPen pen; MakeDropMarkerPen(pen);
821 SelectObjectScope penScope(dc,pen);
822 const CRect rc = GetItemRect(item);
823 dc.MoveTo(rc.left,rc.top);
824 dc.LineTo(rc.right,rc.top);
825 dc.LineTo(rc.right,rc.bottom);
826 dc.LineTo(rc.left,rc.bottom);
827 dc.LineTo(rc.left,rc.top);
828 } else {
829 RenderDropMarkerByOffset2(DropMarkerOffset(item),dc);
830 }
831 }
832 }
833
834 SIZE CListControlWithSelectionBase::DropMarkerMargin() const {
835 const int penDeltaX = MulDiv(5 /* we don't know how to translate CreatePen units... */,m_dpi.cx,96);
836 const int penDeltaY = MulDiv(5 /* we don't know how to translate CreatePen units... */,m_dpi.cy,96);
837 SIZE s = {penDeltaX,penDeltaY};
838 return s;
839 }
840 void CListControlWithSelectionBase::MakeDropMarkerPen(CPen & out) const {
841 WIN32_OP_D( out.CreatePen(PS_SOLID,3,GetSysColorHook(colorText)) != NULL );
842 }
843
844 void CListControlWithSelectionBase::RenderDropMarkerByOffset2(int offset,CDCHandle p_dc) {
845 CPen pen; MakeDropMarkerPen(pen);
846 const int delta = MulDiv(5,m_dpi.cy,96);
847 SelectObjectScope penScope(p_dc,pen);
848 const int width = GetViewAreaWidth();
849 if (width > 0) {
850 const int vx = this->GetViewOffset().x;
851 const int left = -vx;
852 const int right = width - 1 - vx;
853 p_dc.MoveTo(left,offset);
854 p_dc.LineTo(right,offset);
855 p_dc.MoveTo(left,offset-delta);
856 p_dc.LineTo(left,offset+delta);
857 p_dc.MoveTo(right,offset-delta);
858 p_dc.LineTo(right,offset+delta);
859 }
860 }
861
862 void CListControlWithSelectionBase::FocusToUpdateRgn(HRGN rgn) {
863 size_t focusItem = GetFocusItem();
864 if (focusItem != SIZE_MAX) AddItemToUpdateRgn(rgn,focusItem);
865 size_t focusGroup = GetGroupFocus2();
866 if (focusGroup != SIZE_MAX) AddGroupHeaderToUpdateRgn2(rgn,focusGroup);
867 }
868
869 void CListControlWithSelectionImpl::ReloadData() {
870 if ( GetItemCount() != m_selection.get_size() ) {
871 this->SelHandleReset();
872 }
873 __super::ReloadData();
874 }
875
876 size_t CListControlWithSelectionImpl::GetGroupFocus2() const {
877 return (m_groupFocus && m_focus < GetItemCount()) ? m_focus : SIZE_MAX;
878 }
879
880 void CListControlWithSelectionImpl::SetSelectionImpl(pfc::bit_array const & affected, pfc::bit_array const & status) {
881 const t_size total = m_selection.get_size();
882 pfc::bit_array_flatIndexList toUpdate;
883
884 // Only fire UpdateItems for stuff that's both on-screen and actually changed
885 // Firing for whole affected mask will repaint everything when selecting one item
886 t_size base, count;
887 if (!GetItemRangeAbs(GetVisibleRectAbs(), base, count)) { base = count = 0; }
888
889 affected.walk( total, [&] (size_t idx) {
890 if ( m_selection[idx] != status[idx] && this->CanSelectItem(idx) ) {
891 m_selection[idx] = status[idx];
892 if ( idx >= base && idx < base+count ) toUpdate.add(idx);
893 }
894 } );
895
896 if ( toUpdate.get_count() > 0 ) {
897 UpdateItems(toUpdate);
898 }
899
900
901 // Fire subclassable method ONLY WITH ITEMS THAT CHANGED
902 // We provide no other means for them to know old state
903 this->OnSelectionChanged(toUpdate, status );
904 }
905
906 void CListControlWithSelectionImpl::SetSelection(pfc::bit_array const & affected, pfc::bit_array const & status) {
907 RefreshSelectionSize();
908
909
910 if ( m_selectionSupport == selectionSupportNone ) return;
911
912 if ( m_selectionSupport == selectionSupportSingle ) {
913 size_t single = SIZE_MAX;
914 bool selNone = true;
915 const size_t total = m_selection.get_size();
916 for( size_t walk = 0; walk < total; ++ walk ) {
917 if ( affected.get(walk) ) {
918 if ( status.get(walk) && single == SIZE_MAX ) {
919 single = walk;
920 }
921 } else if ( IsItemSelected( walk ) ) {
922 selNone = false;
923 }
924 }
925 if ( single < total ) {
926 SetSelectionImpl( pfc::bit_array_true(), pfc::bit_array_one( single ) );
927 } else if ( selNone ) {
928 this->SetSelectionImpl( pfc::bit_array_true(), pfc::bit_array_false() );
929 }
930 } else {
931 SetSelectionImpl( affected, status );
932 }
933
934 }
935
936 void CListControlWithSelectionImpl::RefreshSelectionSize() {
937 RefreshSelectionSize(GetItemCount());
938 }
939 void CListControlWithSelectionImpl::RefreshSelectionSize(t_size total) {
940 const t_size oldSize = m_selection.get_size();
941 if (total != oldSize) {
942 m_selection.set_size(total);
943 for(t_size walk = oldSize; walk < total; ++walk) m_selection[walk] = false;
944 }
945 }
946
947 void CListControlWithSelectionImpl::SetGroupFocusByItem(t_size item) {
948 CRgn update; update.CreateRectRgn(0,0,0,0);
949 FocusToUpdateRgn(update);
950 m_groupFocus = true; m_focus = item;
951 FocusToUpdateRgn(update);
952 InvalidateRgn(update);
953
954
955 CRect header;
956 if (GetGroupHeaderRectAbs2(item,header)) EnsureVisibleRectAbs(header);
957
958 this->OnFocusChangedGroup2( item );
959 }
960
961 void CListControlWithSelectionImpl::SetFocusItem(t_size index) {
962 CRgn update; update.CreateRectRgn(0,0,0,0);
963 FocusToUpdateRgn(update);
964 size_t oldFocus = m_focus;
965 m_groupFocus = false; m_focus = index;
966 FocusToUpdateRgn(update);
967 InvalidateRgn(update);
968
969 if ( index != SIZE_MAX ) {
970 EnsureVisibleRectAbs(GetItemRectAbs(index));
971 }
972
973 SetSelectionStart(index);
974
975 this->OnFocusChanged(oldFocus, index);
976 }
977
978 static void UpdateIndexOnReorder(t_size & index, const t_size * order, t_size count) {
979 index = pfc::permutation_find_reverse(order,count,index);
980 }
981 static void UpdateIndexOnRemoval(t_size & index, const pfc::bit_array & mask, t_size oldCount, t_size newCount) {
982 if (index >= oldCount || newCount == 0) {index = SIZE_MAX; return;}
983 for(t_size walk = mask.find_first(true,0,oldCount); walk < newCount; ++walk) {
984 if (walk < index) --index;
985 else break;
986 }
987 if (index >= newCount) index = newCount - 1;
988 }
989
990 static void UpdateIndexOnInsert(size_t& index, pfc::bit_array const& mask, size_t newCount) {
991 if (index == SIZE_MAX) return;
992 for (size_t walk = 0; walk < index; ) {
993 size_t delta = mask.calc_count(true, walk, index - walk);
994 if (delta == 0) break;
995 walk = index; index += delta;
996 }
997 }
998
999 static void UpdateIndexOnInsert(t_size & index, t_size base, t_size count) {
1000 if (index != SIZE_MAX && index >= base) index += count;
1001 }
1002
1003 void CListControlWithSelectionImpl::SelHandleReorder(const t_size * order, t_size count) {
1004 RefreshSelectionSize();
1005 UpdateIndexOnReorder(m_focus,order,count);
1006 UpdateIndexOnReorder(m_selectionStart,order,count);
1007 pfc::array_t<bool> newSel; newSel.set_size(m_selection.get_size());
1008 for(t_size walk = 0; walk < m_selection.get_size(); ++walk) newSel[walk] = m_selection[order[walk]];
1009 m_selection = newSel;
1010 }
1011
1012 void CListControlWithSelectionImpl::SelHandleReset() {
1013 RefreshSelectionSize(GetItemCount());
1014 for(t_size walk = 0; walk < m_selection.get_size(); walk++) m_selection[walk] = false;
1015 m_focus = SIZE_MAX;
1016 m_groupFocus = false;
1017
1018 }
1019 void CListControlWithSelectionImpl::SelHandleRemoval(const pfc::bit_array & mask, t_size oldCount) {
1020 RefreshSelectionSize(oldCount);
1021 const t_size newCount = GetItemCount();
1022 UpdateIndexOnRemoval(m_focus,mask,oldCount,newCount);
1023 UpdateIndexOnRemoval(m_selectionStart,mask,oldCount,newCount);
1024 pfc::remove_mask_t(m_selection,mask);
1025 }
1026
1027 void CListControlWithSelectionImpl::SelHandleInsertion(pfc::bit_array const& mask, size_t oldCount, size_t newCount, bool select) {
1028 PFC_ASSERT(newCount == GetItemCount());
1029 PFC_ASSERT(oldCount <= newCount); (void)oldCount;
1030
1031 // To behave sanely in single-select mode, we'd have to alter selection of other items from here
1032 // Let caller worry and outright deny select requests in modes other than multisel
1033 if (m_selectionSupport != selectionSupportMulti) select = false;
1034
1035 UpdateIndexOnInsert(m_focus, mask, newCount);
1036 UpdateIndexOnInsert(m_selectionStart, mask, newCount);
1037 pfc::array_t<bool> newSel;
1038 newSel.resize(newCount);
1039
1040 size_t inWalk = 0;
1041 for (size_t walk = 0; walk < newCount; ++walk) {
1042 bool v = false;
1043 if (mask[walk]) {
1044 v = select;
1045 } else if (inWalk < m_selection.get_size()) {
1046 v = m_selection[inWalk++];
1047 }
1048 newSel[walk] = v;
1049 }
1050
1051 m_selection = std::move(newSel);
1052 }
1053
1054 void CListControlWithSelectionImpl::SelHandleInsertion(t_size base, t_size count, bool select) {
1055 PFC_ASSERT(base + count <= GetItemCount());
1056
1057 // To behave sanely in single-select mode, we'd have to alter selection of other items from here
1058 // Let caller worry and outright deny select requests in modes other than multisel
1059 if (m_selectionSupport != selectionSupportMulti) select = false;
1060
1061 UpdateIndexOnInsert(m_focus,base,count);
1062 UpdateIndexOnInsert(m_selectionStart,base,count);
1063 RefreshSelectionSize(GetItemCount() - count);
1064
1065
1066 m_selection.insert_multi(select,base,count);
1067 }
1068
1069
1070 LRESULT CListControlWithSelectionBase::OnGetDlgCode(UINT,WPARAM,LPARAM p_lp,BOOL& bHandled) {
1071 if (p_lp == 0) {
1072 return DLGC_WANTALLKEYS | DLGC_WANTCHARS | DLGC_WANTARROWS;
1073 } else {
1074 const MSG * pmsg = reinterpret_cast<const MSG *>(p_lp);
1075 switch(pmsg->message) {
1076 case WM_KEYDOWN:
1077 case WM_KEYUP:
1078 switch(pmsg->wParam) {
1079 case VK_ESCAPE:
1080 case VK_TAB:
1081 bHandled = FALSE;
1082 return 0;
1083 default:
1084 return DLGC_WANTMESSAGE;
1085 }
1086 case WM_CHAR:
1087 return DLGC_WANTMESSAGE;
1088 default:
1089 bHandled = FALSE;
1090 return 0;
1091 }
1092 }
1093 }
1094
1095 bool CListControlWithSelectionBase::GetFocusRect(CRect & p_rect) {
1096 CRect temp;
1097 if (!GetFocusRectAbs(temp)) return false;
1098 p_rect = RectClientToAbs(temp);
1099 return true;
1100 }
1101
1102 bool CListControlWithSelectionBase::GetFocusRectAbs(CRect & p_rect) {
1103 size_t item = this->GetFocusItem();
1104 if (item != SIZE_MAX) {
1105 p_rect = this->GetItemRectAbs(item);
1106 return true;
1107 }
1108
1109 item = this->GetGroupFocus2();
1110 if (item != SIZE_MAX) {
1111 return this->GetGroupHeaderRectAbs2(item, p_rect);
1112 }
1113
1114 return false;
1115 }
1116
1117 bool CListControlWithSelectionBase::GetContextMenuPoint2(CPoint& ptInOut) {
1118 CPoint ptInvalid(-1,-1);
1119 if (ptInOut == ptInvalid) {
1120 ptInOut = GetContextMenuPointDefault();
1121 return ptInOut != ptInvalid;
1122 } else {
1123 CRect rc = this->GetClientRectHook();
1124 WIN32_OP_D( ClientToScreen(rc) );
1125 return !!rc.PtInRect(ptInOut);
1126 }
1127 }
1128
1129 CPoint CListControlWithSelectionBase::GetContextMenuPointDefault() {
1130 CRect rect;
1131 if (!GetFocusRectAbs(rect)) return CPoint(-1,-1);
1132 EnsureVisibleRectAbs(rect);
1133 CPoint pt = rect.CenterPoint() - GetViewOffset();
1134 ClientToScreen(&pt);
1135 return pt;
1136 }
1137
1138 CPoint CListControlWithSelectionBase::GetContextMenuPoint(CPoint ptGot) {
1139 CPoint pt;
1140 if (ptGot.x == -1 && ptGot.y == -1) {
1141 pt = GetContextMenuPointDefault();
1142 } else {
1143 pt = ptGot;
1144 }
1145 return pt;
1146 }
1147
1148 CPoint CListControlWithSelectionBase::GetContextMenuPoint(LPARAM lp) {
1149 CPoint pt;
1150 if (lp == -1) {
1151 pt = GetContextMenuPointDefault();
1152 } else {
1153 pt = lp;
1154 }
1155 return pt;
1156 }
1157
1158 bool CListControlWithSelectionBase::MakeDropReorderPermutation(pfc::array_t<t_size> & out, CPoint ptDrop) const {
1159 t_size insertMark = InsertIndexFromPoint(ptDrop);
1160 /*if (insertMark != this->GetFocusItem())*/ {
1161 const t_size count = GetItemCount();
1162 if (insertMark > count) insertMark = count;
1163 {
1164 t_size selBefore = 0;
1165 for(t_size walk = 0; walk < insertMark; ++walk) {
1166 if (IsItemSelected(walk)) selBefore++;
1167 }
1168 insertMark -= selBefore;
1169 }
1170 {
1171 pfc::array_t<t_size> permutation, selected, nonselected;
1172 const t_size selcount = this->GetSelectedCount();
1173 selected.set_size(selcount); nonselected.set_size(count - selcount);
1174 permutation.set_size(count);
1175 if (insertMark > nonselected.get_size()) insertMark = nonselected.get_size();
1176 for(t_size walk = 0, swalk = 0, nwalk = 0; walk < count; ++walk) {
1177 if (IsItemSelected(walk)) {
1178 selected[swalk++] = walk;
1179 } else {
1180 nonselected[nwalk++] = walk;
1181 }
1182 }
1183 for(t_size walk = 0; walk < insertMark; ++walk) {
1184 permutation[walk] = nonselected[walk];
1185 }
1186 for(t_size walk = 0; walk < selected.get_size(); ++walk) {
1187 permutation[insertMark + walk] = selected[walk];
1188 }
1189 for(t_size walk = insertMark; walk < nonselected.get_size(); ++walk) {
1190 permutation[selected.get_size() + walk] = nonselected[walk];
1191 }
1192 for(t_size walk = 0; walk < permutation.get_size(); ++walk) {
1193 if (permutation[walk] != walk) {
1194 out = permutation;
1195 return true;
1196 }
1197 }
1198 }
1199 }
1200 return false;
1201 }
1202
1203 void CListControlWithSelectionBase::EnsureVisibleRectAbs(const CRect & p_rect) {
1204 if (!m_noEnsureVisible) TParent::EnsureVisibleRectAbs(p_rect);
1205 }
1206
1207 bool CListControlWithSelectionBase::TypeFindCheck(DWORD ts) const {
1208 if (m_typeFindTS == 0) return false;
1209 return ts - m_typeFindTS < 1000;
1210 }
1211
1212 void CListControlWithSelectionBase::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) {
1213 (void)nRepCnt; (void)nFlags;
1214 if (nChar < 32) {
1215 m_typeFindTS = 0;
1216 return;
1217 }
1218
1219 const DWORD ts = GetTickCount();
1220 if (!TypeFindCheck(ts)) m_typeFind.reset();
1221
1222 if (nChar == ' ' && m_typeFind.is_empty()) {
1223 m_typeFindTS = 0;
1224 return;
1225 }
1226
1227 m_typeFindTS = ts;
1228 if (m_typeFindTS == 0) m_typeFindTS = UINT32_MAX;
1229 char temp[10] = {};
1230 pfc::utf8_encode_char(nChar, temp);
1231 m_typeFind += temp;
1232 RunTypeFind();
1233 }
1234
1235 static unsigned detectRepetition( pfc::string8 const & str ) {
1236 size_t count = 0;
1237 size_t walk = 0;
1238 uint32_t first = 0;
1239
1240 while( walk < str.length() ) {
1241 uint32_t current;
1242 auto delta = pfc::utf8_decode_char( str.c_str() + walk, current, str.length() - walk );
1243 if ( delta == 0 ) break;
1244 walk += delta;
1245
1246 if ( count == 0 ) first = current;
1247 else if ( first != current ) return 0;
1248
1249 ++ count;
1250 }
1251
1252 if ( count > 1 ) return first;
1253 return 0;
1254 }
1255
1256 size_t CListControlWithSelectionBase::EvalTypeFind() {
1257 if ( GetItemCount() == 0 ) return SIZE_MAX;
1258
1259 static SmartStrStr tool;
1260
1261 const size_t itemCount = GetItemCount();
1262 const size_t colCount = GetColumnCount();
1263 pfc::string_formatter temp; temp.prealloc(1024);
1264 t_size searchBase = this->GetFocusItem();
1265 if (searchBase >= itemCount) searchBase = 0;
1266
1267 size_t partial = SIZE_MAX;
1268 size_t repetition = SIZE_MAX;
1269 bool useRepetition = false;
1270 pfc::string8 strRepetitionChar;
1271 unsigned repChar = detectRepetition( m_typeFind );
1272 if ( repChar != 0 ) {
1273 useRepetition = true;
1274 strRepetitionChar.add_char( repChar );
1275 }
1276
1277 for(t_size walk = 0; walk < itemCount; ++walk) {
1278 t_size index = (walk + searchBase) % itemCount;
1279 for(size_t cWalk = 0; cWalk < colCount; ++cWalk) {
1280
1281 temp.reset();
1282
1283 if (AllowTypeFindInCell( index, cWalk )) {
1284 this->GetSubItemText(index, cWalk, temp);
1285 }
1286
1287 if ( temp.length() == 0 ) {
1288 continue;
1289 }
1290 if (partial == SIZE_MAX) {
1291 size_t matchAt;
1292 if (tool.strStrEnd( temp, m_typeFind, & matchAt ) != nullptr) {
1293 if ( matchAt == 0 ) return index;
1294 partial = index;
1295 }
1296 } else {
1297 if ( tool.matchHere( temp, m_typeFind ) ) return index;
1298 }
1299 if (useRepetition && index != searchBase) {
1300 if ( tool.matchHere( temp, strRepetitionChar ) ) {
1301 useRepetition = false;
1302 repetition = index;
1303 }
1304 }
1305 }
1306 }
1307 if (partial < itemCount) return partial;
1308 if (repetition < itemCount) return repetition;
1309 return SIZE_MAX;
1310 }
1311
1312 void CListControlWithSelectionBase::RunTypeFind() {
1313 size_t index = EvalTypeFind();
1314 if (index < GetItemCount() ) {
1315 this->SetFocusItem( index );
1316 this->SetSelection(pfc::bit_array_true(), pfc::bit_array_one(index) );
1317 } else {
1318 MessageBeep(0);
1319 }
1320 }
1321
1322 size_t CListControlWithSelectionBase::GetFirstSelected() const {
1323 const size_t count = GetItemCount();
1324 for( size_t w = 0; w < count; ++w ) {
1325 if ( IsItemSelected(w) ) return w;
1326 }
1327 return SIZE_MAX;
1328 }
1329
1330 size_t CListControlWithSelectionBase::GetLastSelected() const {
1331 const size_t count = GetItemCount();
1332 for( size_t w = count - 1; (t_ssize) w >= 0; --w ) {
1333 if ( IsItemSelected(w) ) return w;
1334 }
1335 return SIZE_MAX;
1336 }
1337
1338 namespace {
1339 class CDropTargetImpl : public ImplementCOMRefCounter<IDropTarget> {
1340 public:
1341 COM_QI_BEGIN()
1342 COM_QI_ENTRY(IUnknown)
1343 COM_QI_ENTRY(IDropTarget)
1344 COM_QI_END()
1345
1346 bool valid = true;
1347 std::function<void(CPoint pt) > Track;
1348 std::function<DWORD (IDataObject*)> HookAccept;
1349 std::function<void (IDataObject*, CPoint pt)> HookDrop;
1350 std::function<void ()> HookLeave;
1351
1352 DWORD m_effect = DROPEFFECT_NONE;
1353
1354 HRESULT STDMETHODCALLTYPE DragEnter(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {
1355 (void)grfKeyState; (void)pt;
1356 if (pDataObj == NULL || pdwEffect == NULL) return E_INVALIDARG;
1357 if (!valid) return E_FAIL;
1358 if ( HookAccept ) {
1359 m_effect = HookAccept(pDataObj);
1360 } else {
1361 m_effect = DROPEFFECT_MOVE;
1362 }
1363 *pdwEffect = m_effect;
1364 return S_OK;
1365 }
1366 HRESULT STDMETHODCALLTYPE DragOver(DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {
1367 (void)grfKeyState;
1368 if (pdwEffect == NULL) return E_INVALIDARG;
1369 if (!valid) return E_FAIL;
1370 if ( m_effect != DROPEFFECT_NONE ) Track(CPoint(pt.x, pt.y));
1371 *pdwEffect = m_effect;
1372 return S_OK;
1373 }
1374 HRESULT STDMETHODCALLTYPE DragLeave() {
1375 if (HookLeave) HookLeave();
1376 return S_OK;
1377 }
1378 HRESULT STDMETHODCALLTYPE Drop(IDataObject *pDataObj, DWORD grfKeyState, POINTL pt, DWORD *pdwEffect) {
1379 (void)grfKeyState; (void)pdwEffect;
1380 if ( HookDrop && m_effect != DROPEFFECT_NONE ) {
1381 HookDrop( pDataObj, CPoint(pt.x, pt.y) );
1382 }
1383 return S_OK;
1384 }
1385 };
1386
1387 class CDropSourceImpl : public ImplementCOMRefCounter<IDropSource> {
1388 public:
1389 CPoint droppedAt;
1390 bool droppedAtValid = false;
1391 bool allowReorder = false;
1392
1393 bool allowDragOutside = false;
1394 CWindow wndOrigin;
1395
1396 COM_QI_BEGIN()
1397 COM_QI_ENTRY(IUnknown)
1398 COM_QI_ENTRY(IDropSource)
1399 COM_QI_END()
1400
1401 HRESULT STDMETHODCALLTYPE GiveFeedback(DWORD dwEffect) {
1402 m_effect = dwEffect;
1403 return DRAGDROP_S_USEDEFAULTCURSORS;
1404 }
1405
1406 HRESULT STDMETHODCALLTYPE QueryContinueDrag(BOOL fEscapePressed, DWORD grfKeyState) {
1407
1408 if (fEscapePressed || (grfKeyState & MK_RBUTTON) != 0) {
1409 return DRAGDROP_S_CANCEL;
1410 } else if (!(grfKeyState & MK_LBUTTON)) {
1411 if (m_effect == DROPEFFECT_NONE) return DRAGDROP_S_CANCEL;
1412
1413 CPoint pt;
1414 if (!GetCursorPos(&pt)) return DRAGDROP_S_CANCEL;
1415 bool bInside = false;
1416 if (wndOrigin) {
1417 CRect rc;
1418 WIN32_OP_D(wndOrigin.GetWindowRect(rc));
1419 bInside = rc.PtInRect(pt);
1420 }
1421 if (!allowDragOutside && !bInside) return DRAGDROP_S_CANCEL;
1422
1423 if ( allowReorder && bInside) {
1424 droppedAt = pt;
1425 droppedAtValid = true;
1426 return DRAGDROP_S_CANCEL;
1427 }
1428 return DRAGDROP_S_DROP;
1429
1430 } else {
1431 return S_OK;
1432 }
1433 }
1434 private:
1435 DWORD m_effect = 0;
1436 };
1437 }
1438
1439 void CListControlWithSelectionBase::RunDragDrop(const CPoint & p_origin, bool p_isRightClick) {
1440
1441 uint32_t flags = this->QueryDragDropTypes();
1442 if ( flags == 0 ) {
1443 PFC_ASSERT(!"How did we get here?");
1444 return;
1445 }
1446 if ( flags == dragDrop_reorder ) {
1447 if ( p_isRightClick ) return;
1448 CPoint ptDrop;
1449 if ( RunReorderDragDrop( p_origin, ptDrop ) ) {
1450 pfc::array_t<size_t> order;
1451 if (MakeDropReorderPermutation(order, ptDrop)) {
1452 this->RequestReorder(order.get_ptr(), order.get_size());
1453 }
1454 }
1455 return;
1456 }
1457
1458 auto obj = this->MakeDataObject();
1459 if (obj.is_empty()) {
1460 PFC_ASSERT(!"How did we get here? No IDataObject");
1461 return;
1462 }
1463
1464 pfc::com_ptr_t<CDropSourceImpl> source = new CDropSourceImpl();
1465 source->wndOrigin = m_hWnd;
1466 source->allowDragOutside = true;
1467 source->allowReorder = (flags & dragDrop_reorder) != 0;
1468
1469 DWORD outEffect = DROPEFFECT_NONE;
1470 HRESULT status = DoDragDrop(obj.get_ptr(), source.get_ptr(), DragDropSourceEffects() , &outEffect);
1471
1472 if ( source->droppedAtValid ) {
1473 CPoint ptDrop = source->droppedAt;
1474 WIN32_OP_D(this->ScreenToClient(&ptDrop));
1475 pfc::array_t<size_t> order;
1476 if (MakeDropReorderPermutation(order, ptDrop)) {
1477 this->RequestReorder(order.get_ptr(), order.get_size());
1478 }
1479 } else if (status == DRAGDROP_S_DROP) {
1480 DragDropSourceSucceeded(outEffect);
1481 }
1482 }
1483
1484 pfc::com_ptr_t<IDataObject> CListControlWithSelectionBase::MakeDataObject() {
1485 // return dummy IDataObject, presume derived transmits drag and drop payload by other means
1486 using namespace IDataObjectUtils;
1487 return new ImplementCOMRefCounter< CDataObjectBase >();
1488 }
1489
1490 bool CListControlWithSelectionBase::RunReorderDragDrop(CPoint ptOrigin, CPoint & ptDrop) {
1491 (void)ptOrigin;
1492 pfc::com_ptr_t<CDropSourceImpl> source = new CDropSourceImpl();
1493 pfc::com_ptr_t<CDropTargetImpl> target = new CDropTargetImpl();
1494
1495 source->wndOrigin = m_hWnd;
1496 source->allowDragOutside = false;
1497 source->allowReorder = true;
1498
1499 target->Track = [this](CPoint pt) {
1500 WIN32_OP_D(this->ScreenToClient(&pt));
1501 size_t idx = this->InsertIndexFromPoint(pt);
1502 this->SetDropMark(idx, false);
1503 };
1504
1505 if ( FAILED(RegisterDragDrop(*this, target.get_ptr())) ) {
1506 // OleInitialize not called?
1507 PFC_ASSERT( !"Should not get here" );
1508 return false;
1509 }
1510
1511 pfc::onLeaving scope([=] { target->valid = false; RevokeDragDrop(*this); });
1512
1513 using namespace IDataObjectUtils;
1514 pfc::com_ptr_t<IDataObject> dataobject = new ImplementCOMRefCounter< CDataObjectBase >();
1515 DWORD outeffect = 0;
1516
1517 ToggleDDScroll(true);
1518 DoDragDrop(dataobject.get_ptr(), source.get_ptr(), DROPEFFECT_MOVE, &outeffect);
1519 ClearDropMark();
1520 ToggleDDScroll(false);
1521 if (source->droppedAtValid ) {
1522 CPoint pt = source->droppedAt;
1523 WIN32_OP_D( this->ScreenToClient( &pt ) );
1524 ptDrop = pt;
1525 return true;
1526 }
1527 return false;
1528 }
1529
1530 int CListControlWithSelectionBase::OnCreatePassThru(LPCREATESTRUCT) {
1531 const uint32_t flags = this->QueryDragDropTypes();
1532 if ( flags & dragDrop_external ) {
1533
1534 pfc::com_ptr_t<CDropTargetImpl> target = new CDropTargetImpl();
1535
1536 auto dda = std::make_shared<dragDropAccept_t>();
1537
1538 target->HookAccept = [this, flags, dda] ( IDataObject * obj ) {
1539 if (this->m_ownDDActive && (flags & dragDrop_reorder) != 0) {
1540 // Do not generate OnDrop for reorderings
1541 dda->showDropMark = true;
1542 dda->dwEFfect = DROPEFFECT_MOVE;
1543 } else {
1544 *dda = this->DragDropAccept2(obj);
1545 }
1546 return dda->dwEFfect;
1547 };
1548 target->HookDrop = [this, flags] ( IDataObject * obj, CPoint pt ) {
1549 this->ToggleDDScroll(false);
1550 this->ClearDropMark();
1551 if ( this->m_ownDDActive ) {
1552 // Do not generate OnDrop for reorderings
1553 if ( flags & dragDrop_reorder ) return;
1554 }
1555 this->OnDrop( obj, pt );
1556 };
1557 target->HookLeave = [this] {
1558 this->ClearDropMark();
1559 this->ToggleDDScroll(false);
1560 };
1561
1562 target->Track = [this, dda](CPoint pt) {
1563 this->ToggleDDScroll(true);
1564 if ( dda->showDropMark ) {
1565 WIN32_OP_D(this->ScreenToClient(&pt));
1566 size_t idx = this->InsertIndexFromPoint(pt);
1567 if (dda->dropOnItem) {
1568 if (idx < this->GetItemCount()) {
1569 this->SetDropMark(idx, true);
1570 } else {
1571 this->ClearDropMark();
1572 }
1573 } else {
1574 this->SetDropMark(idx, false);
1575 }
1576
1577 } else {
1578 this->ClearDropMark();
1579 }
1580 };
1581
1582 RegisterDragDrop(*this, target.get_ptr() );
1583 }
1584 SetMsgHandled(FALSE); return 0;
1585 }
1586
1587 void CListControlWithSelectionBase::OnDestroyPassThru() {
1588 AbortSelectDragMode();
1589 ToggleDDScroll(false);
1590 RevokeDragDrop(*this);
1591 SetMsgHandled(FALSE);
1592 }
1593
1594 size_t CListControlWithSelectionBase::GetPasteTarget(const CPoint * ptPaste) const {
1595 size_t target = SIZE_MAX;
1596 if (ptPaste != nullptr) {
1597 CPoint pt(*ptPaste); WIN32_OP_D(ScreenToClient(&pt));
1598 size_t groupBase;
1599 if (GroupHeaderFromPoint2(pt, groupBase)) {
1600 target = groupBase;
1601 } else if (ItemFromPoint(pt, target)) {
1602 auto rc = GetItemRect(target);
1603 auto height = rc.Height();
1604 if (height > 0) {
1605 double posInItem = (double)(pt.y - rc.top) / (double)height;
1606 if (posInItem >= 0.5) ++target;
1607 }
1608 }
1609 } else if (GroupFocusActive()) {
1610 target = GetGroupFocus2();
1611 } else {
1612 target = GetFocusItem();
1613 }
1614 return target;
1615 }
1616
1617
1618 pfc::bit_array_table CListControlWithSelectionImpl::GetSelectionMaskRef() const {
1619 return pfc::bit_array_table(m_selection.get_ptr(), m_selection.get_size());
1620 }
1621 pfc::bit_array_bittable CListControlWithSelectionImpl::GetSelectionMask() const {
1622 pfc::bit_array_bittable ret;
1623 const auto count = GetItemCount();
1624 ret.resize( GetItemCount() );
1625 for( size_t walk = 0; walk < count; ++ walk ) {
1626 ret.set(walk, IsItemSelected(walk));
1627 }
1628 return ret;
1629 }
1630
1631 void CListControlWithSelectionImpl::OnItemsReordered( const size_t * order, size_t count) {
1632 PFC_ASSERT( count == GetItemCount() );
1633
1634 SelHandleReorder( order, count );
1635 __super::OnItemsReordered(order, count);
1636 }
1637
1638 void CListControlWithSelectionImpl::OnItemsRemoved( pfc::bit_array const & mask, size_t oldCount) {
1639 SelHandleRemoval(mask, oldCount);
1640 __super::OnItemsRemoved( mask, oldCount );
1641 }
1642
1643 void CListControlWithSelectionImpl::OnItemsInsertedEx(pfc::bit_array const& mask, size_t oldCount, size_t newCount, bool bSelect) {
1644 SelHandleInsertion( mask, oldCount, newCount, bSelect);
1645 __super::OnItemsInsertedEx( mask, oldCount, newCount, bSelect );
1646 }
1647
1648 bool CListControlWithSelectionImpl::SelectAll() {
1649 if ( m_selectionSupport != selectionSupportMulti ) return false;
1650 return __super::SelectAll();
1651 }
1652
1653 DWORD CListControlWithSelectionBase::DragDropAccept(IDataObject* obj, bool& showDropMark) {
1654 (void)obj;
1655 showDropMark = false; return DROPEFFECT_NONE;
1656 }
1657
1658 CListControlWithSelectionBase::dragDropAccept_t CListControlWithSelectionBase::DragDropAccept2(IDataObject* obj) {
1659 dragDropAccept_t ret;
1660 ret.dwEFfect = this->DragDropAccept(obj, ret.showDropMark);
1661 return ret;
1662 }