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