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