//+----------------------------------------------------------------------------+ //| Description: Magic Set Editor - Program to make Magic (tm) cards | //| Copyright: (C) 2001 - 2008 Twan van Laarhoven and "coppro" | //| License: GNU General Public License 2 or later (see file COPYING) | //+----------------------------------------------------------------------------+ // ----------------------------------------------------------------------------- : Includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include DECLARE_TYPEOF_COLLECTION(StatsDimensionP); DECLARE_TYPEOF_COLLECTION(String); DECLARE_TYPEOF_COLLECTION(CardP); typedef pair pair_StatsDimensionP_String; DECLARE_TYPEOF_COLLECTION(pair_StatsDimensionP_String); // ----------------------------------------------------------------------------- : StatCategoryList #if !USE_DIMENSION_LISTS /// A list of fields of which the statistics can be shown class StatCategoryList : public GalleryList { public: StatCategoryList(Window* parent, int id) : GalleryList(parent, id, wxVERTICAL) { item_size = columns[0].size = wxSize(150, 23); } void show(const GameP&); /// The selected category inline StatsCategory& getSelection() { return *categories.at(columns[0].selection); } protected: virtual size_t itemCount() const; virtual void drawItem(DC& dc, int x, int y, size_t item); private: GameP game; vector categories; ///< Categories, sorted by position_hint }; struct ComparePositionHint{ inline bool operator () (const StatsCategoryP& a, const StatsCategoryP& b) { return a->position_hint < b->position_hint; } }; void StatCategoryList::show(const GameP& game) { this->game = game; categories = game->statistics_categories; stable_sort(categories.begin(), categories.end(), ComparePositionHint()); update(); // select first item columns[0].selection = itemCount() > 0 ? 0 : NO_SELECTION; } size_t StatCategoryList::itemCount() const { return categories.size(); } void StatCategoryList::drawItem(DC& dc, int x, int y, size_t item) { StatsCategory& cat = *categories.at(item); // draw icon if (!cat.icon_filename.empty() && !cat.icon.Ok()) { InputStreamP file = game->openIn(cat.icon_filename); Image img(*file); if (img.Ok()) { cat.icon = Bitmap(resample_preserve_aspect(img, 21, 21)); } } if (cat.icon.Ok()) { dc.DrawBitmap(cat.icon, x+1, y+1); } // draw name RealRect rect(RealPoint(x + 24, y), RealSize(item_size.x - 30, item_size.y)); String str = capitalize(cat.name); dc.SetFont(*wxNORMAL_FONT); int w, h; dc.GetTextExtent(str, &w, &h); RealSize size = RealSize(w,h); RealPoint pos = align_in_rect(ALIGN_MIDDLE_LEFT, size, rect); dc.DrawText(str, (int)pos.x, (int)pos.y); } // ----------------------------------------------------------------------------- : StatDimensionList #else /// A list of fields of which the statistics can be shown class StatDimensionList : public GalleryList { public: StatDimensionList(Window* parent, int id, bool show_empty, int dimension_count = 3) : GalleryList(parent, id, wxVERTICAL, false) , show_empty(show_empty) , dimension_count(dimension_count) , prefered_dimension_count(dimension_count) { //item_size = wxSize(150, 23); columns[0].size = wxSize(140,23); if (dimension_count > 0) { columns[0].selection = NO_SELECTION; columns[0].can_select = false; active_column = 1; } // additional columns Column col; col.selection = show_empty ? NO_SELECTION : 0; col.can_select = true; col.offset.x = columns[0].size.x + SPACING; col.size = wxSize(18,23); for (int i = 0 ; i < dimension_count ; ++i) { columns.push_back(col); col.offset.x += col.size.x + SPACING; } // total item_size = wxSize(col.offset.x - SPACING, 23); } void show(const GameP&); /// The selected category StatsDimensionP getSelection(size_t column=(size_t)-1) { size_t sel = columns[column+1].selection - show_empty; if (sel >= itemCount()) return StatsDimensionP(); else return dimensions.at(sel); } /// The number of dimensions shown const int dimension_count; size_t prefered_dimension_count; protected: virtual size_t itemCount() const; virtual void drawItem(DC& dc, int x, int y, size_t item); virtual double columnActivity(size_t col) const { return col-1 >= prefered_dimension_count ? 0.2 : 0.7; } virtual void onSelect(size_t item, size_t col, bool& changes) { // swap selection with another column? for (size_t j = 1 ; j < columns.size() ; ++j) { if (j != col && columns[j].selection == item) { columns[j].selection = columns[col].selection; changes = true; break; } } // update prefered dimension count? if (col > prefered_dimension_count) { prefered_dimension_count = col; changes = true; } } private: GameP game; bool show_empty; vector dimensions; ///< Dimensions, sorted by position_hint }; struct ComparePositionHint2{ inline bool operator () (const StatsDimensionP& a, const StatsDimensionP& b) { return a->position_hint < b->position_hint; } }; void StatDimensionList::show(const GameP& game) { this->game = game; dimensions = game->statistics_dimensions; stable_sort(dimensions.begin(), dimensions.end(), ComparePositionHint2()); update(); // select first item if (dimension_count > 0) { for (int j = 0 ; j < dimension_count ; ++j) { columns[j+1].selection = itemCount() > 0 ? 0 : NO_SELECTION; } prefered_dimension_count = 1; } else { columns[0].selection = itemCount() > 0 ? 0 : NO_SELECTION; } } size_t StatDimensionList::itemCount() const { return dimensions.size() + show_empty; } void StatDimensionList::drawItem(DC& dc, int x, int y, size_t item) { if (show_empty && item == 0) { RealRect rect(RealPoint(x + 24, y), RealSize(item_size.x - 30, item_size.y)); String str = _("None"); dc.SetFont(*wxNORMAL_FONT); int w, h; dc.GetTextExtent(str, &w, &h); RealSize size = RealSize(w,h); RealPoint pos = align_in_rect(ALIGN_MIDDLE_LEFT, size, rect); dc.DrawText(str, (int)pos.x, (int)pos.y); return; } StatsDimension& dim = *dimensions.at(item - show_empty); // draw icon if (!dim.icon_filename.empty() && !dim.icon.Ok()) { InputStreamP file = game->openIn(dim.icon_filename); Image img(*file); Image resampled(21, 21); resample_preserve_aspect(img, resampled); if (img.Ok()) dim.icon = Bitmap(resampled); } if (dim.icon.Ok()) { dc.DrawBitmap(dim.icon, x+1, y+1); } // draw name RealRect rect(RealPoint(x + 24, y), RealSize(item_size.x - 30, item_size.y)); String str = capitalize(dim.name); dc.SetFont(*wxNORMAL_FONT); int w, h; dc.GetTextExtent(str, &w, &h); RealSize size = RealSize(w,h); RealPoint pos = align_in_rect(ALIGN_MIDDLE_LEFT, size, rect); dc.DrawText(str, (int)pos.x, (int)pos.y); // draw selection icon for (size_t j = 1 ; j <= prefered_dimension_count ; ++j) { if (isSelected(item,j)) { // TODO: different icons for different dimensions /* int cx = x + columns[j].offset.x + columns[j].size.x/2; int cy = y + columns[j].offset.y + columns[j].size.y/2; dc.SetPen(*wxTRANSPARENT_PEN); dc.SetBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); dc.DrawCircle(cx,cy,6); */ } } } #endif // ----------------------------------------------------------------------------- : StatsPanel StatsPanel::StatsPanel(Window* parent, int id) : SetWindowPanel(parent, id) , up_to_date(true), active(false) { // init controls wxSplitterWindow* splitter; #if USE_SEPARATE_DIMENSION_LISTS for (int i = 0 ; i < 3 ; ++i) { dimensions[i] = new StatDimensionList(this, ID_FIELD_LIST, i > 0); } #elif USE_DIMENSION_LISTS dimensions = new StatDimensionList(this, ID_FIELD_LIST, false, 3); #else categories = new StatCategoryList(this, ID_FIELD_LIST); #endif splitter = new wxSplitterWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); graph = new GraphControl (splitter, wxID_ANY); card_list = new FilteredCardList(splitter, wxID_ANY); // init splitter splitter->SetMinimumPaneSize(100); splitter->SetSashGravity(0.6); splitter->SplitHorizontally(graph, card_list, -170); // init sizer wxSizer* s = new wxBoxSizer(wxHORIZONTAL); #if USE_SEPARATE_DIMENSION_LISTS wxSizer* s2 = new wxBoxSizer(wxVERTICAL); s2->Add(dimensions[0], 1, wxBOTTOM, 2); s2->Add(dimensions[1], 1, wxBOTTOM, 2); s2->Add(dimensions[2], 1); s->Add(s2, 0, wxEXPAND | wxRIGHT, 2); #elif USE_DIMENSION_LISTS s->Add(dimensions, 0, wxEXPAND | wxRIGHT, 2); #else s->Add(categories, 0, wxEXPAND | wxRIGHT, 2); #endif s->Add(splitter, 1, wxEXPAND); s->SetSizeHints(this); SetSizer(s); // init menu menuGraph = new IconMenu(); menuGraph->Append(ID_GRAPH_PIE, _("graph_pie"), _MENU_("pie"), _HELP_("pie"), wxITEM_CHECK); menuGraph->Append(ID_GRAPH_BAR, _("graph_bar"), _MENU_("bar"), _HELP_("bar"), wxITEM_CHECK); menuGraph->Append(ID_GRAPH_STACK, _("graph_stack"), _MENU_("stack"), _HELP_("stack"), wxITEM_CHECK); menuGraph->Append(ID_GRAPH_SCATTER, _("graph_scatter"), _MENU_("scatter"), _HELP_("scatter"), wxITEM_CHECK); menuGraph->Append(ID_GRAPH_SCATTER_PIE, _("graph_scatter_pie"), _MENU_("scatter pie"), _HELP_("scatter pie"), wxITEM_CHECK); } StatsPanel::~StatsPanel() { delete menuGraph; } void StatsPanel::onChangeSet() { card_list->setSet(set); #if USE_SEPARATE_DIMENSION_LISTS for (int i = 0 ; i < 3 ; ++i) dimensions[i]->show(set->game); #elif USE_DIMENSION_LISTS dimensions->show(set->game); #else categories->show(set->game); #endif onChange(); } void StatsPanel::onAction(const Action&, bool undone) { onChange(); } void StatsPanel::initUI (wxToolBar* tb, wxMenuBar* mb) { active = true; if (!up_to_date) showCategory(); // Toolbar #if USE_DIMENSION_LISTS || USE_SEPARATE_DIMENSION_LISTS tb->AddTool(ID_GRAPH_PIE, _(""), load_resource_tool_image(_("graph_pie")), wxNullBitmap, wxITEM_CHECK, _TOOLTIP_("pie"), _HELP_("pie")); tb->AddTool(ID_GRAPH_BAR, _(""), load_resource_tool_image(_("graph_bar")), wxNullBitmap, wxITEM_CHECK, _TOOLTIP_("bar"), _HELP_("bar")); tb->AddTool(ID_GRAPH_STACK, _(""), load_resource_tool_image(_("graph_stack")), wxNullBitmap, wxITEM_CHECK, _TOOLTIP_("stack"), _HELP_("stack")); tb->AddTool(ID_GRAPH_SCATTER, _(""), load_resource_tool_image(_("graph_scatter")), wxNullBitmap, wxITEM_CHECK, _TOOLTIP_("scatter"), _HELP_("scatter")); tb->AddTool(ID_GRAPH_SCATTER_PIE, _(""), load_resource_tool_image(_("graph_scatter_pie")), wxNullBitmap, wxITEM_CHECK, _TOOLTIP_("scatter pie"), _HELP_("scatter pie")); tb->Realize(); // Menu mb->Insert(2, menuGraph, _MENU_("graph")); #endif } void StatsPanel::destroyUI(wxToolBar* tb, wxMenuBar* mb) { active = false; #if USE_DIMENSION_LISTS || USE_SEPARATE_DIMENSION_LISTS // Toolbar tb->DeleteTool(ID_GRAPH_PIE); tb->DeleteTool(ID_GRAPH_BAR); tb->DeleteTool(ID_GRAPH_STACK); tb->DeleteTool(ID_GRAPH_SCATTER); tb->DeleteTool(ID_GRAPH_SCATTER_PIE); // Menus mb->Remove(2); #endif } void StatsPanel::onUpdateUI(wxUpdateUIEvent& ev) { switch (ev.GetId()) { case ID_GRAPH_PIE: case ID_GRAPH_BAR: case ID_GRAPH_STACK: case ID_GRAPH_SCATTER: case ID_GRAPH_SCATTER_PIE: { GraphType type = (GraphType)(ev.GetId() - ID_GRAPH_PIE); ev.Check(graph->getLayout() == type); #if USE_SEPARATE_DIMENSION_LISTS ev.Enable(graph->getDimensionality() == dimensionality(type)); #endif break; } } } void StatsPanel::onCommand(int id) { switch (id) { case ID_FIELD_LIST: { onChange(); break; } case ID_GRAPH_PIE: case ID_GRAPH_BAR: case ID_GRAPH_STACK: case ID_GRAPH_SCATTER: case ID_GRAPH_SCATTER_PIE: { GraphType type = (GraphType)(id - ID_GRAPH_PIE); showLayout(type); break; } } } // ----------------------------------------------------------------------------- : Filtering card list bool chosen(const String& choice, const String& input); class StatsFilter : public CardListFilter { public: StatsFilter(Set& set) : set(set) {} virtual bool keep(const CardP& card) { Context& ctx = set.getContext(card); FOR_EACH(v, values) { StatsDimension& dim = *v.first; String value = untag(dim.script.invoke(ctx)->toString()); if (dim.split_list) { if (!chosen(v.second, value)) return false; } else { if (value != v.second) return false; } } return true; } vector > values; ///< Values selected along each dimension Set& set; }; void StatsPanel::onChange() { if (active) { showCategory(); } else { up_to_date = false; // update later } } void StatsPanel::showCategory(const GraphType* prefer_layout) { up_to_date = true; // find dimensions and layout #if USE_DIMENSION_LISTS || USE_SEPARATE_DIMENSION_LISTS // dimensions vector dims; #if USE_SEPARATE_DIMENSION_LISTS for (int i = 0 ; i < 3 ; ++i) { StatsDimensionP dim = dimensions[i]->getSelection(); if (dim) dims.push_back(dim); } #else // USE_DIMENSION_LISTS for (size_t i = 0 ; i < dimensions->prefered_dimension_count ; ++i) { StatsDimensionP dim = dimensions->getSelection(i); if (dim) dims.push_back(dim); } #endif // layout GraphType layout = prefer_layout ? *prefer_layout : graph->getLayout(); if (dimensionality(layout) != dims.size()) { // we must switch to another layout layout = dims.size() == 1 ? GRAPH_TYPE_BAR : dims.size() == 2 ? (layout == GRAPH_TYPE_SCATTER_PIE || dims[1]->numeric ? GRAPH_TYPE_SCATTER : GRAPH_TYPE_STACK) : GRAPH_TYPE_SCATTER_PIE; } #else if (!categories->hasSelection()) return StatsCategory& cat = categories->getSelection(); // dimensions cat.find_dimensions(set->game->statistics_dimensions); vector& dims = cat.dimensions; // layout GraphType layout = cat.type; #endif // create axes GraphDataPre d; FOR_EACH(dim, dims) { d.axes.push_back(new_intrusive5( dim->name, dim->colors.empty() ? AUTO_COLOR_EVEN : AUTO_COLOR_NO, dim->numeric, &dim->colors, dim->groups.empty() ? nullptr : &dim->groups ) ); } // find values FOR_EACH(card, set->cards) { Context& ctx = set->getContext(card); GraphElementP e(new GraphElement); bool show = true; FOR_EACH(dim, dims) { String value = untag(dim->script.invoke(ctx)->toString()); e->values.push_back(value); if (value.empty() && !dim->show_empty) { // don't show this element show = false; break; } } if (show) { d.elements.push_back(e); } } // split lists size_t dim_id = 0; FOR_EACH(dim, dims) { if (dim->split_list) d.splitList(dim_id); ++dim_id; } // update graph and card list graph->setLayout(layout, true); graph->setData(d); filterCards(); } void StatsPanel::showLayout(GraphType layout) { #if USE_DIMENSION_LISTS && !USE_SEPARATE_DIMENSION_LISTS // make sure we have the right number of data dimensions if (dimensions->prefered_dimension_count != dimensionality(layout)) { dimensions->prefered_dimension_count = dimensionality(layout); showCategory(&layout); // full update dimensions->RefreshSelection(); return; } #endif graph->setLayout(layout); graph->Refresh(false); } void StatsPanel::onGraphSelect(wxCommandEvent&) { filterCards(); } void StatsPanel::filterCards() { #if USE_SEPARATE_DIMENSION_LISTS vector dims; for (int i = 0 ; i < 3 ; ++i) { StatsDimensionP dim = dimensions[i]->getSelection(); if (dim) dims.push_back(dim); } #elif USE_DIMENSION_LISTS vector dims; for (size_t i = 0 ; i < dimensions->prefered_dimension_count ; ++i) { StatsDimensionP dim = dimensions->getSelection(i); if (dim) dims.push_back(dim); } #else if (!categories->hasSelection()) return; const StatsCategory& cat = categories->getSelection(); const vector& dims = cat.dimensions; #endif intrusive_ptr filter(new StatsFilter(*set)); for (size_t i = 0 ; i < dims.size() ; ++i) { if (graph->hasSelection(i)) { filter->values.push_back(make_pair(dims[i], graph->getSelection(i))); } } card_list->setFilter(filter); } BEGIN_EVENT_TABLE(StatsPanel, wxPanel) EVT_GRAPH_SELECT(wxID_ANY, StatsPanel::onGraphSelect) END_EVENT_TABLE() // ----------------------------------------------------------------------------- : Selection CardP StatsPanel::selectedCard() const { return card_list->getCard(); } void StatsPanel::selectCard(const CardP& card) { card_list->setCard(card); }