Commit dc60a3e3 authored by twanvl's avatar twanvl

Added a "quick search" box for filtering the card list

parent 0e61ac11
...@@ -55,6 +55,13 @@ String Card::identification() const { ...@@ -55,6 +55,13 @@ String Card::identification() const {
} }
} }
bool Card::contains(String const& query) const {
FOR_EACH_CONST(v, data) {
if (v->toString().find(query) != String::npos) return true;
}
return false;
}
IndexMap<FieldP, ValueP>& Card::extraDataFor(const StyleSheet& stylesheet) { IndexMap<FieldP, ValueP>& Card::extraDataFor(const StyleSheet& stylesheet) {
return extra_data.get(stylesheet.name(), stylesheet.extra_card_fields); return extra_data.get(stylesheet.name(), stylesheet.extra_card_fields);
} }
......
...@@ -61,6 +61,8 @@ class Card : public IntrusivePtrVirtualBase { ...@@ -61,6 +61,8 @@ class Card : public IntrusivePtrVirtualBase {
/// Get the identification of this card, an identification is something like a name, title, etc. /// Get the identification of this card, an identification is something like a name, title, etc.
/** May return "" */ /** May return "" */
String identification() const; String identification() const;
/// Does any field contains the given query string?
bool contains(String const& query) const;
/// Find a value in the data by name and type /// Find a value in the data by name and type
template <typename T> T& value(const String& name) { template <typename T> T& value(const String& name) {
......
...@@ -13,8 +13,8 @@ DECLARE_TYPEOF_COLLECTION(CardP); ...@@ -13,8 +13,8 @@ DECLARE_TYPEOF_COLLECTION(CardP);
// ----------------------------------------------------------------------------- : FilteredCardList // ----------------------------------------------------------------------------- : FilteredCardList
FilteredCardList::FilteredCardList(Window* parent, int id, long style) FilteredCardList::FilteredCardList(Window* parent, int id, long additional_style)
: CardListBase(parent, id, style) : CardListBase(parent, id, additional_style)
{} {}
void FilteredCardList::setFilter(const CardListFilterP& filter) { void FilteredCardList::setFilter(const CardListFilterP& filter) {
...@@ -34,6 +34,8 @@ void FilteredCardList::getItems(vector<VoidP>& out) const { ...@@ -34,6 +34,8 @@ void FilteredCardList::getItems(vector<VoidP>& out) const {
} }
} }
// ----------------------------------------------------------------------------- : CardListFilter
void CardListFilter::getItems(const vector<CardP>& cards, vector<VoidP>& out) const { void CardListFilter::getItems(const vector<CardP>& cards, vector<VoidP>& out) const {
FOR_EACH_CONST(c, cards) { FOR_EACH_CONST(c, cards) {
if (keep(c)) { if (keep(c)) {
...@@ -41,3 +43,8 @@ void CardListFilter::getItems(const vector<CardP>& cards, vector<VoidP>& out) co ...@@ -41,3 +43,8 @@ void CardListFilter::getItems(const vector<CardP>& cards, vector<VoidP>& out) co
} }
} }
} }
bool QueryCardListFilter::keep(const CardP& card) const {
return card->contains(query);
}
...@@ -26,12 +26,21 @@ class CardListFilter : public IntrusivePtrVirtualBase { ...@@ -26,12 +26,21 @@ class CardListFilter : public IntrusivePtrVirtualBase {
virtual void getItems(const vector<CardP>& cards, vector<VoidP>& out) const; virtual void getItems(const vector<CardP>& cards, vector<VoidP>& out) const;
}; };
/// A filter function that searches for cards containing a string
class QueryCardListFilter : public CardListFilter {
public:
QueryCardListFilter(String const& query) : query(query) {}
virtual bool keep(const CardP& card) const;
private:
String query;
};
// ----------------------------------------------------------------------------- : FilteredCardList // ----------------------------------------------------------------------------- : FilteredCardList
/// A card list that lists a subset of the cards in the set /// A card list that lists a subset of the cards in the set
class FilteredCardList : public CardListBase { class FilteredCardList : public CardListBase {
public: public:
FilteredCardList(Window* parent, int id, long style = 0); FilteredCardList(Window* parent, int id, long additional_style = 0);
/// Change the filter to use /// Change the filter to use
void setFilter(const CardListFilterP& filter); void setFilter(const CardListFilterP& filter);
......
...@@ -114,3 +114,28 @@ void ImageCardList::onIdle(wxIdleEvent&) { ...@@ -114,3 +114,28 @@ void ImageCardList::onIdle(wxIdleEvent&) {
BEGIN_EVENT_TABLE(ImageCardList, CardListBase) BEGIN_EVENT_TABLE(ImageCardList, CardListBase)
EVT_IDLE (ImageCardList::onIdle) EVT_IDLE (ImageCardList::onIdle)
END_EVENT_TABLE () END_EVENT_TABLE ()
// ----------------------------------------------------------------------------- : FilteredImageCardList
FilteredImageCardList::FilteredImageCardList(Window* parent, int id, long additional_style)
: ImageCardList(parent, id, additional_style)
{}
void FilteredImageCardList::setFilter(const CardListFilterP& filter) {
this->filter = filter;
rebuild();
}
void FilteredImageCardList::onChangeSet() {
// clear filter before changing set, the filter might not make sense for a different set
filter = CardListFilterP();
CardListBase::onChangeSet();
}
void FilteredImageCardList::getItems(vector<VoidP>& out) const {
if (filter) {
filter->getItems(set->cards,out);
} else {
ImageCardList::getItems(out);
}
}
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include <util/prec.hpp> #include <util/prec.hpp>
#include <gui/control/card_list.hpp> #include <gui/control/card_list.hpp>
#include <gui/control/filtered_card_list.hpp>
DECLARE_POINTER_TYPE(ImageField); DECLARE_POINTER_TYPE(ImageField);
...@@ -39,5 +40,23 @@ class ImageCardList : public CardListBase { ...@@ -39,5 +40,23 @@ class ImageCardList : public CardListBase {
friend class CardThumbnailRequest; friend class CardThumbnailRequest;
}; };
// ----------------------------------------------------------------------------- : FilteredImageCardList
class FilteredImageCardList : public ImageCardList {
public:
FilteredImageCardList(Window* parent, int id, long additional_style = 0);
/// Change the filter to use, if null then don't use a filter
void setFilter(const CardListFilterP& filter);
protected:
/// Get only the subset of the cards
virtual void getItems(vector<VoidP>& out) const;
virtual void onChangeSet();
private:
CardListFilterP filter; ///< Filter with which this.cards is made
};
// ----------------------------------------------------------------------------- : EOF // ----------------------------------------------------------------------------- : EOF
#endif #endif
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
#include <gui/control/image_card_list.hpp> #include <gui/control/image_card_list.hpp>
#include <gui/control/card_editor.hpp> #include <gui/control/card_editor.hpp>
#include <gui/control/text_ctrl.hpp> #include <gui/control/text_ctrl.hpp>
#include <gui/about_window.hpp> #include <gui/about_window.hpp> // for HoverButton
#include <gui/update_checker.hpp> #include <gui/update_checker.hpp>
#include <gui/icon_menu.hpp> #include <gui/icon_menu.hpp>
#include <gui/util.hpp> #include <gui/util.hpp>
...@@ -34,6 +34,142 @@ DECLARE_TYPEOF_COLLECTION(AddCardsScriptP); ...@@ -34,6 +34,142 @@ DECLARE_TYPEOF_COLLECTION(AddCardsScriptP);
#define HAVE_TOOLBAR_DROPDOWN_MENU 1 #define HAVE_TOOLBAR_DROPDOWN_MENU 1
#endif #endif
// ----------------------------------------------------------------------------- : FilterControl
/// Text control that forwards focus events to the parent
class TextCtrlWithFocus : public wxTextCtrl {
public:
DECLARE_EVENT_TABLE();
void forwardEvent(wxFocusEvent&);
};
/// A search/filter textbox
class FilterCtrl : public wxControl {
public:
FilterCtrl(wxWindow* parent, int id);
/// Set the filter text
void setFilter(const String& filter, bool event = false);
void clearFilter() { setFilter(String()); }
bool hasFilter() const { return !value.empty(); }
String const& getFilter() const { return value; }
//bool AcceptsFocus() const { return false; }
private:
DECLARE_EVENT_TABLE();
bool changing;
wxString value;
TextCtrlWithFocus* filter_ctrl;
HoverButton* clear_button;
void update();
bool hasFocus();
void onChange();
void onChange(wxCommandEvent&);
void onClear(wxCommandEvent&);
void onSize(wxSizeEvent&);
void onSize();
public:
void onSetFocus(wxFocusEvent&);
void onKillFocus(wxFocusEvent&);
};
FilterCtrl::FilterCtrl(wxWindow* parent, int id)
: wxControl(parent, id, wxDefaultPosition, wxSize(160,41), wxSTATIC_BORDER)
, changing(false)
{
wxColour bg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
SetBackgroundColour(bg);
SetCursor(wxCURSOR_IBEAM);
filter_ctrl = new TextCtrlWithFocus();
filter_ctrl->Create(this, wxID_ANY, _(""), wxDefaultPosition, wxSize(130,-1), wxNO_BORDER);
clear_button = new HoverButton(this, wxID_ANY, _("btn_clear_filter"), bg, false);
clear_button->SetCursor(*wxSTANDARD_CURSOR);
onSize();
update();
}
void FilterCtrl::setFilter(const String& new_value, bool event) {
if (this->value == new_value) return;
// update ui
this->value = new_value;
update();
// send event
if (event) {
wxCommandEvent ev(wxEVT_COMMAND_TEXT_UPDATED, GetId());
GetParent()->ProcessEvent(ev);
}
}
void FilterCtrl::update() {
changing = true;
if (!value.empty() || hasFocus()) {
filter_ctrl->SetValue(value);
wxColour fg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
filter_ctrl->SetDefaultStyle(wxTextAttr(fg));
filter_ctrl->SetForegroundColour(fg);
} else {
filter_ctrl->SetValue(_LABEL_("search cards"));
wxColour fg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
wxColour bg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
filter_ctrl->SetDefaultStyle(wxTextAttr(lerp(fg,bg,0.5)));
filter_ctrl->SetForegroundColour(lerp(fg,bg,0.5));
}
clear_button->Show(!value.empty());
changing = false;
}
void FilterCtrl::onChange(wxCommandEvent&) {
if (!changing) {
setFilter(filter_ctrl->GetValue(),true);
}
}
void FilterCtrl::onClear(wxCommandEvent&) {
setFilter(String(),true);
}
void FilterCtrl::onSize(wxSizeEvent&) {
onSize();
}
void FilterCtrl::onSize() {
wxSize s = GetClientSize();
wxSize fs = filter_ctrl->GetBestSize();
wxSize cs = clear_button->GetBestSize();
int margin = 2;
filter_ctrl ->SetSize(margin, max(margin,(s.y-fs.y)/2), s.x - cs.x - 3*margin, fs.y);
clear_button->SetSize(s.x - cs.x - margin, (s.y-cs.y)/2, cs.x, cs.y);
}
void FilterCtrl::onSetFocus(wxFocusEvent&) {
filter_ctrl->SetFocus();
update();
}
void FilterCtrl::onKillFocus(wxFocusEvent&) {
update();
}
bool FilterCtrl::hasFocus() {
wxWindow* focus = wxWindow::FindFocus();
return focus == this || focus == filter_ctrl || focus == clear_button;
}
BEGIN_EVENT_TABLE(FilterCtrl, wxControl)
EVT_BUTTON (wxID_ANY, FilterCtrl::onClear)
EVT_TEXT (wxID_ANY, FilterCtrl::onChange)
EVT_SIZE (FilterCtrl::onSize)
EVT_SET_FOCUS (FilterCtrl::onSetFocus)
EVT_KILL_FOCUS(FilterCtrl::onKillFocus)
END_EVENT_TABLE()
void TextCtrlWithFocus::forwardEvent(wxFocusEvent& ev) {
GetParent()->ProcessEvent(ev);
}
BEGIN_EVENT_TABLE(TextCtrlWithFocus, wxTextCtrl)
EVT_SET_FOCUS (TextCtrlWithFocus::forwardEvent)
EVT_KILL_FOCUS(TextCtrlWithFocus::forwardEvent)
END_EVENT_TABLE()
// ----------------------------------------------------------------------------- : CardsPanel // ----------------------------------------------------------------------------- : CardsPanel
CardsPanel::CardsPanel(Window* parent, int id) CardsPanel::CardsPanel(Window* parent, int id)
...@@ -42,7 +178,7 @@ CardsPanel::CardsPanel(Window* parent, int id) ...@@ -42,7 +178,7 @@ CardsPanel::CardsPanel(Window* parent, int id)
// init controls // init controls
editor = new CardEditor(this, ID_EDITOR); editor = new CardEditor(this, ID_EDITOR);
splitter = new wxSplitterWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); splitter = new wxSplitterWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
card_list = new ImageCardList(splitter, ID_CARD_LIST); card_list = new FilteredImageCardList(splitter, ID_CARD_LIST);
nodes_panel = new Panel(splitter, wxID_ANY); nodes_panel = new Panel(splitter, wxID_ANY);
notes = new TextCtrl(nodes_panel, ID_NOTES, true); notes = new TextCtrl(nodes_panel, ID_NOTES, true);
collapse_notes = new HoverButton(nodes_panel, ID_COLLAPSE_NOTES, _("btn_collapse"), wxNullColour, false); collapse_notes = new HoverButton(nodes_panel, ID_COLLAPSE_NOTES, _("btn_collapse"), wxNullColour, false);
...@@ -213,9 +349,10 @@ void CardsPanel::initUI(wxToolBar* tb, wxMenuBar* mb) { ...@@ -213,9 +349,10 @@ void CardsPanel::initUI(wxToolBar* tb, wxMenuBar* mb) {
#else #else
tb->AddTool(ID_CARD_ROTATE, _(""), load_resource_tool_image(_("card_rotate")), wxNullBitmap,wxITEM_NORMAL, _TOOLTIP_("rotate card"), _HELP_("rotate card")); tb->AddTool(ID_CARD_ROTATE, _(""), load_resource_tool_image(_("card_rotate")), wxNullBitmap,wxITEM_NORMAL, _TOOLTIP_("rotate card"), _HELP_("rotate card"));
#endif #endif
//% tb->AddSeparator(); // Filter/search textbox
//% if (!filter) filter = new wxTextCtrl(tb, wxID_ANY, _(""), wxDefaultPosition, wxDefaultSize, wxSTATIC_BORDER); tb->AddSeparator();
//% tb->AddControl(filter); if (!filter) filter = new FilterCtrl(tb, ID_CARD_FILTER);
tb->AddControl(filter);
tb->Realize(); tb->Realize();
// Menus // Menus
mb->Insert(2, menuCard, _MENU_("cards")); mb->Insert(2, menuCard, _MENU_("cards"));
...@@ -231,11 +368,11 @@ void CardsPanel::destroyUI(wxToolBar* tb, wxMenuBar* mb) { ...@@ -231,11 +368,11 @@ void CardsPanel::destroyUI(wxToolBar* tb, wxMenuBar* mb) {
tb->DeleteTool(ID_CARD_ADD); tb->DeleteTool(ID_CARD_ADD);
tb->DeleteTool(ID_CARD_REMOVE); tb->DeleteTool(ID_CARD_REMOVE);
tb->DeleteTool(ID_CARD_ROTATE); tb->DeleteTool(ID_CARD_ROTATE);
//% tb->DeleteTool(filter->GetId()); filter = nullptr; tb->DeleteTool(filter->GetId()); filter = nullptr;
// HACK: hardcoded size of rest of toolbar // HACK: hardcoded size of rest of toolbar
tb->DeleteToolByPos(12); // delete separator tb->DeleteToolByPos(12); // delete separator
tb->DeleteToolByPos(12); // delete separator tb->DeleteToolByPos(12); // delete separator
//% tb->DeleteToolByPos(12); // delete separator tb->DeleteToolByPos(12); // delete separator
// Menus // Menus
mb->Remove(3); mb->Remove(3);
mb->Remove(2); mb->Remove(2);
...@@ -343,6 +480,15 @@ void CardsPanel::onCommand(int id) { ...@@ -343,6 +480,15 @@ void CardsPanel::onCommand(int id) {
} }
break; break;
} }
case ID_CARD_FILTER: {
// card filter has changed, update the card list
if (filter->hasFilter()) {
card_list->setFilter(intrusive(new QueryCardListFilter(filter->getFilter())));
} else {
card_list->setFilter(CardListFilterP());
}
break;
}
default: { default: {
if (id >= ID_INSERT_SYMBOL_MENU_MIN && id <= ID_INSERT_SYMBOL_MENU_MAX) { if (id >= ID_INSERT_SYMBOL_MENU_MIN && id <= ID_INSERT_SYMBOL_MENU_MAX) {
// pass on to editor // pass on to editor
......
...@@ -13,12 +13,13 @@ ...@@ -13,12 +13,13 @@
#include <gui/set/panel.hpp> #include <gui/set/panel.hpp>
class wxSplitterWindow; class wxSplitterWindow;
class ImageCardList; class FilteredImageCardList;
class DataEditor; class DataEditor;
class TextCtrl; class TextCtrl;
class IconMenu; class IconMenu;
class HoverButton; class HoverButton;
class FindInfo; class FindInfo;
class FilterCtrl;
// ----------------------------------------------------------------------------- : CardsPanel // ----------------------------------------------------------------------------- : CardsPanel
...@@ -75,11 +76,11 @@ class CardsPanel : public SetWindowPanel { ...@@ -75,11 +76,11 @@ class CardsPanel : public SetWindowPanel {
wxSizer* s_left; wxSizer* s_left;
wxSplitterWindow* splitter; wxSplitterWindow* splitter;
DataEditor* editor; DataEditor* editor;
ImageCardList* card_list; FilteredImageCardList* card_list;
Panel* nodes_panel; Panel* nodes_panel;
TextCtrl* notes; TextCtrl* notes;
HoverButton* collapse_notes; HoverButton* collapse_notes;
wxTextCtrl* filter; FilterCtrl* filter;
bool notes_below_editor; bool notes_below_editor;
/// Move the notes panel below the editor or below the card list /// Move the notes panel below the editor or below the card list
......
...@@ -769,6 +769,7 @@ BEGIN_EVENT_TABLE(SetWindow, wxFrame) ...@@ -769,6 +769,7 @@ BEGIN_EVENT_TABLE(SetWindow, wxFrame)
EVT_COMMAND_RANGE (ID_CHILD_MIN, ID_CHILD_MAX, wxEVT_COMMAND_BUTTON_CLICKED, SetWindow::onChildMenu) EVT_COMMAND_RANGE (ID_CHILD_MIN, ID_CHILD_MAX, wxEVT_COMMAND_BUTTON_CLICKED, SetWindow::onChildMenu)
EVT_COMMAND_RANGE (ID_CHILD_MIN, ID_CHILD_MAX, wxEVT_COMMAND_SPINCTRL_UPDATED, SetWindow::onChildMenu) EVT_COMMAND_RANGE (ID_CHILD_MIN, ID_CHILD_MAX, wxEVT_COMMAND_SPINCTRL_UPDATED, SetWindow::onChildMenu)
EVT_COMMAND_RANGE (ID_CHILD_MIN, ID_CHILD_MAX, wxEVT_COMMAND_RADIOBUTTON_SELECTED, SetWindow::onChildMenu) EVT_COMMAND_RANGE (ID_CHILD_MIN, ID_CHILD_MAX, wxEVT_COMMAND_RADIOBUTTON_SELECTED, SetWindow::onChildMenu)
EVT_COMMAND_RANGE (ID_CHILD_MIN, ID_CHILD_MAX, wxEVT_COMMAND_TEXT_UPDATED, SetWindow::onChildMenu)
EVT_GALLERY_SELECT (ID_FIELD_LIST, SetWindow::onChildMenu) // for StatsPanel, because it is not a EVT_TOOL EVT_GALLERY_SELECT (ID_FIELD_LIST, SetWindow::onChildMenu) // for StatsPanel, because it is not a EVT_TOOL
EVT_UPDATE_UI (wxID_ANY, SetWindow::onUpdateUI) EVT_UPDATE_UI (wxID_ANY, SetWindow::onUpdateUI)
...@@ -781,4 +782,5 @@ BEGIN_EVENT_TABLE(SetWindow, wxFrame) ...@@ -781,4 +782,5 @@ BEGIN_EVENT_TABLE(SetWindow, wxFrame)
EVT_CARD_SELECT (wxID_ANY, SetWindow::onCardSelect) EVT_CARD_SELECT (wxID_ANY, SetWindow::onCardSelect)
EVT_CARD_ACTIVATE (wxID_ANY, SetWindow::onCardActivate) EVT_CARD_ACTIVATE (wxID_ANY, SetWindow::onCardActivate)
EVT_SIZE_CHANGE (wxID_ANY, SetWindow::onSizeChange) EVT_SIZE_CHANGE (wxID_ANY, SetWindow::onSizeChange)
EVT_ERASE_BACKGROUND( SetWindow::onEraseBackground)
END_EVENT_TABLE () END_EVENT_TABLE ()
...@@ -165,6 +165,7 @@ class SetWindow : public wxFrame, public SetView { ...@@ -165,6 +165,7 @@ class SetWindow : public wxFrame, public SetView {
void onIdle (wxIdleEvent&); void onIdle (wxIdleEvent&);
void onSizeChange (wxCommandEvent&); void onSizeChange (wxCommandEvent&);
void onEraseBackground (wxEraseEvent&) {} // reduce flicker
}; };
// ----------------------------------------------------------------------------- : EOF // ----------------------------------------------------------------------------- : EOF
......
# This file contains the keys expected to be in MSE locales # This file contains the keys expected to be in MSE locales
# It was automatically generated by tools/locale/locale.pl # It was automatically generated by tools/locale/locale.pl
# Generated on Mon Aug 2 23:18:19 2010 # Generated on Wed Aug 4 23:41:21 2010
action: action:
add control point: 0 add control point: 0
...@@ -124,7 +124,6 @@ error: ...@@ -124,7 +124,6 @@ error:
no game specified: 1 no game specified: 1
no stylesheet specified for the set: 0 no stylesheet specified for the set: 0
no updates: 0 no updates: 0
pack item not found: 1
pack type duplicate name: 1 pack type duplicate name: 1
pack type not found: 1 pack type not found: 1
package not found: 1 package not found: 1
...@@ -329,6 +328,7 @@ label: ...@@ -329,6 +328,7 @@ label:
result: 0 result: 0
rules: 0 rules: 0
save changes: 1 save changes: 1
search cards: 0
seed: 0 seed: 0
select cards: 0 select cards: 0
select cards print: optional, 0 select cards print: optional, 0
......
...@@ -165,6 +165,10 @@ btn_expand_normal IMAGE "../common/btn_expand_normal.png" ...@@ -165,6 +165,10 @@ btn_expand_normal IMAGE "../common/btn_expand_normal.png"
btn_expand_hover IMAGE "../common/btn_expand_hover.png" btn_expand_hover IMAGE "../common/btn_expand_hover.png"
btn_expand_focus IMAGE "../common/btn_expand_focus.png" btn_expand_focus IMAGE "../common/btn_expand_focus.png"
btn_expand_down IMAGE "../common/btn_expand_down.png" btn_expand_down IMAGE "../common/btn_expand_down.png"
btn_clear_filter_normal IMAGE "../common/btn_clear_filter_normal.png"
btn_clear_filter_hover IMAGE "../common/btn_clear_filter_hover.png"
btn_clear_filter_focus IMAGE "../common/btn_clear_filter_focus.png"
btn_clear_filter_down IMAGE "../common/btn_clear_filter_down.png"
//about_xmas IMAGE "about-xmas.png" //about_xmas IMAGE "about-xmas.png"
//two_xmas IMAGE "two_beta-xmas.png" //two_xmas IMAGE "two_beta-xmas.png"
......
...@@ -104,6 +104,7 @@ enum ChildMenuID { ...@@ -104,6 +104,7 @@ enum ChildMenuID {
, ID_CARD_ROTATE_90 , ID_CARD_ROTATE_90
, ID_CARD_ROTATE_180 , ID_CARD_ROTATE_180
, ID_CARD_ROTATE_270 , ID_CARD_ROTATE_270
, ID_CARD_FILTER
// CardList // CardList
, ID_SELECT_COLUMNS , ID_SELECT_COLUMNS
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment