diff --git a/src/data/card.cpp b/src/data/card.cpp index 149534ef..be2ab4ce 100644 --- a/src/data/card.cpp +++ b/src/data/card.cpp @@ -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& Card::extraDataFor(const StyleSheet& stylesheet) { return extra_data.get(stylesheet.name(), stylesheet.extra_card_fields); } diff --git a/src/data/card.hpp b/src/data/card.hpp index 3fd0c9a2..644857d9 100644 --- a/src/data/card.hpp +++ b/src/data/card.hpp @@ -61,6 +61,8 @@ class Card : public IntrusivePtrVirtualBase { /// Get the identification of this card, an identification is something like a name, title, etc. /** May return "" */ 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 template T& value(const String& name) { diff --git a/src/gui/control/filtered_card_list.cpp b/src/gui/control/filtered_card_list.cpp index 4f53d042..493931fd 100644 --- a/src/gui/control/filtered_card_list.cpp +++ b/src/gui/control/filtered_card_list.cpp @@ -13,8 +13,8 @@ DECLARE_TYPEOF_COLLECTION(CardP); // ----------------------------------------------------------------------------- : FilteredCardList -FilteredCardList::FilteredCardList(Window* parent, int id, long style) - : CardListBase(parent, id, style) +FilteredCardList::FilteredCardList(Window* parent, int id, long additional_style) + : CardListBase(parent, id, additional_style) {} void FilteredCardList::setFilter(const CardListFilterP& filter) { @@ -34,6 +34,8 @@ void FilteredCardList::getItems(vector& out) const { } } +// ----------------------------------------------------------------------------- : CardListFilter + void CardListFilter::getItems(const vector& cards, vector& out) const { FOR_EACH_CONST(c, cards) { if (keep(c)) { @@ -41,3 +43,8 @@ void CardListFilter::getItems(const vector& cards, vector& out) co } } } + +bool QueryCardListFilter::keep(const CardP& card) const { + return card->contains(query); +} + diff --git a/src/gui/control/filtered_card_list.hpp b/src/gui/control/filtered_card_list.hpp index 3e2032eb..2a4a9472 100644 --- a/src/gui/control/filtered_card_list.hpp +++ b/src/gui/control/filtered_card_list.hpp @@ -26,12 +26,21 @@ class CardListFilter : public IntrusivePtrVirtualBase { virtual void getItems(const vector& cards, vector& 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 /// A card list that lists a subset of the cards in the set class FilteredCardList : public CardListBase { public: - FilteredCardList(Window* parent, int id, long style = 0); + FilteredCardList(Window* parent, int id, long additional_style = 0); /// Change the filter to use void setFilter(const CardListFilterP& filter); diff --git a/src/gui/control/image_card_list.cpp b/src/gui/control/image_card_list.cpp index 4449e3dd..899fa5f4 100644 --- a/src/gui/control/image_card_list.cpp +++ b/src/gui/control/image_card_list.cpp @@ -114,3 +114,28 @@ void ImageCardList::onIdle(wxIdleEvent&) { BEGIN_EVENT_TABLE(ImageCardList, CardListBase) EVT_IDLE (ImageCardList::onIdle) 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& out) const { + if (filter) { + filter->getItems(set->cards,out); + } else { + ImageCardList::getItems(out); + } +} diff --git a/src/gui/control/image_card_list.hpp b/src/gui/control/image_card_list.hpp index 7f93b381..eb78b148 100644 --- a/src/gui/control/image_card_list.hpp +++ b/src/gui/control/image_card_list.hpp @@ -11,6 +11,7 @@ #include #include +#include DECLARE_POINTER_TYPE(ImageField); @@ -39,5 +40,23 @@ class ImageCardList : public CardListBase { 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& out) const; + virtual void onChangeSet(); + + private: + CardListFilterP filter; ///< Filter with which this.cards is made +}; + // ----------------------------------------------------------------------------- : EOF #endif diff --git a/src/gui/set/cards_panel.cpp b/src/gui/set/cards_panel.cpp index 6088a3ab..0460c9ec 100644 --- a/src/gui/set/cards_panel.cpp +++ b/src/gui/set/cards_panel.cpp @@ -11,7 +11,7 @@ #include #include #include -#include +#include // for HoverButton #include #include #include @@ -34,6 +34,142 @@ DECLARE_TYPEOF_COLLECTION(AddCardsScriptP); #define HAVE_TOOLBAR_DROPDOWN_MENU 1 #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(Window* parent, int id) @@ -42,7 +178,7 @@ CardsPanel::CardsPanel(Window* parent, int id) // init controls editor = new CardEditor(this, ID_EDITOR); 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); notes = new TextCtrl(nodes_panel, ID_NOTES, true); collapse_notes = new HoverButton(nodes_panel, ID_COLLAPSE_NOTES, _("btn_collapse"), wxNullColour, false); @@ -213,9 +349,10 @@ void CardsPanel::initUI(wxToolBar* tb, wxMenuBar* mb) { #else tb->AddTool(ID_CARD_ROTATE, _(""), load_resource_tool_image(_("card_rotate")), wxNullBitmap,wxITEM_NORMAL, _TOOLTIP_("rotate card"), _HELP_("rotate card")); #endif -//% tb->AddSeparator(); -//% if (!filter) filter = new wxTextCtrl(tb, wxID_ANY, _(""), wxDefaultPosition, wxDefaultSize, wxSTATIC_BORDER); -//% tb->AddControl(filter); + // Filter/search textbox + tb->AddSeparator(); + if (!filter) filter = new FilterCtrl(tb, ID_CARD_FILTER); + tb->AddControl(filter); tb->Realize(); // Menus mb->Insert(2, menuCard, _MENU_("cards")); @@ -231,11 +368,11 @@ void CardsPanel::destroyUI(wxToolBar* tb, wxMenuBar* mb) { tb->DeleteTool(ID_CARD_ADD); tb->DeleteTool(ID_CARD_REMOVE); tb->DeleteTool(ID_CARD_ROTATE); -//% tb->DeleteTool(filter->GetId()); filter = nullptr; + tb->DeleteTool(filter->GetId()); filter = nullptr; // 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 // Menus mb->Remove(3); mb->Remove(2); @@ -343,6 +480,15 @@ void CardsPanel::onCommand(int id) { } 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: { if (id >= ID_INSERT_SYMBOL_MENU_MIN && id <= ID_INSERT_SYMBOL_MENU_MAX) { // pass on to editor diff --git a/src/gui/set/cards_panel.hpp b/src/gui/set/cards_panel.hpp index 59eb366d..48446a3a 100644 --- a/src/gui/set/cards_panel.hpp +++ b/src/gui/set/cards_panel.hpp @@ -13,12 +13,13 @@ #include class wxSplitterWindow; -class ImageCardList; +class FilteredImageCardList; class DataEditor; class TextCtrl; class IconMenu; class HoverButton; class FindInfo; +class FilterCtrl; // ----------------------------------------------------------------------------- : CardsPanel @@ -75,11 +76,11 @@ class CardsPanel : public SetWindowPanel { wxSizer* s_left; wxSplitterWindow* splitter; DataEditor* editor; - ImageCardList* card_list; + FilteredImageCardList* card_list; Panel* nodes_panel; TextCtrl* notes; HoverButton* collapse_notes; - wxTextCtrl* filter; + FilterCtrl* filter; bool notes_below_editor; /// Move the notes panel below the editor or below the card list diff --git a/src/gui/set/window.cpp b/src/gui/set/window.cpp index e5bd3cac..f17a5007 100644 --- a/src/gui/set/window.cpp +++ b/src/gui/set/window.cpp @@ -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_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_TEXT_UPDATED, SetWindow::onChildMenu) EVT_GALLERY_SELECT (ID_FIELD_LIST, SetWindow::onChildMenu) // for StatsPanel, because it is not a EVT_TOOL EVT_UPDATE_UI (wxID_ANY, SetWindow::onUpdateUI) @@ -781,4 +782,5 @@ BEGIN_EVENT_TABLE(SetWindow, wxFrame) EVT_CARD_SELECT (wxID_ANY, SetWindow::onCardSelect) EVT_CARD_ACTIVATE (wxID_ANY, SetWindow::onCardActivate) EVT_SIZE_CHANGE (wxID_ANY, SetWindow::onSizeChange) + EVT_ERASE_BACKGROUND( SetWindow::onEraseBackground) END_EVENT_TABLE () diff --git a/src/gui/set/window.hpp b/src/gui/set/window.hpp index 4b2f2789..e9664df2 100644 --- a/src/gui/set/window.hpp +++ b/src/gui/set/window.hpp @@ -165,6 +165,7 @@ class SetWindow : public wxFrame, public SetView { void onIdle (wxIdleEvent&); void onSizeChange (wxCommandEvent&); + void onEraseBackground (wxEraseEvent&) {} // reduce flicker }; // ----------------------------------------------------------------------------- : EOF diff --git a/src/resource/common/btn_clear_filter_down.png b/src/resource/common/btn_clear_filter_down.png new file mode 100644 index 00000000..b94ec936 Binary files /dev/null and b/src/resource/common/btn_clear_filter_down.png differ diff --git a/src/resource/common/btn_clear_filter_focus.png b/src/resource/common/btn_clear_filter_focus.png new file mode 100644 index 00000000..b944224f Binary files /dev/null and b/src/resource/common/btn_clear_filter_focus.png differ diff --git a/src/resource/common/btn_clear_filter_hover.png b/src/resource/common/btn_clear_filter_hover.png new file mode 100644 index 00000000..b944224f Binary files /dev/null and b/src/resource/common/btn_clear_filter_hover.png differ diff --git a/src/resource/common/btn_clear_filter_normal.png b/src/resource/common/btn_clear_filter_normal.png new file mode 100644 index 00000000..2844e7eb Binary files /dev/null and b/src/resource/common/btn_clear_filter_normal.png differ diff --git a/src/resource/common/expected_locale_keys b/src/resource/common/expected_locale_keys index 04634a70..9fc057df 100644 --- a/src/resource/common/expected_locale_keys +++ b/src/resource/common/expected_locale_keys @@ -1,6 +1,6 @@ # This file contains the keys expected to be in MSE locales # 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: add control point: 0 @@ -124,7 +124,6 @@ error: no game specified: 1 no stylesheet specified for the set: 0 no updates: 0 - pack item not found: 1 pack type duplicate name: 1 pack type not found: 1 package not found: 1 @@ -329,6 +328,7 @@ label: result: 0 rules: 0 save changes: 1 + search cards: 0 seed: 0 select cards: 0 select cards print: optional, 0 diff --git a/src/resource/msw/mse.rc b/src/resource/msw/mse.rc index bd53e3de..2afca012 100644 --- a/src/resource/msw/mse.rc +++ b/src/resource/msw/mse.rc @@ -165,6 +165,10 @@ btn_expand_normal IMAGE "../common/btn_expand_normal.png" btn_expand_hover IMAGE "../common/btn_expand_hover.png" btn_expand_focus IMAGE "../common/btn_expand_focus.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" //two_xmas IMAGE "two_beta-xmas.png" diff --git a/src/util/window_id.hpp b/src/util/window_id.hpp index 683a6022..d20014e0 100644 --- a/src/util/window_id.hpp +++ b/src/util/window_id.hpp @@ -104,6 +104,7 @@ enum ChildMenuID { , ID_CARD_ROTATE_90 , ID_CARD_ROTATE_180 , ID_CARD_ROTATE_270 +, ID_CARD_FILTER // CardList , ID_SELECT_COLUMNS