//+----------------------------------------------------------------------------+ //| Description: Magic Set Editor - Program to make card games | //| Copyright: (C) Twan van Laarhoven and the other MSE developers | //| License: GNU General Public License 2 or later (see file COPYING) | //+----------------------------------------------------------------------------+ // ----------------------------------------------------------------------------- : Includes #include #include #include #include // ----------------------------------------------------------------------------- : ItemList ItemList::ItemList(Window* parent, int id, long additional_style, bool multi_sel) : wxListView(parent, id, wxDefaultPosition, wxDefaultSize, additional_style | wxLC_REPORT | wxLC_VIRTUAL | (multi_sel ? 0 : wxLC_SINGLE_SEL)) , selected_item_pos(-1) , sort_by_column(-1), sort_ascending(true) { // create image list wxImageList* il = new wxImageList(18,14); il->Add(load_resource_image(_("sort_asc")), Color(255,0,255)); il->Add(load_resource_image(_("sort_desc")), Color(255,0,255)); AssignImageList(il, wxIMAGE_LIST_SMALL); // Theming things wx fails to do for us #if defined(__WXMSW__) && defined(LVS_EX_DOUBLEBUFFER) // Fancy theming (on windows) enable_themed_selection_rectangle(this); // Use double buffering ListView_SetExtendedListViewStyle(GetHwnd(), ListView_GetExtendedListViewStyle(GetHwnd()) | LVS_EX_DOUBLEBUFFER); #endif } // ----------------------------------------------------------------------------- : ItemList : Selection bool ItemList::canSelectPrevious() const { return selected_item_pos - 1 >= 0; } bool ItemList::canSelectNext() const { return selected_item_pos >= 0 && static_cast(selected_item_pos + 1) < sorted_list.size(); } bool ItemList::canSelectAll() const { return sorted_list.size() > 0 && !(GetWindowStyle() & wxLC_SINGLE_SEL); } void ItemList::selectPrevious() { assert(selected_item_pos >= 1); focusNone(); selectItemPos(selected_item_pos - 1, true, true); } void ItemList::selectNext() { assert(selected_item_pos + 1 < (long)sorted_list.size()); focusNone(); selectItemPos(selected_item_pos + 1, true, true); } void ItemList::selectFirst() { if (sorted_list.empty()) return; selectItemPos(0, true); } void ItemList::doSelectAll() { long count = GetItemCount(); for (long pos = 0; pos < count; ++pos) { Select(pos,true); } } bool ItemList::doCut() { // cut = copy + delete if (!canCut()) return false; if (!doCopy()) return false; doDelete(); return true; } // ----------------------------------------------------------------------------- : ItemList : Selection (private) void ItemList::selectItem(const VoidP& item, bool focus, bool event) { if (item != selected_item && focus) { focusNone(); } selected_item = item; if (event) sendEvent(); // sending an event will trigger a UI update findSelectedItemPos(); if (focus) focusSelectedItem(); } void ItemList::selectItemPos(long pos, bool focus, bool force_focus) { VoidP item; if ((size_t)pos < sorted_list.size()) { item = getItem(pos); } else if (!sorted_list.empty()) { item = sorted_list.back(); } else { // clear selection } if (item != selected_item) { selectItem(item, false, true); } //!selected_item_pos = pos; if (focus) focusSelectedItem(force_focus); } void ItemList::findSelectedItemPos() { // find the position of the selected item long count = GetItemCount(); selected_item_pos = -1; for (long pos = 0 ; pos < count ; ++pos) { if (getItem(pos) == selected_item) { selected_item_pos = pos; break; } } } long ItemList::findGivenItemPos(const VoidP& item) { long count = GetItemCount(); for (long pos = 0; pos < count; ++pos) { if (getItem(pos) == item) { return pos; } } } void ItemList::focusSelectedItem(bool force_focus) { if (GetItemCount() > 0) { if (selected_item_pos == -1 || (size_t)selected_item_pos > sorted_list.size()) { // deselect currently selected item, if any long sel = GetFirstSelected(); Select(sel, false); } else if (selected_item_pos != GetFocusedItem() || force_focus) { Select(selected_item_pos); Focus (selected_item_pos); } } } void ItemList::focusNone() { long count = GetItemCount(); for (long pos = 0 ; pos < count ; ++pos) { Select(pos, false); } } void ItemList::focusItem(const VoidP& item, bool focus) { long count = GetItemCount(); for (long pos = 0 ; pos < count ; ++pos) { if (getItem(pos) == item) { Select(pos, focus); break; } } } long ItemList::focusCount() const { long count = GetItemCount(); long focused = 0; for (long pos = 0 ; pos < count ; ++pos) { if (const_cast(this)->IsSelected(pos)) focused++; } return focused; } // ----------------------------------------------------------------------------- : ItemList : Building the list // Comparison object for comparing items struct ItemList::ItemComparer { ItemComparer(ItemList& list) : list(list) {} ItemList& list; // 'this' pointer // Compare two items using the current criterium and order bool operator () (const VoidP& a, const VoidP& b) { if (list.sort_ascending) { return list.compareItems(a.get(), b.get()); } else { return list.compareItems(b.get(), a.get()); } } }; void ItemList::refreshList(bool refresh_current_only) { // Get all items vector old_sorted_list; swap(sorted_list, old_sorted_list); getItems(sorted_list); // Sort the list if (sort_by_column >= 0) { stable_sort(sorted_list.begin(), sorted_list.end(), ItemComparer(*this)); } // Has the entire list changed? if (refresh_current_only && sorted_list == old_sorted_list) { if (selected_item_pos >= 0) RefreshItem(selected_item_pos); return; } // refresh // Note: Freeze/Thaw makes flicker worse long item_count = (long)sorted_list.size(); SetItemCount(item_count); if (item_count == 0) { Refresh(); } else { // (re)select current item findSelectedItemPos(); focusNone(); focusSelectedItem(true); // refresh items RefreshItems(0, item_count - 1); } } void ItemList::sortBy(long column, bool ascending) { // Change image in column header long count = GetColumnCount(); for (long i = 0 ; i < count ; ++i) { if (i == column) { SetColumnImage(i, ascending ? 0 : 1); // arrow up/down } else if (i == sort_by_column) { SetColumnImage(i, -1); } } // sort list sort_by_column = column; sort_ascending = ascending; refreshList(); } void ItemList::SetColumnImage(int col, int image) { #if defined(__WXMSW__) && defined(HDF_SORTUP) if ( wxApp::GetComCtl32Version() >= 470 ) { // use built in sort indicator HWND header = ListView_GetHeader(GetHwnd()); HDITEM header_item = {0}; header_item.mask = HDI_FORMAT; Header_GetItem(header, col, &header_item); header_item.fmt &= ~(HDF_SORTUP | HDF_SORTDOWN); if (image == 0) header_item.fmt |= HDF_SORTUP; if (image == 1) header_item.fmt |= HDF_SORTDOWN; Header_SetItem(header, col, &header_item); return; } #endif // The wx version of this function is broken, // setting the wxLIST_MASK_IMAGE also sets the FORMAT flag, so we lose alignment info wxListItem item; item.SetMask(wxLIST_MASK_IMAGE | wxLIST_MASK_FORMAT); GetColumn(col, item); item.SetImage(image); SetColumn(col, item); } // ----------------------------------------------------------------------------- : ItemList : Window events wxSize ItemList::DoGetBestClientSize() const { // wxWidgets decided to be 'smart', and calculate the best size of a list view based on the column header width. // But this also sets the minimum window size, so the window can't be made any smaller than that, even on small screens. // See issue #9 // So, return some small size instead. return wxSize(10,10); } void ItemList::onColumnClick(wxListEvent& ev) { long new_sort_by_column = ev.GetColumn(); if (sort_by_column == new_sort_by_column) { if (sort_ascending) { sort_ascending = false; // 2nd click on same column -> sort descending } else if (mustSort()) { sort_ascending = true; // 3rd click on same column -> sort ascending again } else { new_sort_by_column = -1; // 3rd click on same column -> don't sort } } else { sort_ascending = true; } sortBy(new_sort_by_column, sort_ascending); } void ItemList::onItemFocus(wxListEvent& ev) { selectItemPos(ev.GetIndex(), false); } // ----------------------------------------------------------------------------- : ItemList : Event table BEGIN_EVENT_TABLE(ItemList, wxListView) EVT_LIST_COL_CLICK (wxID_ANY, ItemList::onColumnClick) EVT_LIST_ITEM_FOCUSED (wxID_ANY, ItemList::onItemFocus) END_EVENT_TABLE ()