1 #include "filezilla.h"
2 #include "listctrlex.h"
3 #include "filezillaapp.h"
4 
5 #include <wx/renderer.h>
6 #include <wx/statbox.h>
7 
8 #include "Options.h"
9 #include "dialogex.h"
10 #ifdef __WXMSW__
11 #include "commctrl.h"
12 
13 #ifndef HDF_SORTUP
14 #define HDF_SORTUP				0x0400
15 #endif
16 #ifndef HDF_SORTDOWN
17 #define HDF_SORTDOWN			0x0200
18 #endif
19 #else
20 #include "themeprovider.h"
21 #endif
22 
23 wxDECLARE_EVENT(fzEVT_POSTSCROLL, wxCommandEvent);
24 wxDEFINE_EVENT(fzEVT_POSTSCROLL, wxCommandEvent);
25 
BEGIN_EVENT_TABLE(wxListCtrlEx,wxListCtrlExBase)26 BEGIN_EVENT_TABLE(wxListCtrlEx, wxListCtrlExBase)
27 EVT_COMMAND(wxID_ANY, fzEVT_POSTSCROLL, wxListCtrlEx::OnPostScrollEvent)
28 EVT_SCROLLWIN(wxListCtrlEx::OnScrollEvent)
29 EVT_MOUSEWHEEL(wxListCtrlEx::OnMouseWheel)
30 EVT_LIST_ITEM_FOCUSED(wxID_ANY, wxListCtrlEx::OnSelectionChanged)
31 EVT_LIST_ITEM_SELECTED(wxID_ANY, wxListCtrlEx::OnSelectionChanged)
32 EVT_KEY_DOWN(wxListCtrlEx::OnKeyDown)
33 EVT_LIST_BEGIN_LABEL_EDIT(wxID_ANY, wxListCtrlEx::OnBeginLabelEdit)
34 EVT_LIST_END_LABEL_EDIT(wxID_ANY, wxListCtrlEx::OnEndLabelEdit)
35 #ifdef __WXMSW__
36 EVT_SYS_COLOUR_CHANGED(wxListCtrlEx::SystemColorChange)
37 #else
38 EVT_LIST_COL_DRAGGING(wxID_ANY, wxListCtrlEx::OnColumnDragging)
39 #endif
40 END_EVENT_TABLE()
41 
42 #define MIN_COLUMN_WIDTH 12
43 
44 wxListCtrlEx::wxListCtrlEx(wxWindow *parent,
45 						   wxWindowID id,
46 						   const wxPoint& pos,
47 						   const wxSize& size,
48 						   long style,
49 						   const wxValidator& validator,
50 						   const wxString& name)
51 {
52 	Create(parent, id, pos, size, style, validator, name);
53 
54 #ifndef __WXMSW__
55 	m_editing = false;
56 #else
57 	m_columnDragging = false;
58 
59 	::SendMessage(GetHandle(), LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_HEADERDRAGDROP, 0);
60 #endif
61 
62 	m_blockedLabelEditing = false;
63 }
64 
~wxListCtrlEx()65 wxListCtrlEx::~wxListCtrlEx()
66 {
67 #ifdef __WXMSW__
68 	delete m_pHeaderImageList;
69 #endif
70 	delete [] m_pVisibleColumnMapping;
71 }
72 
GetMainWindow()73 wxWindow* wxListCtrlEx::GetMainWindow()
74 {
75 #ifdef __WXMSW__
76 	return this;
77 #else
78 	return reinterpret_cast<wxWindow*>(m_mainWin);
79 #endif
80 }
81 
GetMainWindow() const82 wxWindow const* wxListCtrlEx::GetMainWindow() const
83 {
84 #ifdef __WXMSW__
85 	return this;
86 #else
87 	return reinterpret_cast<wxWindow const*>(m_mainWin);
88 #endif
89 }
90 
OnPostScroll()91 void wxListCtrlEx::OnPostScroll()
92 {
93 }
94 
OnPostScrollEvent(wxCommandEvent &)95 void wxListCtrlEx::OnPostScrollEvent(wxCommandEvent&)
96 {
97 	OnPostScroll();
98 }
99 
OnPreEmitPostScrollEvent()100 void wxListCtrlEx::OnPreEmitPostScrollEvent()
101 {
102 	EmitPostScrollEvent();
103 }
104 
EmitPostScrollEvent()105 void wxListCtrlEx::EmitPostScrollEvent()
106 {
107 	QueueEvent(new wxCommandEvent(fzEVT_POSTSCROLL, wxID_ANY));
108 }
109 
OnScrollEvent(wxScrollWinEvent & event)110 void wxListCtrlEx::OnScrollEvent(wxScrollWinEvent& event)
111 {
112 	event.Skip();
113 	OnPreEmitPostScrollEvent();
114 }
115 
OnMouseWheel(wxMouseEvent & event)116 void wxListCtrlEx::OnMouseWheel(wxMouseEvent& event)
117 {
118 	event.Skip();
119 	OnPreEmitPostScrollEvent();
120 }
121 
OnSelectionChanged(wxListEvent & event)122 void wxListCtrlEx::OnSelectionChanged(wxListEvent& event)
123 {
124 	event.Skip();
125 	OnPreEmitPostScrollEvent();
126 }
127 
ScrollTopItem(int item)128 void wxListCtrlEx::ScrollTopItem(int item)
129 {
130 	if (!GetItemCount()) {
131 		return;
132 	}
133 
134 	if (item < 0) {
135 		item = 0;
136 	}
137 	else if (item >= GetItemCount()) {
138 		item = GetItemCount() - 1;
139 	}
140 
141 	const int current = GetTopItem();
142 
143 	int delta = item - current;
144 
145 	if (!delta) {
146 		return;
147 	}
148 
149 	wxRect rect;
150 	GetItemRect(current, rect, wxLIST_RECT_BOUNDS);
151 
152 	delta *= rect.GetHeight();
153 	ScrollList(0, delta);
154 }
155 
156 
HandlePrefixSearch(wxChar character)157 void wxListCtrlEx::HandlePrefixSearch(wxChar character)
158 {
159 	wxASSERT(character);
160 
161 	// Keyboard navigation within items
162 	fz::datetime now = fz::datetime::now();
163 	if (!m_prefixSearch_lastKeyPress.empty()) {
164 		fz::duration span = now - m_prefixSearch_lastKeyPress;
165 		if (span.get_seconds() >= 1) {
166 			m_prefixSearch_prefix.clear();
167 		}
168 	}
169 	m_prefixSearch_lastKeyPress = now;
170 
171 	wxString newPrefix = m_prefixSearch_prefix + character;
172 
173 	int item;
174 #ifndef __WXMSW__
175 	// GetNextItem is O(n) if nothing is selected, GetSelectedItemCount() is O(1)
176 	if (!GetSelectedItemCount()) {
177 		item = -1;
178 	}
179 	else
180 #endif
181 	{
182 		item = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
183 	}
184 
185 	bool beep = false;
186 	if (item != -1) {
187 		wxString text = GetItemText(item, 0);
188 		if (text.Length() >= m_prefixSearch_prefix.Length() && !m_prefixSearch_prefix.CmpNoCase(text.Left(m_prefixSearch_prefix.Length()))) {
189 			beep = true;
190 		}
191 	}
192 	else if (m_prefixSearch_prefix.empty()) {
193 		beep = true;
194 	}
195 
196 	int start = item;
197 	if (start < 0) {
198 		start = 0;
199 	}
200 
201 	int newPos = FindItemWithPrefix(newPrefix, start);
202 
203 	if (newPos == -1 && (m_prefixSearch_prefix.Len() == 1 && m_prefixSearch_prefix[0] == character) && item != -1 && beep) {
204 		// Search the next item that starts with the same letter
205 		newPrefix = m_prefixSearch_prefix;
206 		newPos = FindItemWithPrefix(newPrefix, item + 1);
207 	}
208 
209 	m_prefixSearch_prefix = newPrefix;
210 	if (newPos == -1) {
211 		if (beep) {
212 			wxBell();
213 		}
214 		return;
215 	}
216 
217 	while (item != -1) {
218 		SetItemState(item, 0, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED);
219 		item = GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
220 	}
221 	SetItemState(newPos, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED, wxLIST_STATE_SELECTED | wxLIST_STATE_FOCUSED);
222 
223 #ifdef __WXMSW__
224 	// SetItemState does not move the selection mark, that is the item from
225 	// which a multiple selection starts (e.g. shift+up/down)
226 	HWND hWnd = (HWND)GetHandle();
227 	::SendMessage(hWnd, LVM_SETSELECTIONMARK, 0, newPos);
228 #endif
229 
230 	EnsureVisible(newPos);
231 }
232 
OnKeyDown(wxKeyEvent & event)233 void wxListCtrlEx::OnKeyDown(wxKeyEvent& event)
234 {
235 	if (!m_prefixSearch_enabled) {
236 		event.Skip();
237 		return;
238 	}
239 
240 	int code = event.GetKeyCode();
241 	if (code == WXK_LEFT ||
242 		code == WXK_RIGHT ||
243 		code == WXK_UP ||
244 		code == WXK_DOWN ||
245 		code == WXK_HOME ||
246 		code == WXK_END)
247 	{
248 		ResetSearchPrefix();
249 		event.Skip();
250 		return;
251 	}
252 
253 	if (event.AltDown() && !event.ControlDown()) {
254 		// Alt but not AltGr
255 		event.Skip();
256 		return;
257 	}
258 
259 	wxChar key;
260 
261 	switch (code)
262 	{
263 	case WXK_NUMPAD0:
264 	case WXK_NUMPAD1:
265 	case WXK_NUMPAD2:
266 	case WXK_NUMPAD3:
267 	case WXK_NUMPAD4:
268 	case WXK_NUMPAD5:
269 	case WXK_NUMPAD6:
270 	case WXK_NUMPAD7:
271 	case WXK_NUMPAD8:
272 	case WXK_NUMPAD9:
273 		key = '0' + code - WXK_NUMPAD0;
274 		break;
275 	case WXK_NUMPAD_ADD:
276 		key = '+';
277 		break;
278 	case WXK_NUMPAD_SUBTRACT:
279 		key = '-';
280 		break;
281 	case WXK_NUMPAD_MULTIPLY:
282 		key = '*';
283 		break;
284 	case WXK_NUMPAD_DIVIDE:
285 		key = '/';
286 		break;
287 	default:
288 		key = 0;
289 		break;
290 	}
291 	if (key) {
292 		if (event.GetModifiers()) {
293 			// Numpad keys can not have modifiers
294 			event.Skip();
295 		}
296 		HandlePrefixSearch(key);
297 		return;
298 	}
299 
300 #if defined(__WXMSW__)
301 
302 	if (code >= 300 && code != WXK_NUMPAD_DECIMAL) {
303 		event.Skip();
304 		return;
305 	}
306 
307 	// Get the actual key
308 	BYTE state[256];
309 	if (!GetKeyboardState(state)) {
310 		event.Skip();
311 		return;
312 	}
313 	wxChar buffer[1];
314 	int res = ToUnicode(event.GetRawKeyCode(), 0, state, buffer, 1, 0);
315 	if (res != 1) {
316 		event.Skip();
317 		return;
318 	}
319 
320 	key = buffer[0];
321 
322 	if (key < 32) {
323 		event.Skip();
324 		return;
325 	}
326 	if (key == 32 && event.HasModifiers()) {
327 		event.Skip();
328 		return;
329 	}
330 	HandlePrefixSearch(key);
331 	return;
332 #else
333 	if (code > 32 && code < 300 && !event.HasModifiers()) {
334 		int unicodeKey = event.GetUnicodeKey();
335 		if (unicodeKey) {
336 			code = unicodeKey;
337 		}
338 		HandlePrefixSearch(code);
339 	}
340 	else {
341 		event.Skip();
342 	}
343 #endif //defined(__WXMSW__)
344 }
345 
346 // Declared const due to design error in wxWidgets.
347 // Won't be fixed since a fix would break backwards compatibility
348 // Both functions use a const_cast<CLocalListView *>(this) and modify
349 // the instance.
OnGetItemText(long item,long column) const350 wxString wxListCtrlEx::OnGetItemText(long item, long column) const
351 {
352 	wxListCtrlEx *pThis = const_cast<wxListCtrlEx *>(this);
353 	return pThis->GetItemText(item, (unsigned int)m_pVisibleColumnMapping[column]);
354 }
355 
FindItemWithPrefix(const wxString & searchPrefix,int start)356 int wxListCtrlEx::FindItemWithPrefix(const wxString& searchPrefix, int start)
357 {
358 	const int count = GetItemCount();
359 	for (int i = start; i < (count + start); ++i) {
360 		int item = i % count;
361 		wxString namePrefix = GetItemText(item, 0).Left(searchPrefix.Length());
362 		if (!namePrefix.CmpNoCase(searchPrefix)) {
363 			return i % count;
364 		}
365 	}
366 	return -1;
367 }
368 
SaveSetItemCount(long count)369 void wxListCtrlEx::SaveSetItemCount(long count)
370 {
371 #ifndef __WXMSW__
372 	if (count < GetItemCount()) {
373 		int focused = GetNextItem(count - 1, wxLIST_NEXT_ALL, wxLIST_STATE_FOCUSED);
374 		if (focused != -1) {
375 			SetItemState(focused, 0, wxLIST_STATE_FOCUSED);
376 		}
377 	}
378 #endif //__WXMSW__
379 	SetItemCount(count);
380 }
381 
ResetSearchPrefix()382 void wxListCtrlEx::ResetSearchPrefix()
383 {
384 	m_prefixSearch_prefix.clear();
385 }
386 
ShowColumn(unsigned int col,bool show)387 void wxListCtrlEx::ShowColumn(unsigned int col, bool show)
388 {
389 	if (col >= m_columnInfo.size()) {
390 		return;
391 	}
392 
393 	if (m_columnInfo[col].shown == show) {
394 		return;
395 	}
396 
397 	m_columnInfo[col].shown = show;
398 
399 	if (show) {
400 		// Insert new column
401 		int pos = 0;
402 		for (unsigned int i = 0; i < m_columnInfo.size(); ++i) {
403 			if (i == col) {
404 				continue;
405 			}
406 			t_columnInfo& info = m_columnInfo[i];
407 			if (info.shown && info.order < m_columnInfo[col].order) {
408 				++pos;
409 			}
410 		}
411 		for (int i = GetColumnCount() - 1; i >= pos; --i) {
412 			m_pVisibleColumnMapping[i + 1] = m_pVisibleColumnMapping[i];
413 		}
414 		m_pVisibleColumnMapping[pos] = col;
415 
416 		t_columnInfo& info = m_columnInfo[col];
417 		InsertColumn(pos, info.name, info.align, info.width);
418 	}
419 	else {
420 		int i;
421 		for (i = 0; i < GetColumnCount(); ++i) {
422 			if (m_pVisibleColumnMapping[i] == col) {
423 				break;
424 			}
425 		}
426 		wxASSERT(m_columnInfo[col].order >= (unsigned int)i);
427 		for (int j = i + 1; j < GetColumnCount(); ++j) {
428 			m_pVisibleColumnMapping[j - 1] = m_pVisibleColumnMapping[j];
429 		}
430 
431 		wxASSERT(i < GetColumnCount());
432 
433 		m_columnInfo[col].width = GetColumnWidth(i);
434 		DeleteColumn(i);
435 	}
436 }
437 
LoadColumnSettings(interfaceOptions widthsOptionId,interfaceOptions visibilityOptionId,interfaceOptions sortOptionId)438 void wxListCtrlEx::LoadColumnSettings(interfaceOptions widthsOptionId, interfaceOptions visibilityOptionId, interfaceOptions sortOptionId)
439 {
440 	wxASSERT(!GetColumnCount());
441 
442 	if (widthsOptionId != OPTIONS_NUM) {
443 		ReadColumnWidths(widthsOptionId);
444 	}
445 
446 	delete [] m_pVisibleColumnMapping;
447 	m_pVisibleColumnMapping = new unsigned int[m_columnInfo.size()];
448 
449 	if (visibilityOptionId != OPTIONS_NUM) {
450 		wxString visibleColumns = COptions::Get()->get_string(visibilityOptionId);
451 		if (visibleColumns.Len() >= m_columnInfo.size()) {
452 			for (unsigned int i = 0; i < m_columnInfo.size(); ++i) {
453 				if (!m_columnInfo[i].fixed) {
454 					m_columnInfo[i].shown = visibleColumns[i] == '1';
455 				}
456 			}
457 		}
458 	}
459 
460 	if (sortOptionId != OPTIONS_NUM) {
461 		auto tokens = fz::strtok(COptions::Get()->get_string(sortOptionId), L",");
462 
463 		if (tokens.size() >= m_columnInfo.size()) {
464 			unsigned int *order = new unsigned int[m_columnInfo.size()];
465 			bool *order_set = new bool[m_columnInfo.size()];
466 			memset(order_set, 0, sizeof(bool) * m_columnInfo.size());
467 
468 			size_t i{};
469 			for (; i < m_columnInfo.size(); ++i) {
470 				order[i] = fz::to_integral(tokens[i], std::numeric_limits<unsigned int>::max());
471 				if (order[i] == std::numeric_limits<unsigned int>::max()) {
472 					break;
473 				}
474 				if (order[i] >= m_columnInfo.size() || order_set[order[i]]) {
475 					break;
476 				}
477 				order_set[order[i]] = true;
478 			}
479 
480 			if (i == m_columnInfo.size()) {
481 				bool valid = true;
482 				for (size_t j = 0; j < m_columnInfo.size(); ++j) {
483 					if (!m_columnInfo[j].fixed) {
484 						continue;
485 					}
486 
487 					if (j != order[j]) {
488 						valid = false;
489 						break;
490 					}
491 				}
492 
493 				if (valid) {
494 					for (size_t j = 0; j < m_columnInfo.size(); ++j) {
495 						m_columnInfo[j].order = order[j];
496 					}
497 				}
498 			}
499 
500 			delete [] order;
501 			delete [] order_set;
502 		}
503 	}
504 
505 	CreateVisibleColumnMapping();
506 }
507 
SaveColumnSettings(interfaceOptions widthsOptionId,interfaceOptions visibilityOptionId,interfaceOptions sortOptionId)508 void wxListCtrlEx::SaveColumnSettings(interfaceOptions widthsOptionId, interfaceOptions visibilityOptionId, interfaceOptions sortOptionId)
509 {
510 	if (widthsOptionId != OPTIONS_NUM) {
511 		SaveColumnWidths(widthsOptionId);
512 	}
513 
514 	if (visibilityOptionId != OPTIONS_NUM) {
515 		std::wstring visibleColumns;
516 		for (unsigned int i = 0; i < m_columnInfo.size(); ++i) {
517 			if (m_columnInfo[i].shown) {
518 				visibleColumns += L"1";
519 			}
520 			else {
521 				visibleColumns += L"0";
522 			}
523 		}
524 		COptions::Get()->set(visibilityOptionId, visibleColumns);
525 	}
526 
527 	if (sortOptionId != OPTIONS_NUM) {
528 		std::wstring order;
529 		for (unsigned int i = 0; i < m_columnInfo.size(); ++i) {
530 			if (i) {
531 				order += L",";
532 			}
533 			order += fz::to_wstring(m_columnInfo[i].order);
534 		}
535 		COptions::Get()->set(sortOptionId, order);
536 	}
537 }
538 
ReadColumnWidths(interfaceOptions optionId)539 bool wxListCtrlEx::ReadColumnWidths(interfaceOptions optionId)
540 {
541 	wxASSERT(!GetColumnCount());
542 
543 	if (wxGetKeyState(WXK_SHIFT) &&
544 		wxGetKeyState(WXK_ALT) &&
545 		wxGetKeyState(WXK_CONTROL))
546 	{
547 		return true;
548 	}
549 
550 
551 	auto tokens = fz::strtok(COptions::Get()->get_string(optionId), L" ");
552 
553 	auto const count = std::min(tokens.size(), m_columnInfo.size());
554 	for (size_t i = 0; i < count; ++i) {
555 		int width = fz::to_integral(tokens[i], -1);
556 		if (width >= 0 && width < 10000) {
557 			m_columnInfo[i].width = width;
558 		}
559 	}
560 
561 	return true;
562 }
563 
SaveColumnWidths(interfaceOptions optionId)564 void wxListCtrlEx::SaveColumnWidths(interfaceOptions optionId)
565 {
566 	const unsigned int count = m_columnInfo.size();
567 
568 	wxString widths;
569 	for (unsigned int i = 0; i < count; ++i) {
570 		int width = 0;
571 
572 		bool found = false;
573 		for (int j = 0; j < GetColumnCount(); ++j) {
574 			if (m_pVisibleColumnMapping[j] != i) {
575 				continue;
576 			}
577 
578 			found = true;
579 			width = GetColumnWidth(j);
580 		}
581 		if (!found) {
582 			width = m_columnInfo[i].width;
583 		}
584 		widths += wxString::Format(_T("%d "), width);
585 	}
586 	widths.RemoveLast();
587 
588 	COptions::Get()->set(optionId, widths.ToStdWstring());
589 }
590 
591 
AddColumn(const wxString & name,int align,int initialWidth,bool fixed)592 void wxListCtrlEx::AddColumn(const wxString& name, int align, int initialWidth, bool fixed)
593 {
594 	wxASSERT(!GetColumnCount());
595 
596 	t_columnInfo info;
597 	info.name = name;
598 	info.align = align;
599 	info.width = initialWidth;
600 	info.shown = true;
601 	info.order = m_columnInfo.size();
602 	info.fixed = fixed;
603 
604 	m_columnInfo.push_back(info);
605 }
606 
607 // Moves column. Target position includes both hidden
608 // as well as shown columns
MoveColumn(unsigned int col,unsigned int before)609 void wxListCtrlEx::MoveColumn(unsigned int col, unsigned int before)
610 {
611 	if (m_columnInfo[col].order == before) {
612 		return;
613 	}
614 
615 	for (unsigned int i = 0; i < m_columnInfo.size(); ++i) {
616 		if (i == col) {
617 			continue;
618 		}
619 
620 		t_columnInfo& info = m_columnInfo[i];
621 		if (info.order > col) {
622 			--info.order;
623 		}
624 		if (info.order >= before) {
625 			++info.order;
626 		}
627 	}
628 
629 	t_columnInfo& info = m_columnInfo[col];
630 
631 	if (info.shown) {
632 		int icon = -1;
633 		// Remove old column
634 		for (unsigned int i = 0; i < (unsigned int)GetColumnCount(); ++i) {
635 			if (m_pVisibleColumnMapping[i] != col) {
636 				continue;
637 			}
638 
639 			for (unsigned int j = i + 1; j < (unsigned int)GetColumnCount(); ++j) {
640 				m_pVisibleColumnMapping[j - 1] = m_pVisibleColumnMapping[j];
641 			}
642 			info.width = GetColumnWidth(i);
643 
644 			icon = GetHeaderSortIconIndex(i);
645 			DeleteColumn(i);
646 
647 			break;
648 		}
649 
650 		// Insert new column
651 		unsigned int pos = 0;
652 		for (unsigned int i = 0; i < m_columnInfo.size(); ++i) {
653 			if (i == col) {
654 				continue;
655 			}
656 			t_columnInfo& info2 = m_columnInfo[i];
657 			if (info2.shown && info2.order < before) {
658 				++pos;
659 			}
660 		}
661 		for (unsigned int i = (int)GetColumnCount(); i > pos; --i) {
662 			m_pVisibleColumnMapping[i] = m_pVisibleColumnMapping[i - 1];
663 		}
664 		m_pVisibleColumnMapping[pos] = col;
665 
666 		InsertColumn(pos, info.name, info.align, info.width);
667 
668 		SetHeaderSortIconIndex(pos, icon);
669 	}
670 	m_columnInfo[col].order = before;
671 }
672 
CreateVisibleColumnMapping()673 void wxListCtrlEx::CreateVisibleColumnMapping()
674 {
675 	int pos = 0;
676 	for (unsigned int j = 0; j < m_columnInfo.size(); ++j) {
677 		for (unsigned int i = 0; i < m_columnInfo.size(); ++i) {
678 			const t_columnInfo &column = m_columnInfo[i];
679 
680 			if (!column.shown) {
681 				continue;
682 			}
683 
684 			if (column.order != j) {
685 				continue;
686 			}
687 
688 			m_pVisibleColumnMapping[pos] = i;
689 			InsertColumn(pos++, column.name, column.align, column.width);
690 		}
691 	}
692 }
693 
694 class CColumnEditDialog final : public wxDialogEx
695 {
696 public:
697 	std::vector<int> order_;
698 	wxCheckListBox * list_box_{};
699 
Create(wxWindow * parent)700 	bool Create(wxWindow* parent)
701 	{
702 		if (!wxDialogEx::Create(parent, -1, _("Column setup"))) {
703 			return false;
704 		}
705 
706 		auto& lay = layout();
707 		auto main = lay.createMain(this, 1);
708 
709 		{
710 			auto [box, inner] = lay.createStatBox(main, _("Visible columns"), 1);
711 			inner->AddGrowableCol(0);
712 			inner->Add(new wxStaticText(box, -1, _("&Select the columns that should be displayed:")));
713 
714 			auto row = lay.createFlex(2);
715 			row->AddGrowableCol(0);
716 			inner->Add(row, lay.grow);
717 			list_box_ = new wxCheckListBox(box, -1);
718 			list_box_->Bind(wxEVT_LISTBOX, &CColumnEditDialog::OnSelChanged, this);
719 			list_box_->Bind(wxEVT_CHECKLISTBOX, &CColumnEditDialog::OnCheck, this);
720 			row->Add(list_box_, lay.grow);
721 
722 			auto col = lay.createFlex(1);
723 			col->AddGrowableCol(0);
724 			row->Add(col);
725 			up_ = new wxButton(box, -1, _("Move &up"));
726 			up_->Bind(wxEVT_BUTTON, &CColumnEditDialog::OnUp, this);
727 			up_->Disable();
728 			col->Add(up_, lay.grow);
729 			down_ = new wxButton(box, -1, _("Move &down"));
730 			down_->Bind(wxEVT_BUTTON, &CColumnEditDialog::OnDown, this);
731 			down_->Disable();
732 			col->Add(down_, lay.grow);
733 		}
734 
735 		auto buttons = lay.createButtonSizer(this, main, true);
736 		auto ok = new wxButton(this, wxID_OK, _("OK"));
737 		ok->SetDefault();
738 		buttons->AddButton(ok);
739 		auto cancel = new wxButton(this, wxID_CANCEL, _("Cancel"));
740 		buttons->AddButton(cancel);
741 		buttons->Realize();
742 
743 		Layout();
744 		GetSizer()->Fit(this);
745 
746 		return true;
747 	}
748 
749 protected:
750 
751 	wxButton* up_{};
752 	wxButton* down_{};
753 
OnUp(wxCommandEvent &)754 	void OnUp(wxCommandEvent&)
755 	{
756 		int sel = list_box_->GetSelection();
757 		if (sel < 2) {
758 			return;
759 		}
760 
761 		std::swap(order_[sel], order_[sel - 1]);
762 
763 		wxString name = list_box_->GetString(sel);
764 		bool checked = list_box_->IsChecked(sel);
765 		list_box_->Delete(sel);
766 		list_box_->Insert(name, sel - 1);
767 		list_box_->Check(sel - 1, checked);
768 		list_box_->SetSelection(sel - 1);
769 
770 		wxCommandEvent evt;
771 		OnSelChanged(evt);
772 	}
773 
OnDown(wxCommandEvent &)774 	void OnDown(wxCommandEvent&)
775 	{
776 		int sel = list_box_->GetSelection();
777 		if (sel < 1) {
778 			return;
779 		}
780 		if (sel >= (int)list_box_->GetCount() - 1) {
781 			return;
782 		}
783 
784 		std::swap(order_[sel], order_[sel + 1]);
785 
786 		wxString name = list_box_->GetString(sel);
787 		bool checked = list_box_->IsChecked(sel);
788 		list_box_->Delete(sel);
789 		list_box_->Insert(name, sel + 1);
790 		list_box_->Check(sel + 1, checked);
791 		list_box_->SetSelection(sel + 1);
792 
793 		wxCommandEvent evt;
794 		OnSelChanged(evt);
795 	}
796 
OnSelChanged(wxCommandEvent &)797 	void OnSelChanged(wxCommandEvent&)
798 	{
799 		int sel = list_box_->GetSelection();
800 		up_->Enable(sel > 1);
801 		down_->Enable(sel > 0 && sel < (int)list_box_->GetCount() - 1);
802 	}
803 
OnCheck(wxCommandEvent & event)804 	void OnCheck(wxCommandEvent& event)
805 	{
806 		if (!event.GetSelection() && !event.IsChecked()) {
807 			list_box_->Check(0);
808 			wxMessageBoxEx(_("The filename column can neither be hidden nor moved."), _("Column properties"));
809 		}
810 	}
811 };
812 
ShowColumnEditor()813 void wxListCtrlEx::ShowColumnEditor()
814 {
815 	CColumnEditDialog dlg;
816 
817 	if (!dlg.Create(this)) {
818 		return;
819 	}
820 
821 	dlg.order_.resize(m_columnInfo.size());
822 	for (size_t j = 0; j < m_columnInfo.size(); ++j) {
823 		for (size_t i = 0; i < m_columnInfo.size(); ++i) {
824 			if (m_columnInfo[i].order != j) {
825 				continue;
826 			}
827 			dlg.order_[j] = i;
828 			dlg.list_box_->Append(m_columnInfo[i].name);
829 			if (m_columnInfo[i].shown) {
830 				dlg.list_box_->Check(j);
831 			}
832 		}
833 	}
834 	wxASSERT(dlg.list_box_->GetCount() == m_columnInfo.size());
835 
836 	dlg.GetSizer()->Fit(&dlg);
837 
838 	if (dlg.ShowModal() != wxID_OK) {
839 		return;
840 	}
841 
842 	for (size_t i = 0; i < m_columnInfo.size(); ++i) {
843 		int col = dlg.order_[i];
844 		bool isChecked = dlg.list_box_->IsChecked(i);
845 		if (!isChecked && !col) {
846 			isChecked = true;
847 			wxMessageBoxEx(_("The filename column cannot be hidden."));
848 		}
849 		MoveColumn(col, i);
850 		if (m_columnInfo[col].shown != isChecked) {
851 			ShowColumn(col, isChecked);
852 		}
853 	}
854 
855 	// Generic wxListCtrl needs manual refresh
856 	Refresh();
857 }
858 
GetColumnVisibleIndex(int col)859 int wxListCtrlEx::GetColumnVisibleIndex(int col)
860 {
861 	if (!m_pVisibleColumnMapping) {
862 		return -1;
863 	}
864 
865 	for (int i = 0; i < GetColumnCount(); ++i) {
866 		if (m_pVisibleColumnMapping[i] == (unsigned int)col) {
867 			return i;
868 		}
869 	}
870 
871 	return -1;
872 }
873 
GetHeaderSortIconIndex(int col)874 int wxListCtrlEx::GetHeaderSortIconIndex(int col)
875 {
876 	if (col < 0 || col >= GetColumnCount()) {
877 		return -1;
878 	}
879 
880 #ifdef __WXMSW__
881 	HWND hWnd = (HWND)GetHandle();
882 	HWND header = (HWND)SendMessage(hWnd, LVM_GETHEADER, 0, 0);
883 
884 	HDITEM item;
885 	item.mask = HDI_IMAGE | HDI_FORMAT;
886 	SendMessage(header, HDM_GETITEM, col, (LPARAM)&item);
887 
888 	if (!(item.fmt & HDF_IMAGE)) {
889 		return -1;
890 	}
891 
892 	return item.iImage;
893 #else
894 	wxListItem item;
895 	if (!GetColumn(col, item)) {
896 		return -1;
897 	}
898 
899 	return item.GetImage();
900 #endif
901 }
902 
InitHeaderSortImageList()903 void wxListCtrlEx::InitHeaderSortImageList()
904 {
905 #ifndef __WXMSW__
906 	wxColour colour = wxSystemSettings::GetColour(wxSYS_COLOUR_3DFACE);
907 
908 	wxString lightness;
909 	if (colour.Red() + colour.Green() + colour.Blue() > 3 * 128) {
910 		lightness = _T("DARK");
911 	}
912 	else {
913 		lightness = _T("LIGHT");
914 	}
915 
916 	auto * imageList = GetSystemImageList();
917 	if (imageList) {
918 		wxBitmap bmp;
919 
920 		bmp = CThemeProvider::Get()->CreateBitmap(_T("ART_SORT_UP_") + lightness, wxART_OTHER, CThemeProvider::GetIconSize(iconSizeSmall));
921 		m_header_icon_index.up = imageList->Add(bmp);
922 		bmp = CThemeProvider::Get()->CreateBitmap(_T("ART_SORT_DOWN_") + lightness, wxART_OTHER, CThemeProvider::GetIconSize(iconSizeSmall));
923 		m_header_icon_index.down = imageList->Add(bmp);
924 	}
925 #endif
926 }
927 
SetHeaderSortIconIndex(int col,int icon)928 void wxListCtrlEx::SetHeaderSortIconIndex(int col, int icon)
929 {
930 	if (col < 0 || col >= GetColumnCount()) {
931 		return;
932 	}
933 
934 #ifdef __WXMSW__
935 	HWND hWnd = (HWND)GetHandle();
936 	HWND header = (HWND)SendMessage(hWnd, LVM_GETHEADER, 0, 0);
937 
938 	HDITEM item = {};
939 	item.mask = HDI_FORMAT;
940 	SendMessage(header, HDM_GETITEM, col, (LPARAM)&item);
941 	if (icon != -1) {
942 		item.fmt &= ~(HDF_IMAGE | HDF_BITMAP_ON_RIGHT | HDF_SORTUP | HDF_SORTDOWN);
943 		item.iImage = -1;
944 		if (icon) {
945 			item.fmt |= HDF_SORTDOWN;
946 		}
947 		else {
948 			item.fmt |= HDF_SORTUP;
949 		}
950 	}
951 	else {
952 		item.fmt &= ~(HDF_IMAGE | HDF_BITMAP_ON_RIGHT | HDF_SORTUP | HDF_SORTDOWN);
953 		item.iImage = -1;
954 	}
955 	SendMessage(header, HDM_SETITEM, col, (LPARAM)&item);
956 #else
957 	wxListItem item;
958 	if (!GetColumn(col, item)) {
959 		return;
960 	}
961 
962 	if (icon != -1) {
963 		icon = icon ? m_header_icon_index.down : m_header_icon_index.up;
964 	}
965 
966 	item.SetImage(icon);
967 	SetColumn(col, item);
968 #endif
969 }
970 
RefreshListOnly(bool eraseBackground)971 void wxListCtrlEx::RefreshListOnly(bool eraseBackground /*=true*/)
972 {
973 	// See comment in wxGenericListCtrl::Refresh
974 	GetMainWindow()->Refresh(eraseBackground);
975 }
976 
CancelLabelEdit()977 void wxListCtrlEx::CancelLabelEdit()
978 {
979 #ifdef __WXMSW__
980 	if (GetEditControl()) {
981 		ListView_CancelEditLabel((HWND)GetHandle());
982 	}
983 #else
984 	m_editing = false;
985 	wxTextCtrl* pEdit = GetEditControl();
986 	if (pEdit) {
987 		wxKeyEvent evt(wxEVT_CHAR);
988 		evt.m_keyCode = WXK_ESCAPE;
989 		pEdit->GetEventHandler()->ProcessEvent(evt);
990 	}
991 #endif
992 
993 }
994 
OnBeginLabelEdit(wxListEvent & event)995 void wxListCtrlEx::OnBeginLabelEdit(wxListEvent& event)
996 {
997 #ifndef __WXMSW__
998 	if (m_editing) {
999 		event.Veto();
1000 		return;
1001 	}
1002 #endif
1003 	if (m_blockedLabelEditing) {
1004 		event.Veto();
1005 		return;
1006 	}
1007 
1008 	if (!OnBeginRename(event)) {
1009 		event.Veto();
1010 	}
1011 #ifndef __WXMSW__
1012 	else {
1013 		m_editing = true;
1014 	}
1015 #endif
1016 }
1017 
OnEndLabelEdit(wxListEvent & event)1018 void wxListCtrlEx::OnEndLabelEdit(wxListEvent& event)
1019 {
1020 #ifdef __WXMAC__
1021 	int item = event.GetIndex();
1022 	if (item != -1) {
1023 		int to = item + 1;
1024 		if (to < GetItemCount()) {
1025 			int from = item;
1026 			if (from) {
1027 				--from;
1028 			}
1029 			RefreshItems(from, to);
1030 		}
1031 		else {
1032 			RefreshListOnly();
1033 		}
1034 	}
1035 #endif
1036 
1037 #ifndef __WXMSW__
1038 	if (!m_editing) {
1039 		event.Veto();
1040 		return;
1041 	}
1042 	m_editing = false;
1043 #endif
1044 
1045 	if (event.IsEditCancelled()) {
1046 		return;
1047 	}
1048 
1049 	if (!OnAcceptRename(event)) {
1050 		event.Veto();
1051 	}
1052 }
1053 
OnBeginRename(const wxListEvent &)1054 bool wxListCtrlEx::OnBeginRename(const wxListEvent&)
1055 {
1056 	return false;
1057 }
1058 
OnAcceptRename(const wxListEvent &)1059 bool wxListCtrlEx::OnAcceptRename(const wxListEvent&)
1060 {
1061 	return false;
1062 }
1063 
SetLabelEditBlock(bool block)1064 void wxListCtrlEx::SetLabelEditBlock(bool block)
1065 {
1066 	if (block) {
1067 		CancelLabelEdit();
1068 		++m_blockedLabelEditing;
1069 	}
1070 	else {
1071 		wxASSERT(m_blockedLabelEditing);
1072 		if (m_blockedLabelEditing > 0) {
1073 			--m_blockedLabelEditing;
1074 		}
1075 	}
1076 }
1077 
CLabelEditBlocker(wxListCtrlEx & listCtrl)1078 CLabelEditBlocker::CLabelEditBlocker(wxListCtrlEx& listCtrl)
1079 	: m_listCtrl(listCtrl)
1080 {
1081 	m_listCtrl.SetLabelEditBlock(true);
1082 }
1083 
~CLabelEditBlocker()1084 CLabelEditBlocker::~CLabelEditBlocker()
1085 {
1086 	m_listCtrl.SetLabelEditBlock(false);
1087 }
1088 
OnColumnDragging(wxListEvent & event)1089 void wxListCtrlEx::OnColumnDragging(wxListEvent& event)
1090 {
1091 	if (event.GetItem().GetWidth() < MIN_COLUMN_WIDTH) {
1092 		event.Veto();
1093 	}
1094 }
1095 
1096 #ifdef __WXMSW__
MSWOnNotify(int idCtrl,WXLPARAM lParam,WXLPARAM * result)1097 bool wxListCtrlEx::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result)
1098 {
1099 	// MSW doesn't generate HDN_TRACK on all header styles, so handle it
1100 	// ourselves using HDN_ITEMCHANGING.
1101 	NMHDR *nmhdr = (NMHDR *)lParam;
1102 	HWND hwndHdr = ListView_GetHeader((HWND)GetHandle());
1103 
1104 	if (nmhdr->hwndFrom != hwndHdr) {
1105 		return wxListCtrl::MSWOnNotify(idCtrl, lParam, result);
1106 	}
1107 
1108 	HD_NOTIFY *nmHDR = (HD_NOTIFY *)nmhdr;
1109 
1110 	switch ( nmhdr->code )
1111 	{
1112 		// See comment in src/msw/listctrl.cpp of wx why both A and W are needed
1113 	case HDN_BEGINTRACKA:
1114 	case HDN_BEGINTRACKW:
1115 		m_columnDragging = true;
1116 		break;
1117 	case HDN_ENDTRACKA:
1118 	case HDN_ENDTRACKW:
1119 		m_columnDragging = true;
1120 		break;
1121 	case HDN_ITEMCHANGINGA:
1122 	case HDN_ITEMCHANGINGW:
1123 		if (m_columnDragging) {
1124 			if (nmHDR->pitem->mask & HDI_WIDTH && nmHDR->pitem->cxy < MIN_COLUMN_WIDTH) {
1125 				*result = 1;
1126 				return true;
1127 			}
1128 			else {
1129 				*result = 0;
1130 				return false;
1131 			}
1132 		}
1133 		else {
1134 			return false;
1135 		}
1136 	case HDN_DIVIDERDBLCLICK:
1137 		{
1138 			auto event = new wxListEvent(wxEVT_LIST_COL_END_DRAG, GetId());
1139 			event->SetEventObject(this);
1140 			QueueEvent(event);
1141 		}
1142 		break;
1143 	}
1144 
1145 	return wxListCtrl::MSWOnNotify(idCtrl, lParam, result);
1146 }
1147 #endif
1148 
HasSelection() const1149 bool wxListCtrlEx::HasSelection() const
1150 {
1151 #ifndef __WXMSW__
1152 	// GetNextItem is O(n) if nothing is selected, GetSelectedItemCount() is O(1)
1153 	return GetSelectedItemCount() != 0;
1154 #else
1155 	return GetNextItem(-1, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED) != -1;
1156 #endif
1157 }
1158 
1159 
GetActualClientRect() const1160 wxRect wxListCtrlEx::GetActualClientRect() const
1161 {
1162 	wxRect windowRect = GetMainWindow()->GetClientRect();
1163 #ifdef __WXMSW__
1164 	wxRect topRect;
1165 	if (GetItemRect(0, topRect)) {
1166 		windowRect.height -= topRect.y;
1167 		windowRect.y += topRect.y;
1168 	}
1169 #endif
1170 
1171 	return windowRect;
1172 }
1173 
1174 #ifdef __WXMSW__
SystemColorChange(wxSysColourChangedEvent & event)1175 void wxListCtrlEx::SystemColorChange(wxSysColourChangedEvent& event)
1176 {
1177 	SetTextColour(GetDefaultAttributes().colFg);
1178 	event.Skip();
1179 }
1180 #endif
1181