mirror of
https://github.com/amyinspace/MagicSetEditor2.git
synced 2026-06-10 04:57:00 -04:00
Support for 2d bar graphs;
separator_after for keywords; Slightly more advanced english_plural/singular; Windows uninstaller will remove app data git-svn-id: svn://svn.code.sf.net/p/magicseteditor/code/trunk@348 0fc631ac-6414-0410-93d0-97cfa31319b6
This commit is contained in:
@@ -518,6 +518,9 @@ error:
|
||||
# Update checking
|
||||
checking updates failed: Checking updates failed.
|
||||
no updates: There are no available updates.
|
||||
|
||||
# Stats panel
|
||||
dimension not found: There is no statistics dimension '%s'
|
||||
|
||||
############################################################## Types used in scripts / shape names
|
||||
type:
|
||||
|
||||
+23
-11
@@ -152,14 +152,20 @@ init script:
|
||||
};
|
||||
|
||||
# replaces — correctly
|
||||
alternative_cost := replace_rule(match:"\\.$", replace:"") + replace_rule(match:"^[A-Z]", replace: { to_lower() })
|
||||
add := "" # default is nothing
|
||||
for_mana_costs := format_cost := {
|
||||
if input.separator=="—" then
|
||||
"<param-cost>{alternative_cost(input.param)}</param-cost>"
|
||||
if input.separator_before == "—" then
|
||||
"<param-cost>{input.param}</param-cost>"
|
||||
else
|
||||
"<param-mana>{add}{input.param}</param-mana>"
|
||||
}
|
||||
alternative_cost := replace_rule(match:"^[A-Z]", replace: { to_lower() })
|
||||
format_alt_cost := {
|
||||
if input.separator_before == "—" then
|
||||
alternative_cost(input.param)
|
||||
else
|
||||
input.param
|
||||
}
|
||||
long_dash := replace_rule(match:"-", replace:"—")
|
||||
# Utilities for keywords
|
||||
has_cc := { card.casting_cost != "" }
|
||||
@@ -256,8 +262,11 @@ init script:
|
||||
replace: "Æ") +
|
||||
# step 2 : longdash for keywords
|
||||
replace_rule(
|
||||
match: "--| - ",
|
||||
replace: "—")
|
||||
match: "--",
|
||||
replace: "—") +
|
||||
replace_rule(
|
||||
match: " - ",
|
||||
replace: " — ")
|
||||
|
||||
#character filter for copyright line
|
||||
copyright_filter :=
|
||||
@@ -955,7 +964,7 @@ card field:
|
||||
############################################################## Statistics categories
|
||||
|
||||
statistics dimension:
|
||||
name: card color
|
||||
name: card color2
|
||||
script: primary_card_color(card.card_color)
|
||||
icon: stats/card_color.png
|
||||
colors:
|
||||
@@ -994,10 +1003,10 @@ statistics dimension:
|
||||
# numeric: true
|
||||
# icon: stats/toughness.png
|
||||
|
||||
#statistics category:
|
||||
# name: color / rarity
|
||||
# dimension: card_color
|
||||
# dimension: rarity
|
||||
statistics category:
|
||||
name: color / rarity
|
||||
dimension: card color2
|
||||
dimension: rarity
|
||||
|
||||
#statistics category:
|
||||
# name: power / toughness
|
||||
@@ -1095,6 +1104,7 @@ pack type:
|
||||
|
||||
has keywords: true
|
||||
|
||||
keyword match script: name_filter(value)
|
||||
#keyword preview: {keyword} <i>({reminder})</i>
|
||||
|
||||
keyword mode:
|
||||
@@ -1122,6 +1132,7 @@ keyword parameter type:
|
||||
name: cost
|
||||
match: [ ][STXYZ0-9WUBRG/|]*|[-—][^(\n]*
|
||||
separator before is: [ —-]
|
||||
separator after is: [.]
|
||||
optional: false
|
||||
# note: the separator is part of match
|
||||
refer script:
|
||||
@@ -1136,6 +1147,7 @@ keyword parameter type:
|
||||
name: add "pay " for mana costs
|
||||
description: When using mana only costs, words the reminder text as "pay <cost>"
|
||||
script: \{for_mana_costs(add:"pay ",{input})\}
|
||||
reminder script: format_alt_cost()
|
||||
separator script: long_dash()
|
||||
keyword parameter type:
|
||||
name: number
|
||||
@@ -1161,7 +1173,7 @@ keyword parameter type:
|
||||
match: [^(,\n]+
|
||||
keyword parameter type:
|
||||
name: name
|
||||
match: [^(.,\n]+
|
||||
match: [^(.,\n-—]+
|
||||
keyword parameter type:
|
||||
name: prefix
|
||||
description: Prefix for things like "<something>walk"
|
||||
|
||||
+62
-27
@@ -38,6 +38,7 @@ IMPLEMENT_REFLECTION(KeywordParam) {
|
||||
REFLECT(optional);
|
||||
REFLECT(match);
|
||||
REFLECT(separator_before_is);
|
||||
REFLECT(separator_after_is);
|
||||
REFLECT(eat_separator);
|
||||
REFLECT(script);
|
||||
REFLECT(reminder_script);
|
||||
@@ -119,12 +120,20 @@ String KeywordParam::make_separator_before() const {
|
||||
return ret;
|
||||
}*/
|
||||
void KeywordParam::compile() {
|
||||
// compile separator_before
|
||||
if (!separator_before_is.empty() && !separator_before_re.IsValid()) {
|
||||
separator_before_re.Compile(_("^") + separator_before_is, wxRE_ADVANCED);
|
||||
if (eat_separator) {
|
||||
separator_before_eat.Compile(separator_before_is + _("$"), wxRE_ADVANCED);
|
||||
}
|
||||
}
|
||||
// compile separator_after
|
||||
if (!separator_after_is.empty() && !separator_after_re.IsValid()) {
|
||||
separator_after_re.Compile(separator_after_is + _("$"), wxRE_ADVANCED);
|
||||
if (eat_separator) {
|
||||
separator_after_eat.Compile(_("^") + separator_after_is, wxRE_ADVANCED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t Keyword::findMode(const vector<KeywordModeP>& modes) const {
|
||||
@@ -239,6 +248,12 @@ void Keyword::prepare(const vector<KeywordParamP>& param_types, bool force) {
|
||||
// modify regex : match parameter
|
||||
regex += _("(") + make_non_capturing(p->match) + (p->optional ? _(")?") : _(")"));
|
||||
i = skip_tag(match, end);
|
||||
// eat separator_after?
|
||||
if (p->separator_after_eat.IsValid() && p->separator_after_eat.Matches(match.substr(i))) {
|
||||
size_t start, len;
|
||||
p->separator_before_eat.GetMatch(&start, &len);
|
||||
i += start + len;
|
||||
}
|
||||
} else {
|
||||
text += c;
|
||||
i++;
|
||||
@@ -347,22 +362,28 @@ void KeywordDatabase::add(const Keyword& kw) {
|
||||
for (size_t i = 0 ; i < kw.match.size() ;) {
|
||||
Char c = kw.match.GetChar(i);
|
||||
if (is_substr(kw.match, i, _("<atom-param"))) {
|
||||
i = match_close_tag_end(kw.match, i);
|
||||
// parameter, is there a separator we should eat?
|
||||
if (param < kw.parameters.size()) {
|
||||
wxRegEx& sep = kw.parameters[param]->separator_before_eat;
|
||||
if (sep.IsValid() && sep.Matches(text)) {
|
||||
wxRegEx& sep_before = kw.parameters[param]->separator_before_eat;
|
||||
wxRegEx& sep_after = kw.parameters[param]->separator_after_eat;
|
||||
if (sep_before.IsValid() && sep_before.Matches(text)) {
|
||||
// remove the separator from the text to prevent duplicates
|
||||
size_t start, len;
|
||||
sep.GetMatch(&start, &len);
|
||||
sep_before.GetMatch(&start, &len);
|
||||
text = text.substr(0, start);
|
||||
}
|
||||
if (sep_after.IsValid() && sep_after.Matches(kw.match.substr(i))) {
|
||||
size_t start, len;
|
||||
sep_after.GetMatch(&start, &len);
|
||||
i += start + len;
|
||||
}
|
||||
}
|
||||
++param;
|
||||
// match anything
|
||||
cur = cur->insert(text);
|
||||
text.clear();
|
||||
cur = cur->insertAnyStar();
|
||||
i = match_close_tag_end(kw.match, i);
|
||||
} else {
|
||||
text += c;
|
||||
i++;
|
||||
@@ -501,27 +522,40 @@ String KeywordDatabase::expand(const String& text,
|
||||
// parameter
|
||||
KeywordParam& kwp = *kw->parameters[j/2-1];
|
||||
String param = untagged.substr(start_u, len_u); // untagged version
|
||||
// strip separator
|
||||
String separator;
|
||||
if (kwp.separator_before_re.IsValid()) {
|
||||
if (kwp.separator_before_re.Matches(param)) {
|
||||
size_t s_start, s_len; // start should be 0
|
||||
kwp.separator_before_re.GetMatch(&s_start, &s_len);
|
||||
separator = param.substr(0, s_start + s_len);
|
||||
param = param.substr(s_start + s_len);
|
||||
// strip from tagged version
|
||||
size_t end_t = untagged_to_index(part, s_start + s_len, false);
|
||||
part = get_tags(part, 0, end_t, true, true) + part.substr(end_t);
|
||||
// transform?
|
||||
if (kwp.separator_script) {
|
||||
ctx.setVariable(_("input"), to_script(separator));
|
||||
separator = kwp.separator_script.invoke(ctx)->toString();
|
||||
}
|
||||
// strip separator_before
|
||||
String separator_before, separator_after;
|
||||
if (kwp.separator_before_re.IsValid() && kwp.separator_before_re.Matches(param)) {
|
||||
size_t s_start, s_len; // start should be 0
|
||||
kwp.separator_before_re.GetMatch(&s_start, &s_len);
|
||||
separator_before = param.substr(0, s_start + s_len);
|
||||
param = param.substr(s_start + s_len);
|
||||
// strip from tagged version
|
||||
size_t end_t = untagged_to_index(part, s_start + s_len, false);
|
||||
part = get_tags(part, 0, end_t, true, true) + part.substr(end_t);
|
||||
// transform?
|
||||
if (kwp.separator_script) {
|
||||
ctx.setVariable(_("input"), to_script(separator_before));
|
||||
separator_before = kwp.separator_script.invoke(ctx)->toString();
|
||||
}
|
||||
}
|
||||
// strip separator_after
|
||||
if (kwp.separator_after_re.IsValid() && kwp.separator_after_re.Matches(param)) {
|
||||
size_t s_start, s_len; // start + len should be param.size()
|
||||
kwp.separator_after_re.GetMatch(&s_start, &s_len);
|
||||
separator_after = param.substr(s_start);
|
||||
param = param.substr(0, s_start);
|
||||
// strip from tagged version
|
||||
size_t start_t = untagged_to_index(part, s_start, false);
|
||||
part = part.substr(0, start_t) + get_tags(part, start_t, part.size(), true, true);
|
||||
// transform?
|
||||
if (kwp.separator_script) {
|
||||
ctx.setVariable(_("input"), to_script(separator_after));
|
||||
separator_after = kwp.separator_script.invoke(ctx)->toString();
|
||||
}
|
||||
}
|
||||
// to script
|
||||
KeywordParamValueP script_param(new KeywordParamValue(kwp.name, separator, param));
|
||||
KeywordParamValueP script_part (new KeywordParamValue(kwp.name, separator, part));
|
||||
KeywordParamValueP script_param(new KeywordParamValue(kwp.name, separator_before, separator_after, param));
|
||||
KeywordParamValueP script_part (new KeywordParamValue(kwp.name, separator_before, separator_after, part));
|
||||
// process param
|
||||
if (param.empty()) {
|
||||
// placeholder
|
||||
@@ -538,7 +572,7 @@ String KeywordDatabase::expand(const String& text,
|
||||
script_param->value = kwp.reminder_script.invoke(ctx)->toString();
|
||||
}
|
||||
}
|
||||
part = separator + script_part->toString();
|
||||
part = separator_before + script_part->toString() + separator_after;
|
||||
ctx.setVariable(String(_("param")) << (int)(j/2), script_param);
|
||||
}
|
||||
total += part;
|
||||
@@ -601,9 +635,10 @@ KeywordParamValue::operator String() const {
|
||||
return _("<param-") + safe_type + _(">") + value + _("</param-") + safe_type + _(">");
|
||||
}
|
||||
ScriptValueP KeywordParamValue::getMember(const String& name) const {
|
||||
if (name == _("type")) return to_script(type_name);
|
||||
if (name == _("separator")) return to_script(separator);
|
||||
if (name == _("value")) return to_script(value);
|
||||
if (name == _("param")) return to_script(value);
|
||||
if (name == _("type")) return to_script(type_name);
|
||||
if (name == _("separator before")) return to_script(separator_before);
|
||||
if (name == _("separator after")) return to_script(separator_after);
|
||||
if (name == _("value")) return to_script(value);
|
||||
if (name == _("param")) return to_script(value);
|
||||
return ScriptValue::getMember(name);
|
||||
}
|
||||
|
||||
@@ -42,6 +42,9 @@ class KeywordParam : public IntrusivePtrBase<KeywordParam> {
|
||||
String separator_before_is; ///< Regular expression of separator before the param
|
||||
wxRegEx separator_before_re; ///< Regular expression of separator before the param, compiled
|
||||
wxRegEx separator_before_eat;///< Regular expression of separator before the param, if eat_separator
|
||||
String separator_after_is; ///< Regular expression of separator after the param
|
||||
wxRegEx separator_after_re; ///< Regular expression of separator after the param, compiled
|
||||
wxRegEx separator_after_eat; ///< Regular expression of separator after the param, if eat_separator
|
||||
bool eat_separator; ///< Remove the separator from the match string if it also appears there (prevent duplicates)
|
||||
OptionalScript script; ///< Transformation of the value for showing as the parameter
|
||||
OptionalScript reminder_script; ///< Transformation of the value for showing in the reminder text
|
||||
@@ -53,7 +56,7 @@ class KeywordParam : public IntrusivePtrBase<KeywordParam> {
|
||||
//% /** This tries to decode the separator_before_is regex */
|
||||
//% String make_separator_before() const;
|
||||
|
||||
/// Compile regexes
|
||||
/// Compile regexes for separators
|
||||
void compile();
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
@@ -150,11 +153,11 @@ class KeywordDatabase {
|
||||
/// A script value containing the value of a keyword parameter
|
||||
class KeywordParamValue : public ScriptValue {
|
||||
public:
|
||||
KeywordParamValue(const String& type, const String& separator, const String& value)
|
||||
: type_name(type), separator(separator), value(value)
|
||||
KeywordParamValue(const String& type, const String& separator_before, const String& separator_after, const String& value)
|
||||
: type_name(type), separator_before(separator_before), separator_after(separator_after), value(value)
|
||||
{}
|
||||
String type_name;
|
||||
String separator;
|
||||
String separator_before, separator_after;
|
||||
String value;
|
||||
|
||||
virtual ScriptType type() const;
|
||||
|
||||
+23
-1
@@ -10,6 +10,9 @@
|
||||
#include <data/field.hpp>
|
||||
#include <data/field/choice.hpp>
|
||||
|
||||
DECLARE_TYPEOF_COLLECTION(String);
|
||||
DECLARE_TYPEOF_COLLECTION(StatsDimensionP);
|
||||
|
||||
// ----------------------------------------------------------------------------- : Statistics dimension
|
||||
|
||||
StatsDimension::StatsDimension()
|
||||
@@ -68,6 +71,7 @@ StatsCategory::StatsCategory(const StatsDimensionP& dim)
|
||||
|
||||
IMPLEMENT_REFLECTION_ENUM(GraphType) {
|
||||
VALUE_N("bar", GRAPH_TYPE_BAR);
|
||||
VALUE_N("stack", GRAPH_TYPE_STACK);
|
||||
VALUE_N("pie", GRAPH_TYPE_PIE);
|
||||
VALUE_N("scatter", GRAPH_TYPE_SCATTER);
|
||||
}
|
||||
@@ -78,6 +82,24 @@ IMPLEMENT_REFLECTION_NO_GET_MEMBER(StatsCategory) {
|
||||
REFLECT(description);
|
||||
REFLECT_N("icon", icon_filename);
|
||||
REFLECT(type);
|
||||
REFLECT(dimensions);
|
||||
REFLECT_N("dimensions", dimension_names);
|
||||
}
|
||||
}
|
||||
|
||||
void StatsCategory::find_dimensions(const vector<StatsDimensionP>& available) {
|
||||
if (!dimensions.empty()) return;
|
||||
FOR_EACH_CONST(n, dimension_names) {
|
||||
StatsDimensionP dim;
|
||||
FOR_EACH_CONST(d, available) {
|
||||
if (d->name == n) {
|
||||
dim = d;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!dim) {
|
||||
handle_error(_ERROR_1_("dimension not found",dim),false);
|
||||
} else {
|
||||
dimensions.push_back(dim);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,7 @@ class StatsDimension : public IntrusivePtrBase<StatsDimension> {
|
||||
/// Types of graphs
|
||||
enum GraphType
|
||||
{ GRAPH_TYPE_BAR
|
||||
, GRAPH_TYPE_STACK
|
||||
, GRAPH_TYPE_PIE
|
||||
, GRAPH_TYPE_SCATTER
|
||||
};
|
||||
@@ -59,9 +60,13 @@ class StatsCategory : public IntrusivePtrBase<StatsCategory> {
|
||||
String description; ///< Description, used in status bar
|
||||
String icon_filename; ///< Icon for lists
|
||||
Bitmap icon; ///< The loaded icon (optional of course)
|
||||
vector<StatsDimensionP> dimensions; ///< The dimensions to use, higher dimensions may be null
|
||||
vector<String> dimension_names;///< Names of the dimensions to use
|
||||
vector<StatsDimensionP> dimensions; ///< Actual dimensions
|
||||
GraphType type; ///< Type of graph to use
|
||||
|
||||
/// Initialize dimensions from dimension_names
|
||||
void find_dimensions(const vector<StatsDimensionP>& available);
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
|
||||
+193
-54
@@ -16,6 +16,7 @@ DECLARE_TYPEOF_COLLECTION(GraphElementP);
|
||||
DECLARE_TYPEOF_COLLECTION(GraphGroup);
|
||||
DECLARE_TYPEOF_COLLECTION(GraphP);
|
||||
DECLARE_TYPEOF_COLLECTION(int);
|
||||
DECLARE_TYPEOF_COLLECTION(vector<int>);
|
||||
DECLARE_TYPEOF(map<String COMMA UInt>);
|
||||
|
||||
template <typename T> inline T sgn(T v) { return v < 0 ? -1 : 1; }
|
||||
@@ -41,7 +42,6 @@ GraphData::GraphData(const GraphDataPre& d)
|
||||
// total size
|
||||
size = (UInt)d.elements.size();
|
||||
// find groups on each axis
|
||||
size_t value_count = 1;
|
||||
size_t i = 0;
|
||||
FOR_EACH(a, axes) {
|
||||
map<String,UInt> counts; // note: default constructor for UInt() does initialize to 0
|
||||
@@ -105,29 +105,40 @@ GraphData::GraphData(const GraphDataPre& d)
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
value_count *= a->groups.size();
|
||||
++i;
|
||||
}
|
||||
// count elements in each position
|
||||
values.clear();
|
||||
values.resize(value_count, 0);
|
||||
FOR_EACH_CONST(e, d.elements) {
|
||||
// find index j in elements
|
||||
size_t i = 0, j = 0;
|
||||
vector<int> group_nrs(axes.size(), -1);
|
||||
int i = 0;
|
||||
FOR_EACH(a, axes) {
|
||||
String v = e->values[i];
|
||||
size_t k = 0, l = 0;
|
||||
int j = 0;
|
||||
FOR_EACH(g, a->groups) {
|
||||
if (v == g.name) {
|
||||
k = l;
|
||||
group_nrs[i] = j;
|
||||
break;
|
||||
}
|
||||
l += 1;
|
||||
++j;
|
||||
}
|
||||
j = j * a->groups.size() + k;
|
||||
++i;
|
||||
}
|
||||
values[j] += 1;
|
||||
values.push_back(group_nrs);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphData::crossAxis(size_t axis1, size_t axis2, vector<UInt>& out) const {
|
||||
size_t a1_size = axes[axis1]->groups.size();
|
||||
size_t a2_size = axes[axis2]->groups.size();
|
||||
out.clear();
|
||||
out.resize(a1_size * a2_size, 0);
|
||||
FOR_EACH_CONST(v, values) {
|
||||
int v1 = v[axis1], v2 = v[axis2];
|
||||
if (v1 >= 0 && v2 >= 0) {
|
||||
out[a2_size * v1 + v2]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,71 +159,145 @@ bool Graph1D::findItem(const RealPoint& pos, const RealRect& rect, vector<int>&
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Graph2D
|
||||
|
||||
void Graph2D::setData(const GraphDataP& d) {
|
||||
Graph::setData(d);
|
||||
if (data->axes.size() <= max(axis1,axis2)) return;
|
||||
d->crossAxis(axis1,axis2,values);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Bar Graph
|
||||
|
||||
/// Rectangle for the bar of a bar graph
|
||||
RealRect bar_graph_bar(const RealRect& rect, int group, int group_count, int start, int end, int max) {
|
||||
double width_space = rect.width / group_count; // including spacing
|
||||
double width = width_space / 5 * 4;
|
||||
double space = width_space / 5;
|
||||
double step_height = rect.height / max; // multiplier for bar height
|
||||
double top = rect.bottom() + 1 - start * step_height;
|
||||
double bottom = rect.bottom() - end * step_height;
|
||||
RealRect result(
|
||||
rect.x + width_space * group + space / 2,
|
||||
top,
|
||||
width,
|
||||
(int)bottom - top
|
||||
);
|
||||
if (result.height < 0) {
|
||||
result.height = -result.height;
|
||||
result.y -= result.height;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/// Which column of the bar graph with count bars is coordinate x in?
|
||||
int find_bar_graph_column(double width, double x, int count) {
|
||||
double width_space = width / count; // including spacing
|
||||
double space = width_space / 5;
|
||||
// Find column in which the point could be located
|
||||
int col = int(x / width_space);
|
||||
if (col < 0 || col >= count) return -1; // not a column
|
||||
double in_col = x - col * width_space;
|
||||
if (in_col < space / 2) return -1; // left
|
||||
if (in_col > width_space - space / 2) return -1; // right
|
||||
return col;
|
||||
}
|
||||
|
||||
void BarGraph::draw(RotatedDC& dc, int current, DrawLayer layer) const {
|
||||
if (!data) return;
|
||||
// Rectangle for bars
|
||||
RealRect rect = dc.getInternalRect();
|
||||
// Bar sizes
|
||||
GraphAxis& axis = axis_data();
|
||||
int count = int(axis.groups.size());
|
||||
double width_space = rect.width / count; // including spacing
|
||||
double width = width_space / 5 * 4;
|
||||
double space = width_space / 5;
|
||||
double step_height = rect.height / axis.max; // multiplier for bar height
|
||||
// Bar sizes
|
||||
if (layer == LAYER_SELECTION) {
|
||||
// Highlight current column
|
||||
Color bg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
|
||||
if (current >= 0) {
|
||||
double x = rect.x + width_space * current;
|
||||
const GraphGroup& group = axis.groups[current];
|
||||
RealRect bar = bar_graph_bar(rect, current, count, 0, group.size, axis.max);
|
||||
dc.SetPen(*wxTRANSPARENT_PEN);
|
||||
dc.SetBrush(lerp(bg, axis.groups[current].color, 0.25));
|
||||
dc.DrawRectangle(RealRect(x + space / 2 - 5, rect.y + 1 - 15 * sgn(step_height),
|
||||
width + 10, step_height * axis.groups[current].size + (1.999 + 20) * sgn(step_height)));
|
||||
dc.SetBrush(lerp(bg, axis.groups[current].color, 0.5));
|
||||
dc.DrawRectangle(RealRect(x + space / 2 - 2, rect.y + 1 - 2 * sgn(step_height),
|
||||
width + 4, step_height * axis.groups[current].size + (1.999 + 4) * sgn(step_height)));
|
||||
dc.SetBrush(lerp(bg, group.color, 0.25));
|
||||
dc.DrawRectangle(bar.move(-5,-5,10,10));
|
||||
dc.SetBrush(lerp(bg, group.color, 0.5));
|
||||
dc.DrawRectangle(bar.move(-2,-2,4,4));
|
||||
}
|
||||
} else if (layer == LAYER_VALUES) {
|
||||
// Draw bars
|
||||
Color fg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
|
||||
dc.SetPen(fg);
|
||||
double x = rect.x;
|
||||
dc.SetPen(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
|
||||
int i = 0;
|
||||
FOR_EACH_CONST(g, axis.groups) {
|
||||
// draw bar
|
||||
dc.SetBrush(g.color);
|
||||
dc.DrawRectangle(RealRect(x + space / 2, rect.y - 1 * sgn(step_height), width, (int)(rect.y + step_height * g.size) - rect.y + 1 * sgn(step_height)));
|
||||
// draw label, aligned bottom center
|
||||
RealSize text_size = dc.GetTextExtent(g.name);
|
||||
dc.SetClippingRegion(RealRect(x + 2, rect.y + 3, width_space - 4, text_size.height));
|
||||
dc.DrawText(g.name, align_in_rect(ALIGN_TOP_CENTER, text_size, RealRect(x, rect.y + 3, width_space, 0)));
|
||||
dc.DestroyClippingRegion();
|
||||
x += width_space;
|
||||
dc.DrawRectangle(bar_graph_bar(rect, i++, count, 0, g.size, axis.max));
|
||||
}
|
||||
}
|
||||
}
|
||||
int BarGraph::findItem(const RealPoint& pos, const RealRect& rect) const {
|
||||
if (!data) return -1;
|
||||
if (pos.y > max(rect.top(), rect.bottom())) return -1; // below
|
||||
if (pos.y < min(rect.top(), rect.bottom())) return -1; // above
|
||||
return find_bar_graph_column(rect.width, pos.x - rect.x, (int)axis_data().groups.size());
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Bar Graph 2D
|
||||
|
||||
void BarGraph2D::draw(RotatedDC& dc, const vector<int>& current, DrawLayer layer) const {
|
||||
if (!data || data->axes.size() <= max(axis1,axis2)) return;
|
||||
// Rectangle for bars
|
||||
RealRect rect = dc.getInternalRect();
|
||||
GraphAxis& axis1 = axis1_data(); // the major axis
|
||||
GraphAxis& axis2 = axis2_data(); // the stacked axis
|
||||
int count = int(axis1.groups.size());
|
||||
// Bar sizes
|
||||
GraphAxis& axis = axis_data();
|
||||
int count = int(axis.groups.size());
|
||||
double width_space = rect.width / count; // including spacing
|
||||
double space = width_space / 5;
|
||||
// Find column in which this point could be located
|
||||
int col = int((pos.x - rect.x) / width_space);
|
||||
double in_col = (pos.x - rect.x) - col * width_space;
|
||||
if (in_col < space / 2 || // left
|
||||
in_col > width_space - space / 2 || // right
|
||||
pos.y > max(rect.top(), rect.bottom()) || // below
|
||||
pos.y < min(rect.top(), rect.bottom())) { // above
|
||||
return -1;
|
||||
if (layer == LAYER_SELECTION) {
|
||||
// Highlight current column
|
||||
// TODO
|
||||
} else if (layer == LAYER_VALUES) {
|
||||
// Draw bars
|
||||
dc.SetPen(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
|
||||
for (int i = 0 ; i < count ; ++i) {
|
||||
// draw stacked bars
|
||||
int start = 0;
|
||||
int j = 0;
|
||||
FOR_EACH_CONST(g2, axis2.groups) {
|
||||
int end = start + values[j++ + axis2.groups.size() * i];
|
||||
dc.SetBrush(g2.color);
|
||||
dc.DrawRectangle(bar_graph_bar(rect, i, count, start, end, axis1.max));
|
||||
start = end;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (col < 0 || (size_t)col >= axis_data().groups.size()) {
|
||||
return -1;
|
||||
} else {
|
||||
return col;
|
||||
}
|
||||
bool BarGraph2D::findItem(const RealPoint& pos, const RealRect& rect, vector<int>& out) const {
|
||||
if (!data || data->axes.size() <= max(axis1,axis2)) return false;
|
||||
if (pos.y > max(rect.top(), rect.bottom())) return false; // below
|
||||
if (pos.y < min(rect.top(), rect.bottom())) return false; // above
|
||||
// column
|
||||
GraphAxis& axis1 = axis1_data(); // the major axis
|
||||
int count = (int)axis1.groups.size();
|
||||
int col = find_bar_graph_column(rect.width, pos.x - rect.x, count);
|
||||
// row
|
||||
int max_value = (int)axis1.max;
|
||||
int value = (rect.bottom() - pos.y) / rect.height * max_value;
|
||||
if (value < 0 || value > max_value) return false;
|
||||
// find row
|
||||
int row = -1;
|
||||
size_t vs = col * axis2_data().groups.size();
|
||||
for (int i = 0 ; i < count ; ++i) {
|
||||
value -= values[vs+i];
|
||||
if (value < 0) {
|
||||
// in this layer of the stack
|
||||
row = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (row == -1) return false;
|
||||
// done
|
||||
out.clear();
|
||||
out.insert(out.begin(), data->axes.size(), -1);
|
||||
out.at(this->axis1) = col;
|
||||
out.at(this->axis2) = row;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Pie Graph
|
||||
@@ -286,6 +371,59 @@ int PieGraph::findItem(const RealPoint& pos, const RealRect& rect) const {
|
||||
|
||||
// ----------------------------------------------------------------------------- : Graph Legend
|
||||
|
||||
// ----------------------------------------------------------------------------- : Graph label axis
|
||||
|
||||
void GraphLabelAxis::draw(RotatedDC& dc, int current, DrawLayer layer) const {
|
||||
RealRect rect = dc.getInternalRect();
|
||||
GraphAxis& axis = axis_data();
|
||||
int count = int(axis.groups.size());
|
||||
// Draw
|
||||
if (layer == LAYER_SELECTION) {
|
||||
// highlight selection
|
||||
} else if (layer != LAYER_AXES) {
|
||||
if (direction == HORIZONTAL) {
|
||||
double width = rect.width / count; // width of an item
|
||||
// Draw labels
|
||||
double x = rect.x;
|
||||
FOR_EACH_CONST(g, axis.groups) {
|
||||
// draw label, aligned bottom center
|
||||
RealSize text_size = dc.GetTextExtent(g.name);
|
||||
dc.SetClippingRegion(RealRect(x + 2, rect.bottom() + 3, width - 4, text_size.height));
|
||||
dc.DrawText(g.name, align_in_rect(ALIGN_TOP_CENTER, text_size, RealRect(x, rect.bottom() + 3, width, 0)));
|
||||
dc.DestroyClippingRegion();
|
||||
x += width;
|
||||
}
|
||||
// Draw lines
|
||||
Color bg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
|
||||
Color fg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
|
||||
if (draw_lines) {
|
||||
dc.SetPen(lerp(bg, fg, 0.5));
|
||||
for (int i = 0 ; i < count ; ++i) {
|
||||
dc.DrawLine(RealPoint(rect.x + i*width, rect.top()), RealPoint(rect.x + i*width, rect.bottom()));
|
||||
}
|
||||
}
|
||||
// always draw axis line
|
||||
dc.SetPen(fg);
|
||||
dc.DrawLine(rect.topLeft(), rect.bottomLeft());
|
||||
} else {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
}
|
||||
int GraphLabelAxis::findItem(const RealPoint& pos, const RealRect& rect) const {
|
||||
GraphAxis& axis = axis_data();
|
||||
int col;
|
||||
if (direction == HORIZONTAL) {
|
||||
col = (pos.x - rect.x) / rect.width * axis.groups.size();
|
||||
if (pos.y < rect.bottom()) return -1;
|
||||
} else {
|
||||
col = (pos.y - rect.y) / rect.height * axis.groups.size();
|
||||
if (pos.x > rect.left()) return -1;
|
||||
}
|
||||
if (col < 0 || col >= (int)axis.groups.size()) return -1;
|
||||
return col;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Graph value axis
|
||||
|
||||
void GraphValueAxis::draw(RotatedDC& dc, int current, DrawLayer layer) const {
|
||||
@@ -296,7 +434,7 @@ void GraphValueAxis::draw(RotatedDC& dc, int current, DrawLayer layer) const {
|
||||
GraphAxis& axis = axis_data();
|
||||
double step_height = rect.height / axis.max; // height of a single value
|
||||
dc.SetFont(*wxNORMAL_FONT);
|
||||
UInt label_step = UInt(max(1.0, (dc.GetCharHeight() + 1) / step_height)); // values per labeled line
|
||||
UInt label_step = (UInt)floor(max(1.0, (dc.GetCharHeight() + 1) / step_height)); // values per labeled line
|
||||
// Colors
|
||||
Color bg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
|
||||
Color fg = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
|
||||
@@ -304,7 +442,7 @@ void GraphValueAxis::draw(RotatedDC& dc, int current, DrawLayer layer) const {
|
||||
dc.SetPen(lerp(bg, fg, 0.5));
|
||||
for (UInt i = 0 ; i <= axis.max ; ++i) {
|
||||
if (i % label_step == 0) {
|
||||
int y = rect.top() + i * step_height;
|
||||
int y = rect.bottom() - i * step_height;
|
||||
dc.DrawLine(RealPoint(rect.left() - 2, y), RealPoint(rect.right(), y));
|
||||
// draw label, aligned middle right
|
||||
String label; label << i;
|
||||
@@ -312,10 +450,9 @@ void GraphValueAxis::draw(RotatedDC& dc, int current, DrawLayer layer) const {
|
||||
dc.DrawText(label, align_in_rect(ALIGN_MIDDLE_RIGHT, text_size, RealRect(rect.x - 4, y, 0, 0)));
|
||||
}
|
||||
}
|
||||
// Draw axes
|
||||
// Draw axis
|
||||
dc.SetPen(fg);
|
||||
dc.DrawLine(rect.topLeft() - RealSize(2,0), rect.topRight());
|
||||
dc.DrawLine(rect.topLeft(), rect.bottomLeft());
|
||||
dc.DrawLine(rect.bottomLeft() - RealSize(2,0), rect.bottomRight());
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Graph with margins
|
||||
@@ -369,8 +506,10 @@ GraphControl::GraphControl(Window* parent, int id)
|
||||
//*
|
||||
intrusive_ptr<GraphContainer> combined(new GraphContainer());
|
||||
combined->add(new_intrusive1<GraphValueAxis>(0));
|
||||
combined->add(new_intrusive1<BarGraph>(0));
|
||||
graph = new_intrusive6<GraphWithMargins>(combined, 23,8,7,20, true);
|
||||
combined->add(new_intrusive2<GraphLabelAxis>(0, HORIZONTAL));
|
||||
//combined->add(new_intrusive1<BarGraph>(0));
|
||||
combined->add(new_intrusive2<BarGraph2D>(0,1));
|
||||
graph = new_intrusive6<GraphWithMargins>(combined, 23,8,7,20, false);
|
||||
/*/
|
||||
intrusive_ptr<GraphContainer> combined(new GraphContainer());
|
||||
combined->add(new_intrusive1<PieGraph>(0));
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
// ----------------------------------------------------------------------------- : Includes
|
||||
|
||||
#include <util/prec.hpp>
|
||||
#include <util/alignment.hpp>
|
||||
#include <util/rotation.hpp>
|
||||
|
||||
DECLARE_POINTER_TYPE(GraphAxis);
|
||||
@@ -90,9 +91,12 @@ class GraphData : public IntrusivePtrBase<GraphData> {
|
||||
public:
|
||||
GraphData(const GraphDataPre&);
|
||||
|
||||
vector<GraphAxisP> axes; ///< The axes in the data
|
||||
vector<UInt> values; ///< Multi dimensional (dim = axes.size()) array of values
|
||||
UInt size; ///< Total number of elements
|
||||
vector<GraphAxisP> axes; ///< The axes in the data
|
||||
vector<vector<int> > values; ///< All elements, with the group number for each axis, or -1
|
||||
UInt size; ///< Total number of elements
|
||||
|
||||
/// Create a cross table for two axes
|
||||
void crossAxis(size_t axis1, size_t axis2, vector<UInt>& out) const;
|
||||
};
|
||||
|
||||
|
||||
@@ -138,6 +142,18 @@ class Graph1D : public Graph {
|
||||
inline GraphAxis& axis_data() const { return *data->axes.at(axis); }
|
||||
};
|
||||
|
||||
/// Base class for 2 dimensional graph components
|
||||
class Graph2D : public Graph {
|
||||
public:
|
||||
inline Graph2D(size_t axis1, size_t axis2) : axis1(axis1), axis2(axis2) {}
|
||||
virtual void setData(const GraphDataP& d);
|
||||
protected:
|
||||
size_t axis1, axis2;
|
||||
vector<UInt> values; // axis1.size * axis2.size array
|
||||
inline GraphAxis& axis1_data() const { return *data->axes.at(axis1); }
|
||||
inline GraphAxis& axis2_data() const { return *data->axes.at(axis2); }
|
||||
};
|
||||
|
||||
/// A bar graph
|
||||
class BarGraph : public Graph1D {
|
||||
public:
|
||||
@@ -146,9 +162,13 @@ class BarGraph : public Graph1D {
|
||||
virtual int findItem(const RealPoint& pos, const RealRect& rect) const;
|
||||
};
|
||||
|
||||
// TODO
|
||||
//class BarGraph2D {
|
||||
//};
|
||||
// A bar graph with stacked bars
|
||||
class BarGraph2D : public Graph2D {
|
||||
public:
|
||||
inline BarGraph2D(size_t axis_h, size_t axis_v) : Graph2D(axis_h, axis_v) {}
|
||||
virtual void draw(RotatedDC& dc, const vector<int>& current, DrawLayer layer) const;
|
||||
virtual bool findItem(const RealPoint& pos, const RealRect& rect, vector<int>& out) const;
|
||||
};
|
||||
|
||||
/// A pie graph
|
||||
class PieGraph : public Graph1D {
|
||||
@@ -169,9 +189,20 @@ class GraphLegend : public Graph1D {
|
||||
//class GraphTable {
|
||||
//};
|
||||
|
||||
//class GraphAxis : public Graph1D {
|
||||
// virtual void draw(RotatedDC& dc) const;
|
||||
//};
|
||||
/// Draws a horizontal/vertical axis for group labels
|
||||
class GraphLabelAxis : public Graph1D {
|
||||
public:
|
||||
inline GraphLabelAxis(size_t axis, Direction direction, bool rotate = false, bool draw_lines = false)
|
||||
: Graph1D(axis), direction(direction), rotate(rotate), draw_lines(draw_lines)
|
||||
{}
|
||||
virtual void draw(RotatedDC& dc, int current, DrawLayer layer) const;
|
||||
virtual int findItem(const RealPoint& pos, const RealRect& rect) const;
|
||||
private:
|
||||
Direction direction;
|
||||
int levels;
|
||||
bool rotate;
|
||||
bool draw_lines;
|
||||
};
|
||||
|
||||
/// Draws an a vertical axis for counts
|
||||
class GraphValueAxis : public Graph1D {
|
||||
|
||||
@@ -147,6 +147,8 @@ void StatsPanel::onCategorySelect() {
|
||||
if (categories->hasSelection()) {
|
||||
StatsCategory& cat = categories->getSelection();
|
||||
GraphDataPre d;
|
||||
cat.find_dimensions(set->game->statistics_dimensions);
|
||||
// create axes
|
||||
FOR_EACH(dim, cat.dimensions) {
|
||||
d.axes.push_back(new_intrusive4<GraphAxis>(
|
||||
dim->name,
|
||||
@@ -156,6 +158,7 @@ void StatsPanel::onCategorySelect() {
|
||||
)
|
||||
);
|
||||
}
|
||||
// find values
|
||||
FOR_EACH(card, set->cards) {
|
||||
Context& ctx = set->getContext(card);
|
||||
GraphElementP e(new GraphElement);
|
||||
@@ -173,6 +176,7 @@ void StatsPanel::onCategorySelect() {
|
||||
d.elements.push_back(e);
|
||||
}
|
||||
}
|
||||
// TODO graph->setLayout(cat.type)
|
||||
graph->setData(d);
|
||||
filterCards();
|
||||
}
|
||||
|
||||
@@ -11,6 +11,16 @@
|
||||
#include <util/tagged_string.hpp>
|
||||
#include <util/error.hpp>
|
||||
|
||||
// ----------------------------------------------------------------------------- : Util
|
||||
|
||||
bool is_vowel(Char c) {
|
||||
return c == _('a') || c == _('e') || c == _('i') || c == _('o') || c == _('u')
|
||||
|| c == _('A') || c == _('E') || c == _('I') || c == _('O') || c == _('U');
|
||||
}
|
||||
inline bool is_constant(Char c) {
|
||||
return !is_vowel(c);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Numbers
|
||||
|
||||
/// Write a number using words, for example 23 -> "twenty-three"
|
||||
@@ -110,20 +120,41 @@ SCRIPT_FUNCTION(english_number_multiple) {
|
||||
String english_singular(const String& str) {
|
||||
if (str.size() > 3 && is_substr(str, str.size()-3, _("ies"))) {
|
||||
return str.substr(0, str.size() - 3) + _("y");
|
||||
} else if (str.size() > 3 && is_substr(str, str.size()-3, _("oes"))) {
|
||||
return str.substr(0, str.size() - 2);
|
||||
} else if (str.size() > 4 && is_substr(str, str.size()-4, _("ches"))) {
|
||||
return str.substr(0, str.size() - 2);
|
||||
} else if (str.size() > 4 && is_substr(str, str.size()-4, _("shes"))) {
|
||||
return str.substr(0, str.size() - 2);
|
||||
} else if (str.size() > 4 && is_substr(str, str.size()-4, _("sses"))) {
|
||||
return str.substr(0, str.size() - 2);
|
||||
} else if (str.size() > 5 && is_substr(str, str.size()-3, _("ves")) && (is_substr(str, str.size()-5, _("el")) || is_substr(str, str.size()-5, _("ar")) )) {
|
||||
return str.substr(0, str.size() - 3) + _("f");
|
||||
} else if (str.size() > 1 && str.GetChar(str.size() - 1) == _('s')) {
|
||||
return str.substr(0, str.size() - 1);
|
||||
} else if (str.size() >= 3 && is_substr(str, str.size()-3, _("men"))) {
|
||||
return str.substr(0, str.size() - 2) + _("an");
|
||||
} else {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
String english_plural(const String& str) {
|
||||
if (str.size() > 1 && str.GetChar(str.size() - 1) == _('y')) {
|
||||
return str.substr(0, str.size() - 1) + _("ies");
|
||||
} else if (str.size() > 1 && str.GetChar(str.size() - 1) == _('s')) {
|
||||
return str + _("es");
|
||||
} else {
|
||||
return str + _("s");
|
||||
if (str.size() > 2) {
|
||||
Char a = str.GetChar(str.size() - 2);
|
||||
Char b = str.GetChar(str.size() - 1);
|
||||
if (b == _('y') && is_constant(a)) {
|
||||
return str.substr(0, str.size() - 1) + _("ies");
|
||||
} else if (b == _('o') && is_constant(a)) {
|
||||
return str + _("es");
|
||||
} else if ((a == _('s') || a == _('c')) && b == _('h')) {
|
||||
return str + _("es");
|
||||
} else if (b == _('s')) {
|
||||
return str + _("es");
|
||||
} else {
|
||||
return str + _("s");
|
||||
}
|
||||
}
|
||||
return str + _("s");
|
||||
}
|
||||
|
||||
// script_english_singular/plural/singplur
|
||||
@@ -155,11 +186,6 @@ SCRIPT_FUNCTION(english_plural) {
|
||||
|
||||
// ----------------------------------------------------------------------------- : Hints
|
||||
|
||||
bool is_vowel(Char c) {
|
||||
return c == _('a') || c == _('e') || c == _('i') || c == _('o') || c == _('u')
|
||||
|| c == _('A') || c == _('E') || c == _('I') || c == _('O') || c == _('U');
|
||||
}
|
||||
|
||||
/// Process english hints in the input string
|
||||
/** A hint is formed by
|
||||
* 1. an insertion of a parameter, <param-..>...</param->.
|
||||
|
||||
@@ -57,7 +57,9 @@ RealPoint align_in_rect(Alignment align, const RealSize& to_align, const RealRec
|
||||
/// Direction to place something in
|
||||
enum Direction {
|
||||
LEFT_TO_RIGHT, RIGHT_TO_LEFT,
|
||||
TOP_TO_BOTTOM, BOTTOM_TO_TOP
|
||||
TOP_TO_BOTTOM, BOTTOM_TO_TOP,
|
||||
HORIZONTAL = LEFT_TO_RIGHT,
|
||||
VERTICAL = TOP_TO_BOTTOM
|
||||
};
|
||||
|
||||
/// Move a point in a direction
|
||||
|
||||
@@ -173,3 +173,8 @@ Root: HKCR; Subkey: "MagicSetEditor2Symbol\shell\open\command"; ValueType: strin
|
||||
[Run]
|
||||
Filename: "{app}\mse.exe"; Description: "Start Magic Set Editor"; Flags: postinstall nowait skipifsilent unchecked
|
||||
|
||||
; ------------------------------ : Uninstaller
|
||||
|
||||
[UninstallDelete]
|
||||
Type: filesandordirs; Name: "{userappdata}\Magic Set Editor"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user