|
1
|
1 #include "stdafx.h"
|
|
|
2 #include "CListControl.h"
|
|
|
3 #include "CListControlHeaderImpl.h" // redundant but makes intelisense quit showing false errors
|
|
|
4 #include "CListControl-Cells.h"
|
|
|
5 #include "PaintUtils.h"
|
|
|
6 #include "GDIUtils.h"
|
|
|
7 #include "win32_utility.h"
|
|
|
8 #include "DarkMode.h"
|
|
|
9 #include <vssym32.h>
|
|
|
10
|
|
|
11 static constexpr int lineBelowHeaderCY = 1;
|
|
|
12
|
|
|
13 // Prevent erratic behavior of header control
|
|
|
14 static constexpr uint32_t columnWidthSanityLimit = 10000;
|
|
|
15
|
|
|
16 static uint32_t columWidthToPixels(uint32_t user) {
|
|
|
17 PFC_ASSERT(user <= CListControlHeaderImpl::columnWidthMax);
|
|
|
18 return user < columnWidthSanityLimit ? user : columnWidthSanityLimit;
|
|
|
19 }
|
|
|
20
|
|
|
21 static bool testDrawLineBelowHeader() {
|
|
|
22 // Win10
|
|
|
23 return IsWindows10OrGreater();
|
|
|
24 }
|
|
|
25
|
|
|
26 void CListControlHeaderImpl::OnThemeChangedPT() {
|
|
|
27 if (m_header) {
|
|
|
28 auto dark = GetDarkMode();
|
|
|
29 if (dark != m_headerDark) {
|
|
|
30 m_headerDark = dark;
|
|
|
31 DarkMode::ApplyDarkThemeCtrl(m_header, dark, L"ItemsView");
|
|
|
32 }
|
|
|
33 }
|
|
|
34 this->SetMsgHandled(FALSE);
|
|
|
35 }
|
|
|
36
|
|
|
37 void CListControlHeaderImpl::InitializeHeaderCtrl(DWORD flags) {
|
|
|
38 PFC_ASSERT(IsWindow());
|
|
|
39 PFC_ASSERT(!IsHeaderEnabled());
|
|
|
40 m_headerDark = false;
|
|
|
41 WIN32_OP_D( m_header.Create(*this,NULL,NULL,WS_CHILD | flags) != NULL );
|
|
|
42 m_header.SetFont( GetFont() );
|
|
|
43
|
|
|
44 InjectParentEraseHandler(m_header);
|
|
|
45
|
|
|
46 this->OnThemeChangedPT();
|
|
|
47
|
|
|
48 if (testDrawLineBelowHeader()) {
|
|
|
49 WIN32_OP_D( m_headerLine.Create( *this, NULL, NULL, WS_CHILD ) != NULL );
|
|
|
50 InjectParentEraseHandler(m_headerLine);
|
|
|
51 }
|
|
|
52
|
|
|
53 UpdateHeaderLayout();
|
|
|
54 }
|
|
|
55
|
|
|
56 void CListControlHeaderImpl::UpdateHeaderLayout() {
|
|
|
57 CRect client; WIN32_OP_D( GetClientRect(client) );
|
|
|
58 int cw_old = m_clientWidth;
|
|
|
59 m_clientWidth = client.Width();
|
|
|
60 if (IsHeaderEnabled()) {
|
|
|
61 auto rc = client;
|
|
|
62 rc.left -= GetViewOffset().x;
|
|
|
63 WINDOWPOS wPos = {};
|
|
|
64 HDLAYOUT layout = {&rc, &wPos};
|
|
|
65 if (m_header.Layout(&layout)) {
|
|
|
66 m_header.SetWindowPos(wPos.hwndInsertAfter,wPos.x,wPos.y,wPos.cx,wPos.cy,wPos.flags | SWP_SHOWWINDOW);
|
|
|
67 if (m_headerLine != NULL) m_headerLine.SetWindowPos(m_header, wPos.x, wPos.y + wPos.cy, wPos.cx, lineBelowHeaderCY, wPos.flags | SWP_SHOWWINDOW);
|
|
|
68 } else {
|
|
|
69 m_header.ShowWindow(SW_HIDE);
|
|
|
70 if (m_headerLine != NULL) m_headerLine.ShowWindow(SW_HIDE);
|
|
|
71 }
|
|
|
72 } else {
|
|
|
73 if ( cw_old != m_clientWidth) Invalidate();
|
|
|
74 }
|
|
|
75 }
|
|
|
76
|
|
|
77 int CListControlHeaderImpl::GetItemWidth() const {
|
|
|
78 if (IsHeaderEnabled()) return m_itemWidth;
|
|
|
79 else return m_clientWidth;
|
|
|
80 }
|
|
|
81
|
|
|
82 LRESULT CListControlHeaderImpl::OnSizePassThru(UINT,WPARAM,LPARAM) {
|
|
|
83 UpdateHeaderLayout();
|
|
|
84
|
|
|
85 ProcessAutoWidth();
|
|
|
86
|
|
|
87 SetMsgHandled(FALSE);
|
|
|
88 return 0;
|
|
|
89 }
|
|
|
90
|
|
|
91 void CListControlHeaderImpl::OnViewOriginChange(CPoint p_delta) {
|
|
|
92 TParent::OnViewOriginChange(p_delta);
|
|
|
93 if (p_delta.x != 0) UpdateHeaderLayout();
|
|
|
94 }
|
|
|
95
|
|
|
96 void CListControlHeaderImpl::SetHeaderFont(HFONT font) {
|
|
|
97 if (IsHeaderEnabled()) {
|
|
|
98 m_header.SetFont(font); UpdateHeaderLayout();
|
|
|
99 }
|
|
|
100 }
|
|
|
101
|
|
|
102 LRESULT CListControlHeaderImpl::OnHeaderCustomDraw(LPNMHDR hdr) {
|
|
|
103 LPNMCUSTOMDRAW nmcd = reinterpret_cast<LPNMCUSTOMDRAW>(hdr);
|
|
|
104 if ( m_header != NULL && this->GetDarkMode() && nmcd->hdr.hwndFrom == m_header) {
|
|
|
105 switch (nmcd->dwDrawStage)
|
|
|
106 {
|
|
|
107 case CDDS_PREPAINT:
|
|
|
108 return CDRF_NOTIFYITEMDRAW;
|
|
|
109 case CDDS_ITEMPREPAINT:
|
|
|
110 {
|
|
|
111 CDCHandle dc(nmcd->hdc);
|
|
|
112 dc.SetTextColor(0xdedede);
|
|
|
113 dc.SetBkColor(0x191919); // disregarded anyway
|
|
|
114 }
|
|
|
115 return CDRF_DODEFAULT;
|
|
|
116 }
|
|
|
117 }
|
|
|
118 SetMsgHandled(FALSE); return 0;
|
|
|
119 }
|
|
|
120
|
|
|
121 LRESULT CListControlHeaderImpl::OnDividerDoubleClick(int,LPNMHDR hdr,BOOL&) {
|
|
|
122 const NMHEADER * info = (const NMHEADER *) hdr;
|
|
|
123 if (info->iButton == 0) {
|
|
|
124 AutoColumnWidth((t_size)info->iItem);
|
|
|
125 }
|
|
|
126 return 0;
|
|
|
127 }
|
|
|
128
|
|
|
129 LRESULT CListControlHeaderImpl::OnHeaderItemClick(int,LPNMHDR p_hdr,BOOL&) {
|
|
|
130 const NMHEADER * info = (const NMHEADER *) p_hdr;
|
|
|
131 if (info->iButton == 0) {
|
|
|
132 OnColumnHeaderClick((t_uint32)info->iItem);
|
|
|
133 }
|
|
|
134 return 0;
|
|
|
135 }
|
|
|
136
|
|
|
137 LRESULT CListControlHeaderImpl::OnHeaderItemChanged(int,LPNMHDR p_hdr,BOOL&) {
|
|
|
138 const NMHEADER * info = (const NMHEADER*) p_hdr;
|
|
|
139 if (info->pitem->mask & (HDI_WIDTH | HDI_ORDER)) {
|
|
|
140 if(!m_ownColumnsChange) ProcessColumnsChange();
|
|
|
141 }
|
|
|
142 return 0;
|
|
|
143 }
|
|
|
144
|
|
|
145 LRESULT CListControlHeaderImpl::OnHeaderEndDrag(int,LPNMHDR hdr,BOOL&) {
|
|
|
146 NMHEADER * info = (NMHEADER*) hdr;
|
|
|
147 return OnColumnHeaderDrag(info->iItem,info->pitem->iOrder) ? TRUE : FALSE;
|
|
|
148 }
|
|
|
149
|
|
|
150 bool CListControlHeaderImpl::OnColumnHeaderDrag(t_size index, t_size newPos) {
|
|
|
151 index = GetSubItemOrder(index);
|
|
|
152 const t_size count = this->GetColumnCount();
|
|
|
153 if ( count == 0 ) return false;
|
|
|
154 std::vector<size_t> perm; perm.resize(count); pfc::create_move_items_permutation(&perm[0],count, pfc::bit_array_one(index), (int) newPos - (int) index );
|
|
|
155 std::vector<int> order, newOrder; order.resize(count); newOrder.resize(count);
|
|
|
156 WIN32_OP_D(m_header.GetOrderArray((int)count, &order[0]));
|
|
|
157 for(t_size walk = 0; walk < count; ++walk) newOrder[walk] = order[perm[walk]];
|
|
|
158 WIN32_OP_D(m_header.SetOrderArray((int)count, &newOrder[0]));
|
|
|
159 OnColumnsChanged();
|
|
|
160 return true;
|
|
|
161 }
|
|
|
162 t_size CListControlHeaderImpl::SubItemFromPointAbs(CPoint pt) const {
|
|
|
163
|
|
|
164 auto order = GetColumnOrderArray();
|
|
|
165 const t_size colCount = order.size();
|
|
|
166
|
|
|
167 size_t item;
|
|
|
168 if (! ItemFromPointAbs(pt, item ) ) item = SIZE_MAX;
|
|
|
169
|
|
|
170 long xWalk = 0;
|
|
|
171 for(t_size _walk = 0; _walk < colCount; ) {
|
|
|
172 const t_size walk = (t_size) order[_walk];
|
|
|
173
|
|
|
174 size_t span = 1;
|
|
|
175 if (item != SIZE_MAX) span = this->GetSubItemSpan(item, walk);
|
|
|
176
|
|
|
177 PFC_ASSERT( span == 1 || _walk == walk );
|
|
|
178
|
|
|
179 if ( walk + span > colCount ) span = colCount - walk;
|
|
|
180
|
|
|
181 long width = 0;
|
|
|
182 for( size_t sub = 0; sub < span; ++ sub ) {
|
|
|
183 width += (long)this->GetSubItemWidth(walk + sub);
|
|
|
184 }
|
|
|
185
|
|
|
186 if (xWalk + width > pt.x) return walk;
|
|
|
187 xWalk += width;
|
|
|
188 _walk += span;
|
|
|
189 }
|
|
|
190 return SIZE_MAX;
|
|
|
191 }
|
|
|
192
|
|
|
193 bool CListControlHeaderImpl::OnClickedSpecial(DWORD status, CPoint pt) {
|
|
|
194
|
|
|
195 const DWORD maskButtons = MK_LBUTTON | MK_RBUTTON | MK_MBUTTON | MK_XBUTTON1 | MK_XBUTTON2;
|
|
|
196
|
|
|
197 if ( (status & maskButtons) != MK_LBUTTON ) return false;
|
|
|
198
|
|
|
199 if (!GetCellTypeSupported()) return false;
|
|
|
200
|
|
|
201 size_t item; size_t subItem;
|
|
|
202 if (! this->GetItemAtPointAbsEx( this->PointClientToAbs(pt), item, subItem ) ) {
|
|
|
203 return false;
|
|
|
204 }
|
|
|
205
|
|
|
206 auto cellType = GetCellType(item, subItem);
|
|
|
207 if ( !CellTypeReactsToMouseOver( cellType ) ) return false;
|
|
|
208 auto rcHot = CellHotRect( item, subItem, cellType );
|
|
|
209 if (!rcHot.PtInRect( pt )) return false;
|
|
|
210
|
|
|
211 SetPressedItem(item, subItem);
|
|
|
212 mySetCapture([=](UINT msg, DWORD newStatus, CPoint pt) {
|
|
|
213
|
|
|
214 if (msg == WM_MOUSELEAVE) {
|
|
|
215 ClearPressedItem(); return false;
|
|
|
216 }
|
|
|
217
|
|
|
218 {
|
|
|
219 CRect rc = this->GetClientRectHook();
|
|
|
220 if (!rc.PtInRect(pt)) {
|
|
|
221 ClearPressedItem(); return false;
|
|
|
222 }
|
|
|
223 }
|
|
|
224
|
|
|
225 size_t newItem, newSubItem;
|
|
|
226 if (!this->GetItemAtPointAbsEx(this->PointClientToAbs(pt), newItem, newSubItem) || newItem != item || newSubItem != subItem) {
|
|
|
227 ClearPressedItem(); return false;
|
|
|
228 }
|
|
|
229
|
|
|
230 DWORD buttons = newStatus & maskButtons;
|
|
|
231 if (buttons == 0) {
|
|
|
232 // button released?
|
|
|
233 this->defer( [=] {
|
|
|
234 OnSubItemClicked(item, subItem, pt);
|
|
|
235 } );
|
|
|
236 ClearPressedItem(); return false;
|
|
|
237 }
|
|
|
238 if (buttons != MK_LBUTTON) {
|
|
|
239 // another button pressed?
|
|
|
240 ClearPressedItem(); return false;
|
|
|
241 }
|
|
|
242
|
|
|
243 return true;
|
|
|
244 });
|
|
|
245 return true;
|
|
|
246 }
|
|
|
247
|
|
|
248 bool CListControlHeaderImpl::CellTypeUsesSpecialHitTests( cellType_t ct ) {
|
|
|
249 if ( ct == nullptr ) return false;
|
|
|
250 return ct->SuppressRowSelect();
|
|
|
251 }
|
|
|
252
|
|
|
253 bool CListControlHeaderImpl::OnClickedSpecialHitTest(CPoint pt) {
|
|
|
254 if ( ! GetCellTypeSupported() ) return false;
|
|
|
255 auto ct = GetCellTypeAtPointAbs( PointClientToAbs( pt ) );
|
|
|
256 return CellTypeUsesSpecialHitTests(ct);
|
|
|
257 }
|
|
|
258
|
|
|
259 void CListControlHeaderImpl::OnItemClicked(t_size item, CPoint pt) {
|
|
|
260 t_size subItem = SubItemFromPointAbs( PointClientToAbs( pt ) );
|
|
|
261 if (subItem != SIZE_MAX) {
|
|
|
262 if ( this->GetCellTypeSupported() ) {
|
|
|
263 auto ct = this->GetCellType(item, subItem );
|
|
|
264 // we don't handle hyperlink & button clicks thru here
|
|
|
265 if (CellTypeUsesSpecialHitTests(ct)) return;
|
|
|
266 }
|
|
|
267 OnSubItemClicked(item, subItem, pt);
|
|
|
268 }
|
|
|
269 }
|
|
|
270
|
|
|
271 std::vector<int> CListControlHeaderImpl::GetColumnOrderArray() const {
|
|
|
272 const size_t cCount = this->GetColumnCount();
|
|
|
273 std::vector<int> order;
|
|
|
274 if ( cCount > 0 ) {
|
|
|
275 order.resize(cCount);
|
|
|
276 if (IsHeaderEnabled()) {
|
|
|
277 PFC_ASSERT(m_header.IsWindow());
|
|
|
278 PFC_ASSERT(m_header.GetItemCount() == (int)cCount);
|
|
|
279 WIN32_OP_D(m_header.GetOrderArray((int)cCount, &order[0]));
|
|
|
280 } else {
|
|
|
281 for (size_t c = 0; c < cCount; ++c) order[c] = (int)c;
|
|
|
282 }
|
|
|
283 }
|
|
|
284 return order;
|
|
|
285 }
|
|
|
286
|
|
|
287 void CListControlHeaderImpl::RenderItemText(t_size item,const CRect & itemRect,const CRect & updateRect,CDCHandle dc, bool allowColors) {
|
|
|
288
|
|
|
289 int xWalk = itemRect.left;
|
|
|
290 CRect subItemRect(itemRect);
|
|
|
291 auto order = GetColumnOrderArray();
|
|
|
292 const size_t cCount = order.size();
|
|
|
293 SelectObjectScope fontScope(dc,GetFont());
|
|
|
294 for(t_size _walk = 0; _walk < cCount; ) {
|
|
|
295 const t_size walk = order[_walk];
|
|
|
296
|
|
|
297 size_t span = GetSubItemSpan(item, walk);
|
|
|
298
|
|
|
299 PFC_ASSERT( walk == _walk || span == 1 );
|
|
|
300
|
|
|
301 int width = GetSubItemWidth(walk);
|
|
|
302
|
|
|
303 if ( span > 1 ) {
|
|
|
304 if ( walk + span > cCount ) span = cCount - walk;
|
|
|
305 for( size_t extraWalk = 1; extraWalk < span; ++ extraWalk ) {
|
|
|
306 width += GetSubItemWidth(walk + extraWalk);
|
|
|
307 }
|
|
|
308 }
|
|
|
309
|
|
|
310 subItemRect.left = xWalk; subItemRect.right = xWalk + width;
|
|
|
311 CRect test;
|
|
|
312 if (test.IntersectRect(itemRect, subItemRect)) {
|
|
|
313 CRect subUpdate;
|
|
|
314 if (subUpdate.IntersectRect(subItemRect, updateRect)) {
|
|
|
315 DCStateScope scope(dc);
|
|
|
316 if (dc.IntersectClipRect(subItemRect) != NULLREGION) {
|
|
|
317 RenderSubItemText(item, walk, subItemRect, subUpdate, dc, allowColors);
|
|
|
318 }
|
|
|
319 }
|
|
|
320 }
|
|
|
321 xWalk += width;
|
|
|
322
|
|
|
323 _walk += span;
|
|
|
324 }
|
|
|
325 }
|
|
|
326
|
|
|
327 t_size CListControlHeaderImpl::GetSubItemOrder(t_size subItem) const {
|
|
|
328 if ( ! IsHeaderEnabled( ) ) return subItem;
|
|
|
329 HDITEM hditem = {};
|
|
|
330 hditem.mask = HDI_ORDER;
|
|
|
331 WIN32_OP_D( m_header.GetItem( (int) subItem, &hditem ) );
|
|
|
332 return (t_size) hditem.iOrder;
|
|
|
333 }
|
|
|
334
|
|
|
335 size_t CListControlHeaderImpl::GetSubItemSpan(size_t row, size_t column) const {
|
|
|
336 (void)row; (void)column;
|
|
|
337 return 1;
|
|
|
338 }
|
|
|
339
|
|
|
340 uint32_t CListControlHeaderImpl::GetSubItemWidth(t_size subItem) const {
|
|
|
341 if ( ! IsHeaderEnabled( ) ) {
|
|
|
342 // Should be overridden for custom columns layout
|
|
|
343 PFC_ASSERT( GetColumnCount() == 1 );
|
|
|
344 PFC_ASSERT( subItem == 0 );
|
|
|
345 return GetItemWidth();
|
|
|
346 }
|
|
|
347
|
|
|
348 if ( subItem < m_colRuntime.size() ) return m_colRuntime[subItem].m_widthPixels;
|
|
|
349 PFC_ASSERT( !"bad column idx");
|
|
|
350 return 0;
|
|
|
351 }
|
|
|
352
|
|
|
353 int CListControlHeaderImpl::GetHeaderItemWidth( int which ) {
|
|
|
354 HDITEM hditem = {};
|
|
|
355 hditem.mask = HDI_WIDTH;
|
|
|
356 WIN32_OP_D( m_header.GetItem( which, &hditem) );
|
|
|
357 return hditem.cxy;
|
|
|
358 }
|
|
|
359
|
|
|
360 void CListControlHeaderImpl::OnColumnsChanged() {
|
|
|
361 if ( IsHeaderEnabled() ) {
|
|
|
362 for( size_t walk = 0; walk < m_colRuntime.size(); ++ walk ) {
|
|
|
363 m_colRuntime[walk].m_widthPixels = GetHeaderItemWidth( (int) walk );
|
|
|
364 }
|
|
|
365 RecalcItemWidth();
|
|
|
366 }
|
|
|
367 this->OnViewAreaChanged();
|
|
|
368 }
|
|
|
369
|
|
|
370 void CListControlHeaderImpl::ResetColumns(bool update) {
|
|
|
371 m_colRuntime.clear();
|
|
|
372 m_itemWidth = 0;
|
|
|
373 PFC_ASSERT(IsHeaderEnabled());
|
|
|
374 for(;;) {
|
|
|
375 int count = m_header.GetItemCount();
|
|
|
376 if (count <= 0) break;
|
|
|
377 m_header.DeleteItem(count - 1);
|
|
|
378 }
|
|
|
379 if (update) OnColumnsChanged();
|
|
|
380 }
|
|
|
381
|
|
|
382 void CListControlHeaderImpl::SetColumn( size_t which, const char * label, DWORD fmtFlags, bool updateView) {
|
|
|
383 PFC_ASSERT( IsHeaderEnabled() );
|
|
|
384 pfc::stringcvt::string_os_from_utf8 labelOS;
|
|
|
385
|
|
|
386 HDITEM item = {};
|
|
|
387
|
|
|
388 if (label != nullptr) {
|
|
|
389 if (which < m_colRuntime.size()) m_colRuntime[which].m_text = label;
|
|
|
390
|
|
|
391 labelOS.convert(label);
|
|
|
392 item.mask |= HDI_TEXT;
|
|
|
393 item.pszText = const_cast<TCHAR*>(labelOS.get_ptr());
|
|
|
394 }
|
|
|
395 if (fmtFlags != UINT32_MAX) {
|
|
|
396 item.mask |= HDI_FORMAT;
|
|
|
397 item.fmt = fmtFlags | HDF_STRING;
|
|
|
398 }
|
|
|
399
|
|
|
400 m_header.SetItem( (int) which, &item );
|
|
|
401
|
|
|
402 if (updateView) OnColumnsChanged();
|
|
|
403 }
|
|
|
404
|
|
|
405 void CListControlHeaderImpl::GetColumnText(size_t which, pfc::string_base & out) const {
|
|
|
406 if (which < m_colRuntime.size()) {
|
|
|
407 out = m_colRuntime[which].m_text.c_str();
|
|
|
408 } else {
|
|
|
409 out = "";
|
|
|
410 }
|
|
|
411 }
|
|
|
412
|
|
|
413 void CListControlHeaderImpl::ResizeColumn(t_size index, t_uint32 userWidth, bool updateView) {
|
|
|
414 PFC_ASSERT( IsHeaderEnabled() );
|
|
|
415 PFC_ASSERT( index < m_colRuntime.size() );
|
|
|
416 auto& rt = m_colRuntime[index];
|
|
|
417 rt.m_userWidth = userWidth;
|
|
|
418 if (rt.autoWidth()) {
|
|
|
419 this->ProcessAutoWidth();
|
|
|
420 } else {
|
|
|
421 auto widthPixels = columWidthToPixels(userWidth);
|
|
|
422 rt.m_widthPixels = widthPixels;
|
|
|
423 HDITEM item = {};
|
|
|
424 item.mask = HDI_WIDTH;
|
|
|
425 item.cxy = widthPixels;
|
|
|
426 { pfc::vartoggle_t<bool> scope(m_ownColumnsChange, true); m_header.SetItem((int)index, &item); }
|
|
|
427 RecalcItemWidth();
|
|
|
428 if (updateView) OnColumnsChanged();
|
|
|
429 }
|
|
|
430 }
|
|
|
431
|
|
|
432 void CListControlHeaderImpl::DeleteColumns( pfc::bit_array const & mask, bool updateView ) {
|
|
|
433 int nDeleted = 0;
|
|
|
434 const size_t oldCount = GetColumnCount();
|
|
|
435 mask.for_each(true, 0, oldCount, [&] (size_t idx) {
|
|
|
436 int iDelete = (int) idx - nDeleted;
|
|
|
437 bool bDeleted = m_header.DeleteItem( iDelete );
|
|
|
438 PFC_ASSERT( bDeleted );
|
|
|
439 if ( bDeleted ) ++ nDeleted;
|
|
|
440 } );
|
|
|
441
|
|
|
442 pfc::remove_mask_t( m_colRuntime, mask );
|
|
|
443
|
|
|
444 ColumnWidthFix();
|
|
|
445
|
|
|
446 if (updateView) {
|
|
|
447 OnColumnsChanged();
|
|
|
448 }
|
|
|
449
|
|
|
450 }
|
|
|
451
|
|
|
452 bool CListControlHeaderImpl::DeleteColumn(size_t index, bool update) {
|
|
|
453 PFC_ASSERT( IsHeaderEnabled() );
|
|
|
454
|
|
|
455 if (!m_header.DeleteItem( (int) index )) return false;
|
|
|
456
|
|
|
457 pfc::remove_mask_t( m_colRuntime, pfc::bit_array_one( index ) );
|
|
|
458
|
|
|
459 ColumnWidthFix();
|
|
|
460
|
|
|
461 if (update) {
|
|
|
462 OnColumnsChanged();
|
|
|
463 }
|
|
|
464
|
|
|
465 return true;
|
|
|
466 }
|
|
|
467
|
|
|
468 void CListControlHeaderImpl::SetSortIndicator( size_t whichColumn, bool isUp ) {
|
|
|
469 HeaderControl_SetSortIndicator( GetHeaderCtrl(), (int) whichColumn, isUp );
|
|
|
470 }
|
|
|
471
|
|
|
472 void CListControlHeaderImpl::ClearSortIndicator() {
|
|
|
473 HeaderControl_SetSortIndicator(GetHeaderCtrl(), -1, false);
|
|
|
474 }
|
|
|
475
|
|
|
476 bool CListControlHeaderImpl::HaveAutoWidthContentColumns() const {
|
|
|
477 for( auto i = m_colRuntime.begin(); i != m_colRuntime.end(); ++ i ) {
|
|
|
478 if ( i->autoWidthContent() ) return true;
|
|
|
479 }
|
|
|
480 return false;
|
|
|
481 }
|
|
|
482 bool CListControlHeaderImpl::HaveAutoWidthColumns() const {
|
|
|
483 for( auto i = m_colRuntime.begin(); i != m_colRuntime.end(); ++ i ) {
|
|
|
484 if ( i->autoWidth() ) return true;
|
|
|
485 }
|
|
|
486 return false;
|
|
|
487 }
|
|
|
488 void CListControlHeaderImpl::AddColumnEx( const char * label, uint32_t widthPixelsAt96DPI, DWORD fmtFlags, bool update ) {
|
|
|
489 uint32_t w = widthPixelsAt96DPI;
|
|
|
490 if ( w <= columnWidthMax ) {
|
|
|
491 w = MulDiv( w, m_dpi.cx, 96 );
|
|
|
492 }
|
|
|
493 AddColumn( label, w, fmtFlags, update );
|
|
|
494 }
|
|
|
495
|
|
|
496 void CListControlHeaderImpl::AddColumnDLU( const char * label, uint32_t widthDLU, DWORD fmtFlags, bool update ) {
|
|
|
497 uint32_t w = widthDLU;
|
|
|
498 if ( w <= columnWidthMax ) {
|
|
|
499 w = ::MapDialogWidth( GetParent(), w );
|
|
|
500 }
|
|
|
501 AddColumn( label, w, fmtFlags, update );
|
|
|
502 }
|
|
|
503 void CListControlHeaderImpl::AddColumnF( const char * label, float widthF, DWORD fmtFlags, bool update) {
|
|
|
504 uint32_t w = columnWidthMax;
|
|
|
505 if ( widthF >= 0 ) {
|
|
|
506 w = pfc::rint32( widthF * m_dpi.cx / 96.0f );
|
|
|
507 }
|
|
|
508 AddColumn( label, w, fmtFlags, update );
|
|
|
509 }
|
|
|
510 void CListControlHeaderImpl::AddColumn(const char * label, uint32_t userWidth, DWORD fmtFlags,bool update) {
|
|
|
511 // userWidth is either UINT32_MAX or desired width in pixels
|
|
|
512 PFC_ASSERT(IsWindow());
|
|
|
513 if (! IsHeaderEnabled( ) ) InitializeHeaderCtrl();
|
|
|
514
|
|
|
515 uint32_t widthPixels = 0;
|
|
|
516
|
|
|
517 pfc::stringcvt::string_os_from_utf8 labelOS(label);
|
|
|
518 HDITEM item = {};
|
|
|
519 item.mask = HDI_TEXT | HDI_FORMAT;
|
|
|
520 if ( userWidth <= columnWidthMax ) {
|
|
|
521 widthPixels = columWidthToPixels(userWidth);
|
|
|
522 item.cxy = widthPixels;
|
|
|
523 item.mask |= HDI_WIDTH;
|
|
|
524 }
|
|
|
525
|
|
|
526 item.pszText = const_cast<TCHAR*>(labelOS.get_ptr());
|
|
|
527 item.fmt = HDF_STRING | fmtFlags;
|
|
|
528 int iColumn;
|
|
|
529 WIN32_OP_D( (iColumn = m_header.InsertItem(m_header.GetItemCount(),&item) ) >= 0 );
|
|
|
530 colRuntime_t rt;
|
|
|
531 rt.m_text = label;
|
|
|
532 rt.m_userWidth = userWidth;
|
|
|
533 m_itemWidth += widthPixels;
|
|
|
534 rt.m_widthPixels = widthPixels;
|
|
|
535 m_colRuntime.push_back( std::move(rt) );
|
|
|
536
|
|
|
537 if (update) OnColumnsChanged();
|
|
|
538
|
|
|
539 ProcessAutoWidth();
|
|
|
540 }
|
|
|
541
|
|
|
542 float CListControlHeaderImpl::GetColumnWidthF(size_t subItem) const {
|
|
|
543 auto w = GetSubItemWidth(subItem);
|
|
|
544 return (float) w * 96.0f / (float)m_dpi.cx;
|
|
|
545 }
|
|
|
546
|
|
|
547 void CListControlHeaderImpl::RenderBackground(CDCHandle dc, CRect const& rc) {
|
|
|
548 __super::RenderBackground(dc,rc);
|
|
|
549 #if 0
|
|
|
550 if ( m_drawLineBelowHeader && IsHeaderEnabled()) {
|
|
|
551 CRect rcHeader;
|
|
|
552 if (m_header.GetWindowRect(rcHeader)) {
|
|
|
553 // Draw a grid line below header
|
|
|
554 int y = rcHeader.Height();
|
|
|
555 if ( y >= rc.top && y < rc.bottom ) {
|
|
|
556 CDCPen pen(dc, GridColor());
|
|
|
557 SelectObjectScope scope(dc, pen);
|
|
|
558 dc.MoveTo(rc.left, y);
|
|
|
559 dc.LineTo(rc.right, y);
|
|
|
560 }
|
|
|
561 }
|
|
|
562 }
|
|
|
563 #endif
|
|
|
564 }
|
|
|
565
|
|
|
566 CRect CListControlHeaderImpl::GetClientRectHook() const {
|
|
|
567 CRect rcClient = __super::GetClientRectHook();
|
|
|
568 if (m_header != NULL) {
|
|
|
569 PFC_ASSERT( m_header.IsWindow() );
|
|
|
570 CRect rcHeader;
|
|
|
571 if (m_header.GetWindowRect(rcHeader)) {
|
|
|
572 int h = rcHeader.Height();
|
|
|
573 if ( m_headerLine != NULL ) h += lineBelowHeaderCY;
|
|
|
574 rcClient.top = pfc::min_t(rcClient.bottom,rcClient.top + h);
|
|
|
575 }
|
|
|
576 }
|
|
|
577 return rcClient;
|
|
|
578 }
|
|
|
579
|
|
|
580 CRect CListControlHeaderImpl::GetItemTextRectHook(size_t, size_t, CRect const & rc) const {
|
|
|
581 return GetItemTextRect( rc );
|
|
|
582 }
|
|
|
583
|
|
|
584 CRect CListControlHeaderImpl::GetItemTextRect(const CRect & itemRect) const {
|
|
|
585 CRect rc ( itemRect );
|
|
|
586 rc.DeflateRect(GetColumnSpacing(),0);
|
|
|
587 return rc;
|
|
|
588 }
|
|
|
589
|
|
|
590 void CListControlHeaderImpl::SetColumnSort(t_size which, bool isUp) {
|
|
|
591 HeaderControl_SetSortIndicator(m_header,(int)which,isUp);
|
|
|
592 }
|
|
|
593
|
|
|
594 void CListControlHeaderImpl::SetColumnFormat(t_size which, DWORD format) {
|
|
|
595 HDITEM item = {};
|
|
|
596 item.mask = HDI_FORMAT;
|
|
|
597 item.fmt = HDF_STRING | format;
|
|
|
598 WIN32_OP_D( m_header.SetItem((int)which,&item) );
|
|
|
599 }
|
|
|
600
|
|
|
601 DWORD CListControlHeaderImpl::GetColumnFormat(t_size which) const {
|
|
|
602 if (!IsHeaderEnabled()) return HDF_LEFT;
|
|
|
603 HDITEM hditem = {};
|
|
|
604 hditem.mask = HDI_FORMAT;
|
|
|
605 WIN32_OP_D( m_header.GetItem( (int) which, &hditem) );
|
|
|
606 return hditem.fmt;
|
|
|
607 }
|
|
|
608
|
|
|
609 BOOL CListControlHeaderImpl::OnSetCursor(CWindow, UINT nHitTest, UINT message) {
|
|
|
610 (void)nHitTest;
|
|
|
611 if ( message != 0 && GetCellTypeSupported() ) {
|
|
|
612 CPoint pt( (LPARAM) GetMessagePos() );
|
|
|
613 WIN32_OP_D( ScreenToClient( &pt ) );
|
|
|
614 size_t item, subItem;
|
|
|
615 if (GetItemAtPointAbsEx(this->PointClientToAbs(pt), item, subItem)) {
|
|
|
616 auto ct = GetCellType(item, subItem);
|
|
|
617 if (CellTypeReactsToMouseOver(ct)) {
|
|
|
618 auto rc = CellHotRect(item, subItem, ct);
|
|
|
619 if (PtInRect(rc, pt)) {
|
|
|
620 auto cursor = ct->HotCursor();
|
|
|
621 if (cursor) {
|
|
|
622 SetCursor(cursor); return TRUE;
|
|
|
623 }
|
|
|
624 }
|
|
|
625 }
|
|
|
626 }
|
|
|
627 }
|
|
|
628 SetMsgHandled(FALSE); return FALSE;
|
|
|
629 }
|
|
|
630
|
|
|
631 bool CListControlHeaderImpl::GetItemAtPointAbsEx(CPoint pt, size_t & outItem, size_t & outSubItem) const {
|
|
|
632 size_t item, subItem;
|
|
|
633 if (ItemFromPointAbs(pt, item)) {
|
|
|
634 subItem = SubItemFromPointAbs(pt);
|
|
|
635 if (subItem != SIZE_MAX) {
|
|
|
636 outItem = item; outSubItem = subItem; return true;
|
|
|
637 }
|
|
|
638 }
|
|
|
639 return false;
|
|
|
640 }
|
|
|
641
|
|
|
642 CListControlHeaderImpl::cellType_t CListControlHeaderImpl::GetCellTypeAtPointAbs(CPoint pt) const {
|
|
|
643 size_t item, subItem;
|
|
|
644 if ( GetItemAtPointAbsEx( pt, item, subItem) ) {
|
|
|
645 return GetCellType( item, subItem );
|
|
|
646 }
|
|
|
647 return nullptr;
|
|
|
648 }
|
|
|
649
|
|
|
650 CListCell * CListControlHeaderImpl::GetCellType( size_t item, size_t subItem ) const {
|
|
|
651 (void)item; (void)subItem;
|
|
|
652 return &PFC_SINGLETON(CListCell_Text);
|
|
|
653 }
|
|
|
654
|
|
|
655 void CListControlHeaderImpl::RenderSubItemText(t_size item, t_size subItem,const CRect & subItemRect,const CRect & updateRect,CDCHandle dc, bool allowColors) {
|
|
|
656 (void)updateRect;
|
|
|
657 const auto cellType = GetCellType( item, subItem );
|
|
|
658 if ( cellType == nullptr ) {
|
|
|
659 return;
|
|
|
660 }
|
|
|
661 pfc::string_formatter label;
|
|
|
662 const bool bHaveText = GetSubItemText(item,subItem,label);
|
|
|
663 if (! bHaveText ) {
|
|
|
664 label = ""; //sanity
|
|
|
665 }
|
|
|
666
|
|
|
667 pfc::stringcvt::string_os_from_utf8_fast labelOS ( label );
|
|
|
668 CListCell::DrawContentArg_t arg;
|
|
|
669 arg.darkMode = this->GetDarkMode();
|
|
|
670 arg.hdrFormat = GetColumnFormat( subItem );
|
|
|
671 arg.subItemRect = subItemRect;
|
|
|
672 arg.dc = dc;
|
|
|
673 arg.text = labelOS.get_ptr();
|
|
|
674 arg.allowColors = allowColors;
|
|
|
675 bool bPressed;
|
|
|
676 if ( cellType->IsToggle() ) bPressed = this->GetCellCheckState(item, subItem);
|
|
|
677 else bPressed = (item == m_pressedItem) && (subItem == m_pressedSubItem);
|
|
|
678 bool bHot = (item == m_hotItem) && ( subItem == m_hotSubItem );
|
|
|
679 if ( bPressed ) arg.cellState |= CListCell::cellState_pressed;
|
|
|
680 if ( bHot ) arg.cellState|= CListCell::cellState_hot;
|
|
|
681 arg.rcText = GetItemTextRectHook(item, subItem, subItemRect);
|
|
|
682 arg.rcHot = CellHotRect(item, subItem, cellType, subItemRect);
|
|
|
683
|
|
|
684 auto strTheme = cellType->Theme();
|
|
|
685 if ( strTheme != nullptr ) {
|
|
|
686 arg.theme = themeFor( strTheme ).m_theme;
|
|
|
687 }
|
|
|
688 arg.colorHighlight = GetSysColorHook(colorHighlight);
|
|
|
689
|
|
|
690 arg.thisWnd = m_hWnd;
|
|
|
691
|
|
|
692 if (this->IsSubItemGrayed( item, subItem ) ) {
|
|
|
693 arg.cellState |= CListCell::cellState_disabled;
|
|
|
694 }
|
|
|
695
|
|
|
696 if (this->RenderCellImageTest(item, subItem)) {
|
|
|
697 arg.imageRenderer = [this, item, subItem](CDCHandle dc, const CRect& rc) { this->RenderCellImage(item, subItem, dc, rc); };
|
|
|
698 }
|
|
|
699
|
|
|
700 CFontHandle fontRestore;
|
|
|
701 CFont fontOverride;
|
|
|
702
|
|
|
703 LOGFONT data;
|
|
|
704 if (dc.GetCurrentFont().GetLogFont(&data)) {
|
|
|
705 if ( cellType->ApplyTextStyle( data, CellTextScale(item, subItem ), arg.cellState ) ) {
|
|
|
706 if (fontOverride.CreateFontIndirect( & data )) {
|
|
|
707 fontRestore = dc.SelectFont( fontOverride );
|
|
|
708 }
|
|
|
709 }
|
|
|
710 }
|
|
|
711 cellType->DrawContent( arg );
|
|
|
712
|
|
|
713 if ( fontRestore != NULL ) dc.SelectFont( fontRestore );
|
|
|
714 }
|
|
|
715
|
|
|
716 void CListControlHeaderImpl::RenderGroupHeaderText2(size_t baseItem,const CRect & headerRect,const CRect & updateRect,CDCHandle dc) {
|
|
|
717 (void)updateRect;
|
|
|
718 pfc::string_formatter label;
|
|
|
719 if (GetGroupHeaderText2(baseItem,label)) {
|
|
|
720 SelectObjectScope fontScope(dc,GetGroupHeaderFont());
|
|
|
721 pfc::stringcvt::string_os_from_utf8 cvt(label);
|
|
|
722 CRect contentRect(GetItemTextRect(headerRect));
|
|
|
723 dc.DrawText(cvt,(int)cvt.length(),contentRect,DT_NOPREFIX | DT_END_ELLIPSIS | DT_SINGLELINE | DT_VCENTER | DT_LEFT );
|
|
|
724 SIZE txSize;
|
|
|
725 const int lineSpacing = contentRect.Height() / 2;
|
|
|
726 if (dc.GetTextExtent(cvt,(int)cvt.length(),&txSize)) {
|
|
|
727 if (txSize.cx + lineSpacing < contentRect.Width()) {
|
|
|
728 const CPoint center = contentRect.CenterPoint();
|
|
|
729 const CPoint pt1(contentRect.left + txSize.cx + lineSpacing, center.y), pt2(contentRect.right, center.y);
|
|
|
730 const COLORREF lineColor = PaintUtils::BlendColor(dc.GetTextColor(),dc.GetBkColor(),25);
|
|
|
731
|
|
|
732 #ifndef CListControl_ScrollWindowFix
|
|
|
733 #error FIXME CMemoryDC needed
|
|
|
734 #endif
|
|
|
735 PaintUtils::DrawSmoothedLine(dc, pt1, pt2, lineColor, 1.0 * (double)m_dpi.cy / 96.0);
|
|
|
736 }
|
|
|
737 }
|
|
|
738 }
|
|
|
739 }
|
|
|
740
|
|
|
741 uint32_t CListControlHeaderImpl::GetOptimalColumnWidthFixed(const char * fixedText, bool pad) const {
|
|
|
742 CWindowDC dc(*this);
|
|
|
743 SelectObjectScope fontScope(dc, GetFont());
|
|
|
744 GetOptimalWidth_Cache cache;
|
|
|
745 cache.m_dc = dc;
|
|
|
746 cache.m_stringTemp = fixedText;
|
|
|
747 uint32_t ret = cache.GetStringTempWidth();
|
|
|
748 if ( pad ) ret += this->GetColumnSpacing() * 2;
|
|
|
749 return ret;
|
|
|
750 }
|
|
|
751
|
|
|
752 t_uint32 CListControlHeaderImpl::GetOptimalSubItemWidth(t_size item, t_size subItem, GetOptimalWidth_Cache & cache) const {
|
|
|
753 const t_uint32 base = this->GetColumnSpacing() * 2;
|
|
|
754 if (GetSubItemText(item,subItem,cache.m_stringTemp)) {
|
|
|
755 return base + cache.GetStringTempWidth();
|
|
|
756 } else {
|
|
|
757 return base;
|
|
|
758 }
|
|
|
759 }
|
|
|
760
|
|
|
761 t_uint32 CListControlHeaderImpl::GetOptimalWidth_Cache::GetStringTempWidth() {
|
|
|
762 if (m_stringTemp.replace_string_ex(m_stringTempFixAmpersands, "&", "&&") > 0) {
|
|
|
763 m_convertTemp.convert(m_stringTempFixAmpersands);
|
|
|
764 } else {
|
|
|
765 m_convertTemp.convert(m_stringTemp);
|
|
|
766 }
|
|
|
767 return PaintUtils::TextOutColors_CalcWidth(m_dc, m_convertTemp);
|
|
|
768 }
|
|
|
769
|
|
|
770 t_uint32 CListControlHeaderImpl::GetOptimalColumnWidth(t_size which, GetOptimalWidth_Cache & cache) const {
|
|
|
771 const t_size totalItems = GetItemCount();
|
|
|
772 t_uint32 val = 0;
|
|
|
773 for(t_size item = 0; item < totalItems; ++item) {
|
|
|
774 pfc::max_acc( val, GetOptimalSubItemWidth( item, which, cache ) );
|
|
|
775 }
|
|
|
776 return val;
|
|
|
777 }
|
|
|
778
|
|
|
779 t_uint32 CListControlHeaderImpl::GetOptimalSubItemWidthSimple(t_size item, t_size subItem) const {
|
|
|
780 CWindowDC dc(*this);
|
|
|
781 SelectObjectScope fontScope(dc, GetFont() );
|
|
|
782 GetOptimalWidth_Cache cache;
|
|
|
783 cache.m_dc = dc;
|
|
|
784 return GetOptimalSubItemWidth(item, subItem, cache);
|
|
|
785 }
|
|
|
786
|
|
|
787 LRESULT CListControlHeaderImpl::OnKeyDown(UINT,WPARAM wp,LPARAM,BOOL& bHandled) {
|
|
|
788 switch(wp) {
|
|
|
789 case VK_ADD:
|
|
|
790 if (IsKeyPressed(VK_CONTROL)) {
|
|
|
791 AutoColumnWidths();
|
|
|
792 return 0;
|
|
|
793 }
|
|
|
794 break;
|
|
|
795 }
|
|
|
796 bHandled = FALSE;
|
|
|
797 return 0;
|
|
|
798 }
|
|
|
799
|
|
|
800 uint32_t CListControlHeaderImpl::GetOptimalColumnWidth(size_t colIndex) const {
|
|
|
801 CWindowDC dc(*this);
|
|
|
802 SelectObjectScope fontScope(dc, GetFont());
|
|
|
803 GetOptimalWidth_Cache cache;
|
|
|
804 cache.m_dc = dc;
|
|
|
805 uint32_t ret = 0;
|
|
|
806 const auto itemCount = GetItemCount();
|
|
|
807 for (t_size walk = 0; walk < itemCount; ++walk) {
|
|
|
808 pfc::max_acc(ret, GetOptimalSubItemWidth(walk, colIndex, cache));
|
|
|
809 }
|
|
|
810 return ret;
|
|
|
811 }
|
|
|
812
|
|
|
813 void CListControlHeaderImpl::AutoColumnWidths(const pfc::bit_array & mask, bool expandLast) {
|
|
|
814 PFC_ASSERT( IsHeaderEnabled() );
|
|
|
815 if (!IsHeaderEnabled()) return;
|
|
|
816 const t_size itemCount = GetItemCount();
|
|
|
817 if (itemCount == 0) return;
|
|
|
818 const t_size columnCount = (t_size) m_header.GetItemCount();
|
|
|
819 if (columnCount == 0) return;
|
|
|
820 pfc::array_t<t_uint32> widths; widths.set_size(columnCount); widths.fill_null();
|
|
|
821 {
|
|
|
822 CWindowDC dc(*this);
|
|
|
823 SelectObjectScope fontScope(dc,GetFont());
|
|
|
824 GetOptimalWidth_Cache cache;
|
|
|
825 cache.m_dc = dc;
|
|
|
826 for(t_size walk = 0; walk < itemCount; ++walk) {
|
|
|
827 for(t_size colWalk = mask.find_first(true,0,columnCount); colWalk < columnCount; colWalk = mask.find_next(true,colWalk,columnCount)) {
|
|
|
828 pfc::max_acc(widths[colWalk], GetOptimalSubItemWidth(walk,colWalk,cache));
|
|
|
829 }
|
|
|
830 }
|
|
|
831 }
|
|
|
832
|
|
|
833 // Enforce limit
|
|
|
834 for (auto& walk : widths) walk = columWidthToPixels(walk);
|
|
|
835
|
|
|
836 if (expandLast) {
|
|
|
837 uint32_t usedWidth = 0; size_t lastCol = SIZE_MAX;
|
|
|
838 pfc::array_t<int> order; order.set_size(columnCount);
|
|
|
839 WIN32_OP_D( m_header.GetOrderArray((int)columnCount,order.get_ptr()) );
|
|
|
840 for(size_t _walk = 0; _walk < columnCount; ++_walk) {
|
|
|
841 const size_t colWalk = (size_t) order[_walk];
|
|
|
842 PFC_ASSERT( colWalk < columnCount );
|
|
|
843 if (mask[colWalk]) {
|
|
|
844 lastCol = colWalk;
|
|
|
845 usedWidth += widths[colWalk];
|
|
|
846 } else {
|
|
|
847 usedWidth += GetSubItemWidth(colWalk);
|
|
|
848 }
|
|
|
849 }
|
|
|
850 if (lastCol != SIZE_MAX) {
|
|
|
851 t_uint32 clientWidth = this->GetClientRectHook().Width();
|
|
|
852 if (clientWidth > 0) --clientWidth; // $!@# scrollbar hack
|
|
|
853 if (usedWidth < clientWidth) {
|
|
|
854 widths[lastCol] += clientWidth - usedWidth;
|
|
|
855 }
|
|
|
856 }
|
|
|
857 }
|
|
|
858 for(t_size colWalk = mask.find_first(true,0,columnCount); colWalk < columnCount; colWalk = mask.find_next(true,colWalk,columnCount)) {
|
|
|
859 ResizeColumn(colWalk,widths[colWalk],false);
|
|
|
860 }
|
|
|
861 ProcessColumnsChange();
|
|
|
862 }
|
|
|
863
|
|
|
864 t_uint32 CListControlHeaderImpl::GetOptimalGroupHeaderWidth2(size_t baseItem) const {
|
|
|
865 CWindowDC dc(*this);
|
|
|
866 SelectObjectScope fontScope(dc,GetGroupHeaderFont());
|
|
|
867 GetOptimalWidth_Cache cache; cache.m_dc = dc;
|
|
|
868 const t_uint32 base = this->GetColumnSpacing() * 2;
|
|
|
869 if (GetGroupHeaderText2(baseItem,cache.m_stringTemp)) {
|
|
|
870 return base + cache.GetStringTempWidth();
|
|
|
871 } else {
|
|
|
872 return base;
|
|
|
873 }
|
|
|
874 }
|
|
|
875
|
|
|
876 size_t CListControlHeaderImpl::GetColumnCount() const {
|
|
|
877 if ( ! IsHeaderEnabled() ) return 1;
|
|
|
878 #if PFC_DEBUG
|
|
|
879 int iHeaderCount = m_header.GetItemCount();
|
|
|
880 PFC_ASSERT( m_colRuntime.size() == (size_t) iHeaderCount );
|
|
|
881 #endif
|
|
|
882 return m_colRuntime.size();
|
|
|
883 }
|
|
|
884
|
|
|
885 void CListControlHeaderImpl::ColumnWidthFix() {
|
|
|
886 if ( this->HaveAutoWidthColumns() ) {
|
|
|
887 ProcessAutoWidth();
|
|
|
888 } else {
|
|
|
889 RecalcItemWidth();
|
|
|
890 }
|
|
|
891 }
|
|
|
892
|
|
|
893 void CListControlHeaderImpl::ProcessAutoWidth() {
|
|
|
894 if ( HaveAutoWidthColumns() ) {
|
|
|
895 const int clientWidth = this->GetClientRectHook().Width();
|
|
|
896
|
|
|
897 if ( ! this->HaveAutoWidthContentColumns( ) && clientWidth == m_itemWidth) return;
|
|
|
898
|
|
|
899 const size_t count = GetColumnCount();
|
|
|
900 uint32_t totalNonAuto = 0;
|
|
|
901 size_t numAutoWidth = 0;
|
|
|
902 for(size_t walk = 0; walk < count; ++ walk ) {
|
|
|
903 if ( m_colRuntime[walk].autoWidth() ) {
|
|
|
904 ++ numAutoWidth;
|
|
|
905 } else {
|
|
|
906 totalNonAuto += GetSubItemWidth(walk);
|
|
|
907 }
|
|
|
908 }
|
|
|
909 int toDivide = clientWidth - totalNonAuto;
|
|
|
910 if ( toDivide < 0 ) toDivide = 0;
|
|
|
911
|
|
|
912 size_t numLeft = numAutoWidth;
|
|
|
913 auto worker = [&] ( size_t iCol ) {
|
|
|
914 auto & rt = m_colRuntime[iCol];
|
|
|
915 int lo = this->GetOptimalColumnWidthFixed( rt.m_text.c_str() );
|
|
|
916 if ( rt.autoWidthContent() ) {
|
|
|
917 int lo2 = this->GetOptimalColumnWidth( iCol );
|
|
|
918 if ( lo < lo2 ) lo = lo2;
|
|
|
919 }
|
|
|
920 int width = (int)(toDivide / numLeft);
|
|
|
921 if ( width < lo ) width = lo;
|
|
|
922
|
|
|
923 HDITEM item = {};
|
|
|
924 item.mask = HDI_WIDTH;
|
|
|
925 item.cxy = width;
|
|
|
926 WIN32_OP_D( m_header.SetItem( (int) iCol, &item ) );
|
|
|
927 rt.m_widthPixels = width;
|
|
|
928
|
|
|
929 if ( toDivide > width ) {
|
|
|
930 toDivide -= width;
|
|
|
931 } else {
|
|
|
932 toDivide = 0;
|
|
|
933 }
|
|
|
934 -- numLeft;
|
|
|
935
|
|
|
936 };
|
|
|
937 for( size_t iCol = 0; iCol < count; ++ iCol ) {
|
|
|
938 if (m_colRuntime[iCol].autoWidthContent() ) worker(iCol);
|
|
|
939 }
|
|
|
940 for( size_t iCol = 0; iCol < count; ++ iCol ) {
|
|
|
941 if ( m_colRuntime[iCol].autoWidthPlain() ) worker(iCol);
|
|
|
942 }
|
|
|
943
|
|
|
944 RecalcItemWidth();
|
|
|
945 OnColumnsChanged();
|
|
|
946 m_header.Invalidate();
|
|
|
947 }
|
|
|
948 }
|
|
|
949
|
|
|
950 void CListControlHeaderImpl::RecalcItemWidth() {
|
|
|
951 int total = 0;
|
|
|
952 const t_size count = GetColumnCount();
|
|
|
953 for(t_size walk = 0; walk < count; ++walk) total += GetSubItemWidth(walk);
|
|
|
954 m_itemWidth = total;
|
|
|
955 }
|
|
|
956
|
|
|
957 CRect CListControlHeaderImpl::GetSubItemRectAbs(t_size item,t_size subItem) const {
|
|
|
958 CRect rc = GetItemRectAbs(item);
|
|
|
959 auto order = GetColumnOrderArray();
|
|
|
960 const t_size colCount = order.size();
|
|
|
961 for(t_size _walk = 0; _walk < colCount; ++_walk) {
|
|
|
962 const t_size walk = (t_size) order[_walk];
|
|
|
963
|
|
|
964 t_size width = this->GetSubItemWidth(walk);
|
|
|
965 if (subItem == walk) {
|
|
|
966
|
|
|
967 size_t span = GetSubItemSpan(item, walk);
|
|
|
968 if ( walk + span > colCount ) span = colCount - walk;
|
|
|
969 for( size_t extra = 1; extra < span; ++ extra ) {
|
|
|
970 width += GetSubItemWidth( walk + extra);
|
|
|
971 }
|
|
|
972
|
|
|
973 rc.right = rc.left + (long)width;
|
|
|
974
|
|
|
975 return rc;
|
|
|
976 } else {
|
|
|
977 rc.left += (long)width;
|
|
|
978 }
|
|
|
979 }
|
|
|
980 throw pfc::exception_invalid_params();
|
|
|
981 }
|
|
|
982
|
|
|
983 CRect CListControlHeaderImpl::GetSubItemRect(t_size item,t_size subItem) const {
|
|
|
984 return RectAbsToClient(GetSubItemRectAbs(item, subItem));
|
|
|
985 }
|
|
|
986
|
|
|
987 void CListControlHeaderImpl::SetHotItem(size_t row, size_t column) {
|
|
|
988 if ( m_hotItem != row || m_hotSubItem != column ) {
|
|
|
989 const size_t total = GetItemCount();
|
|
|
990 if (m_hotItem < total) InvalidateRect(GetSubItemRect(m_hotItem, m_hotSubItem));
|
|
|
991 m_hotItem = row; m_hotSubItem = column;
|
|
|
992 if (m_hotItem < total) InvalidateRect(GetSubItemRect(m_hotItem, m_hotSubItem));
|
|
|
993 HotItemChanged(row, column);
|
|
|
994 }
|
|
|
995 }
|
|
|
996
|
|
|
997 void CListControlHeaderImpl::SetPressedItem(size_t row, size_t column) {
|
|
|
998 if (m_pressedItem != row || m_pressedSubItem != column) {
|
|
|
999 if (m_pressedItem != SIZE_MAX) InvalidateRect(GetSubItemRect(m_pressedItem, m_pressedSubItem));
|
|
|
1000 m_pressedItem = row; m_pressedSubItem = column;
|
|
|
1001 if (m_pressedItem != SIZE_MAX) InvalidateRect(GetSubItemRect(m_pressedItem, m_pressedSubItem));
|
|
|
1002 PressedItemChanged(row, column);
|
|
|
1003 }
|
|
|
1004 }
|
|
|
1005
|
|
|
1006 void CListControlHeaderImpl::SetCellCheckState(size_t item, size_t subItem, bool value) {
|
|
|
1007 ReloadItem(item); (void)subItem; (void)value;
|
|
|
1008 // Subclass deals with keeping track of state
|
|
|
1009 }
|
|
|
1010
|
|
|
1011 bool CListControlHeaderImpl::ToggleSelectedItemsHook(const pfc::bit_array & mask) {
|
|
|
1012 if (this->GetCellTypeSupported() ) {
|
|
|
1013 bool handled = false;
|
|
|
1014 bool setTo = true;
|
|
|
1015
|
|
|
1016 mask.walk(GetItemCount(), [&](size_t idx) {
|
|
|
1017 auto ct = this->GetCellType(idx, 0);
|
|
|
1018 if ( ct != nullptr && ct->IsToggle() ) {
|
|
|
1019 if ( ct->IsRadioToggle() ) {
|
|
|
1020 if (!handled) {
|
|
|
1021 handled = true;
|
|
|
1022 setTo = !this->GetCellCheckState(idx, 0);
|
|
|
1023 this->SetCellCheckState(idx, 0, setTo);
|
|
|
1024 }
|
|
|
1025 } else {
|
|
|
1026 if (!handled) {
|
|
|
1027 handled = true;
|
|
|
1028 setTo = ! this->GetCellCheckState(idx,0);
|
|
|
1029 }
|
|
|
1030 this->SetCellCheckState(idx,0,setTo);
|
|
|
1031 }
|
|
|
1032 }
|
|
|
1033 });
|
|
|
1034
|
|
|
1035 if (handled) return true;
|
|
|
1036 }
|
|
|
1037 return __super::ToggleSelectedItemsHook(mask);
|
|
|
1038 }
|
|
|
1039
|
|
|
1040 void CListControlHeaderImpl::OnSubItemClicked(t_size item, t_size subItem, CPoint pt) {
|
|
|
1041 auto ct = GetCellType(item, subItem);
|
|
|
1042 if (ct != nullptr) {
|
|
|
1043 if (ct->IsToggle()) {
|
|
|
1044 if (ct->HotRect(GetSubItemRect(item, subItem)).PtInRect(pt)) {
|
|
|
1045 this->SetCellCheckState(item, subItem, !GetCellCheckState(item, subItem));
|
|
|
1046 }
|
|
|
1047 } else if (ct->ClickToEdit()) {
|
|
|
1048 this->RequestEditItem(item, subItem);
|
|
|
1049 }
|
|
|
1050 }
|
|
|
1051 }
|
|
|
1052
|
|
|
1053
|
|
|
1054 bool CListControlHeaderImpl::AllowTypeFindInCell(size_t item, size_t subItem) const {
|
|
|
1055 auto cell = GetCellType( item, subItem );
|
|
|
1056 if ( cell == nullptr ) return false;
|
|
|
1057 return cell->AllowTypeFind();
|
|
|
1058 }
|
|
|
1059
|
|
|
1060 bool CListControlHeaderImpl::CellTypeReactsToMouseOver(cellType_t ct) {
|
|
|
1061 return ct != nullptr && ct->IsInteractive();
|
|
|
1062 }
|
|
|
1063
|
|
|
1064 CRect CListControlHeaderImpl::CellHotRect( size_t, size_t, cellType_t ct, CRect rcCell) {
|
|
|
1065 if ( ct != nullptr ) {
|
|
|
1066 return ct->HotRect(rcCell);
|
|
|
1067 }
|
|
|
1068 return rcCell;
|
|
|
1069 }
|
|
|
1070 CRect CListControlHeaderImpl::CellHotRect(size_t item, size_t subItem, cellType_t ct) {
|
|
|
1071 return CellHotRect( item, subItem, ct, GetSubItemRect( item, subItem ) );
|
|
|
1072 }
|
|
|
1073 void CListControlHeaderImpl::OnMouseMove(UINT nFlags, CPoint pt) {
|
|
|
1074 const DWORD maskButtons = MK_LBUTTON | MK_RBUTTON | MK_MBUTTON | MK_XBUTTON1 | MK_XBUTTON2;
|
|
|
1075 if (GetCellTypeSupported() && (nFlags & maskButtons) == 0 ) {
|
|
|
1076 size_t item; size_t subItem;
|
|
|
1077 if (this->GetItemAtPointAbsEx( PointClientToAbs(pt), item, subItem)) {
|
|
|
1078 auto ct = this->GetCellType( item, subItem );
|
|
|
1079 if (CellTypeReactsToMouseOver(ct) ) {
|
|
|
1080 auto rc = CellHotRect( item, subItem, ct );
|
|
|
1081 if ( PtInRect( rc, pt ) ) {
|
|
|
1082 const bool bTrack = ct != nullptr && ct->TracksMouseMove();
|
|
|
1083 SetHotItem(item, subItem);
|
|
|
1084
|
|
|
1085 if (bTrack) this->CellTrackMouseMove(item, subItem, WM_MOUSEMOVE, nFlags, pt);
|
|
|
1086
|
|
|
1087 mySetCapture([=](UINT msg, DWORD newStatus, CPoint pt) {
|
|
|
1088 if (msg == WM_MOUSELEAVE) {
|
|
|
1089 this->ClearHotItem();
|
|
|
1090 return false;
|
|
|
1091 }
|
|
|
1092
|
|
|
1093 if ((newStatus & maskButtons) != 0 || msg == WM_MOUSEWHEEL || msg == WM_MOUSEHWHEEL ) {
|
|
|
1094 // A button has been pressed or wheel has been moved
|
|
|
1095 this->ClearHotItem();
|
|
|
1096 mySetCaptureMsgHandled(FALSE);
|
|
|
1097 return false;
|
|
|
1098 }
|
|
|
1099
|
|
|
1100 if ( ! PtInRect( rc, pt ) || item >= GetItemCount() ) {
|
|
|
1101 // Left the rect
|
|
|
1102 this->ClearHotItem();
|
|
|
1103 this->mySetCaptureMsgHandled(FALSE);
|
|
|
1104 return false;
|
|
|
1105 }
|
|
|
1106 if (bTrack) {
|
|
|
1107 this->CellTrackMouseMove(item, subItem, msg, newStatus, pt);
|
|
|
1108 }
|
|
|
1109
|
|
|
1110 return true;
|
|
|
1111 });
|
|
|
1112 }
|
|
|
1113 }
|
|
|
1114 }
|
|
|
1115 }
|
|
|
1116 SetMsgHandled(FALSE);
|
|
|
1117 }
|
|
|
1118
|
|
|
1119 bool CListControlHeaderImpl::AllowScrollbar(bool vertical) const {
|
|
|
1120 if ( vertical ) {
|
|
|
1121 // vertical
|
|
|
1122 return true;
|
|
|
1123 } else {
|
|
|
1124 // horizontal
|
|
|
1125 if (! IsHeaderEnabled( ) ) return false; // no header?
|
|
|
1126 return true;
|
|
|
1127 }
|
|
|
1128 }
|
|
|
1129
|
|
|
1130 void CListControlHeaderImpl::OnDestroy() {
|
|
|
1131 m_colRuntime.clear();
|
|
|
1132 m_header = NULL;
|
|
|
1133 m_headerLine = NULL;
|
|
|
1134 m_headerDark = false;
|
|
|
1135 SetMsgHandled(FALSE);
|
|
|
1136 }
|
|
|
1137
|
|
|
1138
|
|
|
1139 uint32_t CListControlHeaderImpl::GetColumnsBlankWidth( size_t colExclude ) const {
|
|
|
1140 auto client = this->GetClientRectHook().Width();
|
|
|
1141 int item = GetItemWidth();
|
|
|
1142 if (colExclude) item -= GetSubItemWidth(colExclude);
|
|
|
1143 if ( item < 0 ) item = 0;
|
|
|
1144 if ( item < client ) return (uint32_t)( client - item );
|
|
|
1145 else return 0;
|
|
|
1146 }
|
|
|
1147
|
|
|
1148 void CListControlHeaderImpl::SizeColumnToContent( size_t which, uint32_t minWidth ) {
|
|
|
1149 auto width = this->GetOptimalColumnWidth( which );
|
|
|
1150 if ( width < minWidth ) width = minWidth;
|
|
|
1151 this->ResizeColumn( which, width );
|
|
|
1152 }
|
|
|
1153
|
|
|
1154 void CListControlHeaderImpl::SizeColumnToContentFillBlank( size_t which ) {
|
|
|
1155 this->SizeColumnToContent( which, this->GetColumnsBlankWidth(which) );
|
|
|
1156 }
|
|
|
1157
|
|
|
1158 HBRUSH CListControlHeaderImpl::OnCtlColorStatic(CDCHandle dc, CStatic wndStatic) {
|
|
|
1159 if ( wndStatic == m_headerLine ) {
|
|
|
1160 COLORREF col = GridColor();
|
|
|
1161 dc.SetDCBrushColor( col );
|
|
|
1162 return (HBRUSH) GetStockObject(DC_BRUSH);
|
|
|
1163 }
|
|
|
1164 SetMsgHandled(FALSE);
|
|
|
1165 return NULL;
|
|
|
1166 }
|
|
|
1167
|
|
|
1168 void CListControlHeaderImpl::ReloadData() {
|
|
|
1169 __super::ReloadData();
|
|
|
1170 if ( this->HaveAutoWidthContentColumns( ) ) {
|
|
|
1171 this->ColumnWidthFix();
|
|
|
1172 }
|
|
|
1173 }
|
|
|
1174
|
|
|
1175 void CListControlHeaderImpl::RenderGroupOverlay(size_t baseItem, const CRect& p_groupWhole, const CRect& p_updateRect, CDCHandle dc) {
|
|
|
1176 CRect subItemRect = p_groupWhole;
|
|
|
1177 t_uint32 xWalk = p_groupWhole.left;
|
|
|
1178 auto order = GetColumnOrderArray();
|
|
|
1179 const size_t cCount = order.size();
|
|
|
1180 for (t_size _walk = 0; _walk < cCount; ) {
|
|
|
1181 const t_size walk = order[_walk];
|
|
|
1182
|
|
|
1183 t_uint32 width = GetSubItemWidth(walk);
|
|
|
1184
|
|
|
1185 subItemRect.left = xWalk; subItemRect.right = xWalk + width;
|
|
|
1186 CRect subUpdate;
|
|
|
1187 if (subUpdate.IntersectRect(subItemRect, p_updateRect)) {
|
|
|
1188 DCStateScope scope(dc);
|
|
|
1189 if (dc.IntersectClipRect(subItemRect) != NULLREGION) {
|
|
|
1190 this->RenderGroupOverlayColumn(baseItem, walk, subItemRect, subUpdate, dc);
|
|
|
1191 }
|
|
|
1192 }
|
|
|
1193 xWalk += width;
|
|
|
1194
|
|
|
1195 _walk += 1;
|
|
|
1196 }
|
|
|
1197 }
|
|
|
1198
|
|
|
1199 void CListControlHeaderImpl::UpdateGroupOverlayColumnByID(groupID_t groupID, size_t subItem) {
|
|
|
1200 if (this->GetItemCount() == 0) return;
|
|
|
1201 CRect rc = this->GetSubItemRectAbs(0, subItem);
|
|
|
1202 this->UpdateGroupOverlayByID(groupID, rc.left, rc.right);
|
|
|
1203 }
|
|
|
1204
|
|
|
1205 void CListControlHeaderImpl::mySetCapture(CaptureProc_t proc) {
|
|
|
1206 this->m_captureProc = proc;
|
|
|
1207 this->TrackMouseLeave();
|
|
|
1208 }
|
|
|
1209
|
|
|
1210 void CListControlHeaderImpl::myReleaseCapture() {
|
|
|
1211 m_captureProc = nullptr;
|
|
|
1212 }
|
|
|
1213
|
|
|
1214 void CListControlHeaderImpl::TrackMouseLeave() {
|
|
|
1215 TRACKMOUSEEVENT tme = { sizeof(tme) };
|
|
|
1216 tme.dwFlags = TME_LEAVE;
|
|
|
1217 tme.hwndTrack = m_hWnd;
|
|
|
1218 TrackMouseEvent(&tme);
|
|
|
1219 }
|
|
|
1220
|
|
|
1221 LRESULT CListControlHeaderImpl::MousePassThru(UINT msg, WPARAM wp, LPARAM lp) {
|
|
|
1222 auto p = m_captureProc; // create local ref in case something in mid-captureproc clears it
|
|
|
1223 if (p) {
|
|
|
1224 CPoint pt(lp);
|
|
|
1225 if (!p(msg, (DWORD)wp, pt)) {
|
|
|
1226 myReleaseCapture();
|
|
|
1227 }
|
|
|
1228 return 0;
|
|
|
1229 }
|
|
|
1230
|
|
|
1231 SetMsgHandled(FALSE);
|
|
|
1232 return 0;
|
|
|
1233 }
|
|
|
1234
|
|
|
1235 void CListControlHeaderImpl::OnMouseLeave() {
|
|
|
1236 if (m_captureProc) {
|
|
|
1237 m_captureProc(WM_MOUSELEAVE, 0, -1);
|
|
|
1238 myReleaseCapture();
|
|
|
1239 }
|
|
|
1240 }
|
|
|
1241
|
|
|
1242 void CListControlHeaderImpl::OnKillFocus(CWindow) {
|
|
|
1243 myReleaseCapture();
|
|
|
1244 SetMsgHandled(FALSE);
|
|
|
1245 }
|
|
|
1246
|
|
|
1247 void CListControlHeaderImpl::OnWindowPosChanged(LPWINDOWPOS arg) {
|
|
|
1248 if (arg->flags & SWP_HIDEWINDOW) {
|
|
|
1249 myReleaseCapture();
|
|
|
1250 }
|
|
|
1251 SetMsgHandled(FALSE);
|
|
|
1252 }
|
|
|
1253
|
|
|
1254 void CListControlHeaderImpl::RequestEditItem(size_t, size_t) {
|
|
|
1255 PFC_ASSERT(!"Please enable CListControl_EditImpl");
|
|
|
1256 }
|
|
|
1257
|
|
|
1258 void CListControlHeaderImpl::OnLButtonDblClk(UINT nFlags, CPoint point) {
|
|
|
1259 (void)nFlags;
|
|
|
1260 if (this->GetCellTypeSupported()) {
|
|
|
1261 auto ptAbs = PointClientToAbs(point);
|
|
|
1262 size_t item = this->ItemFromPointAbs(ptAbs);
|
|
|
1263 size_t subItem = SubItemFromPointAbs(ptAbs);
|
|
|
1264 if (item != SIZE_MAX && subItem != SIZE_MAX) {
|
|
|
1265 auto ct = GetCellType(item, subItem);
|
|
|
1266 if (ct != nullptr) {
|
|
|
1267 if (ct->IsToggle()) {
|
|
|
1268 if (ct->HotRect(GetSubItemRect(item, subItem)).PtInRect(point)) {
|
|
|
1269 return; // disregard doubleclick on checkbox
|
|
|
1270 }
|
|
|
1271 }
|
|
|
1272 }
|
|
|
1273 }
|
|
|
1274 }
|
|
|
1275 SetMsgHandled(FALSE); // handle doubleclick
|
|
|
1276 }
|
|
|
1277
|
|
|
1278
|
|
|
1279 void CListControlHeaderImpl::RenderItemBackground(CDCHandle p_dc, const CRect& p_itemRect, size_t item, uint32_t bkColor) {
|
|
|
1280 if (!this->DelimitColumns()) {
|
|
|
1281 __super::RenderItemBackground(p_dc, p_itemRect, item, bkColor);
|
|
|
1282 } else {
|
|
|
1283 auto cnt = this->GetColumnCount();
|
|
|
1284 const auto order = this->GetColumnOrderArray();
|
|
|
1285 uint32_t x = p_itemRect.left;
|
|
|
1286 for (size_t walk = 0; walk < cnt; ) {
|
|
|
1287 const size_t sub = order[walk];
|
|
|
1288 auto span = this->GetSubItemSpan(item, sub);
|
|
|
1289 PFC_ASSERT(span > 0);
|
|
|
1290 uint32_t width = 0;
|
|
|
1291 for (size_t walk2 = 0; walk2 < span; ++walk2) {
|
|
|
1292 width += this->GetSubItemWidth(sub + walk2);
|
|
|
1293 }
|
|
|
1294 CRect rc = p_itemRect;
|
|
|
1295 rc.left = x;
|
|
|
1296 x += width;
|
|
|
1297 rc.right = x;
|
|
|
1298
|
|
|
1299 CRect test;
|
|
|
1300 if (test.IntersectRect(rc, p_itemRect)) {
|
|
|
1301 DCStateScope s(p_dc);
|
|
|
1302 if (p_dc.IntersectClipRect(rc) != NULLREGION) {
|
|
|
1303 __super::RenderItemBackground(p_dc, rc, item, bkColor);
|
|
|
1304 }
|
|
|
1305 }
|
|
|
1306 walk += span;
|
|
|
1307 }
|
|
|
1308 }
|
|
|
1309 }
|