mirror of
https://github.com/amyinspace/MagicSetEditor2.git
synced 2026-06-11 21:27:01 -04:00
Change tabs to two spaces.
This commit is contained in:
+61
-61
@@ -25,91 +25,91 @@ enum AddingOrRemoving {ADD, REMOVE};
|
||||
template <typename T>
|
||||
class GenericAddAction {
|
||||
public:
|
||||
GenericAddAction(AddingOrRemoving, const T& item, const vector<T>& container);
|
||||
GenericAddAction(AddingOrRemoving, const vector<T>& items, const vector<T>& container);
|
||||
|
||||
String getName() const;
|
||||
void perform(vector<T>& container, bool to_undo) const;
|
||||
|
||||
/// A step of removing/adding
|
||||
struct Step {
|
||||
inline Step(size_t pos, const T& item) : pos(pos), item(item) {}
|
||||
size_t pos;
|
||||
T item;
|
||||
};
|
||||
bool adding; ///< Were objects added? (as opposed to removed)
|
||||
vector<Step> steps; ///< Added/removed objects, sorted by ascending pos
|
||||
GenericAddAction(AddingOrRemoving, const T& item, const vector<T>& container);
|
||||
GenericAddAction(AddingOrRemoving, const vector<T>& items, const vector<T>& container);
|
||||
|
||||
String getName() const;
|
||||
void perform(vector<T>& container, bool to_undo) const;
|
||||
|
||||
/// A step of removing/adding
|
||||
struct Step {
|
||||
inline Step(size_t pos, const T& item) : pos(pos), item(item) {}
|
||||
size_t pos;
|
||||
T item;
|
||||
};
|
||||
bool adding; ///< Were objects added? (as opposed to removed)
|
||||
vector<Step> steps; ///< Added/removed objects, sorted by ascending pos
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Implementation
|
||||
|
||||
template <typename T>
|
||||
bool contains(const vector<T>& items, const T& item) {
|
||||
return find(items.begin(), items.end(), item) != items.end();
|
||||
return find(items.begin(), items.end(), item) != items.end();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
GenericAddAction<T>::GenericAddAction(AddingOrRemoving ar, const T& item, const vector<T>& container)
|
||||
: adding(ar == ADD)
|
||||
: adding(ar == ADD)
|
||||
{
|
||||
if (ar == ADD) {
|
||||
size_t pos = container.size();
|
||||
steps.push_back(Step(pos, item));
|
||||
} else {
|
||||
for (size_t pos = 0 ; pos < container.size() ; ++pos) {
|
||||
if (container[pos] == item) {
|
||||
steps.push_back(Step(pos, item));
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw InternalError(_("Item to remove not found in container"));
|
||||
}
|
||||
if (ar == ADD) {
|
||||
size_t pos = container.size();
|
||||
steps.push_back(Step(pos, item));
|
||||
} else {
|
||||
for (size_t pos = 0 ; pos < container.size() ; ++pos) {
|
||||
if (container[pos] == item) {
|
||||
steps.push_back(Step(pos, item));
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw InternalError(_("Item to remove not found in container"));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
GenericAddAction<T>::GenericAddAction(AddingOrRemoving ar, const vector<T>& items, const vector<T>& container)
|
||||
: adding(ar == ADD)
|
||||
: adding(ar == ADD)
|
||||
{
|
||||
if (ar == ADD) {
|
||||
size_t pos = container.size();
|
||||
for (typename vector<T>::const_iterator it = items.begin() ; it != items.end() ; ++it) {
|
||||
steps.push_back(Step(pos++, *it));
|
||||
}
|
||||
} else {
|
||||
for (size_t pos = 0 ; pos < container.size() ; ++pos) {
|
||||
if (contains(items, container[pos])) {
|
||||
steps.push_back(Step(pos, container[pos]));
|
||||
}
|
||||
}
|
||||
if (steps.size() != items.size()) {
|
||||
throw InternalError(_("Item to remove not found in container"));
|
||||
}
|
||||
}
|
||||
if (ar == ADD) {
|
||||
size_t pos = container.size();
|
||||
for (typename vector<T>::const_iterator it = items.begin() ; it != items.end() ; ++it) {
|
||||
steps.push_back(Step(pos++, *it));
|
||||
}
|
||||
} else {
|
||||
for (size_t pos = 0 ; pos < container.size() ; ++pos) {
|
||||
if (contains(items, container[pos])) {
|
||||
steps.push_back(Step(pos, container[pos]));
|
||||
}
|
||||
}
|
||||
if (steps.size() != items.size()) {
|
||||
throw InternalError(_("Item to remove not found in container"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
String GenericAddAction<T>::getName() const {
|
||||
String type = type_name(steps.front().item) + (steps.size() == 1 ? _("") : _("s"));
|
||||
return adding ? _ACTION_1_("add item", type) : _ACTION_1_("remove item", type);
|
||||
String type = type_name(steps.front().item) + (steps.size() == 1 ? _("") : _("s"));
|
||||
return adding ? _ACTION_1_("add item", type) : _ACTION_1_("remove item", type);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void GenericAddAction<T>::perform(vector<T>& container, bool to_undo) const {
|
||||
if (adding != to_undo) {
|
||||
// (re)insert the items
|
||||
// ascending order, this is the reverse of removal
|
||||
FOR_EACH_CONST(s, steps) {
|
||||
assert(s.pos <= container.size());
|
||||
container.insert(container.begin() + s.pos, s.item);
|
||||
}
|
||||
} else {
|
||||
// remove the items
|
||||
// descending order, because earlier removals shift the rest of the vector
|
||||
FOR_EACH_CONST_REVERSE(s, steps) {
|
||||
assert(s.pos < container.size());
|
||||
container.erase(container.begin() + s.pos);
|
||||
}
|
||||
}
|
||||
if (adding != to_undo) {
|
||||
// (re)insert the items
|
||||
// ascending order, this is the reverse of removal
|
||||
FOR_EACH_CONST(s, steps) {
|
||||
assert(s.pos <= container.size());
|
||||
container.insert(container.begin() + s.pos, s.item);
|
||||
}
|
||||
} else {
|
||||
// remove the items
|
||||
// descending order, because earlier removals shift the rest of the vector
|
||||
FOR_EACH_CONST_REVERSE(s, steps) {
|
||||
assert(s.pos < container.size());
|
||||
container.erase(container.begin() + s.pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
+148
-148
@@ -20,191 +20,191 @@ DECLARE_TYPEOF_COLLECTION(KeywordModeP);
|
||||
// ----------------------------------------------------------------------------- : Add Keyword
|
||||
|
||||
AddKeywordAction::AddKeywordAction(Set& set)
|
||||
: KeywordListAction(set)
|
||||
, action(ADD, intrusive(new Keyword()), set.keywords)
|
||||
: KeywordListAction(set)
|
||||
, action(ADD, intrusive(new Keyword()), set.keywords)
|
||||
{
|
||||
Keyword& keyword = *action.steps.front().item;
|
||||
// find default mode
|
||||
FOR_EACH(mode, set.game->keyword_modes) {
|
||||
if (mode->is_default) {
|
||||
keyword.mode = mode->name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Keyword& keyword = *action.steps.front().item;
|
||||
// find default mode
|
||||
FOR_EACH(mode, set.game->keyword_modes) {
|
||||
if (mode->is_default) {
|
||||
keyword.mode = mode->name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
AddKeywordAction::AddKeywordAction(AddingOrRemoving ar, Set& set, const KeywordP& keyword)
|
||||
: KeywordListAction(set)
|
||||
, action(ar, keyword, set.keywords)
|
||||
: KeywordListAction(set)
|
||||
, action(ar, keyword, set.keywords)
|
||||
{}
|
||||
/*AddKeywordAction::AddKeywordAction(AddingOrRemoving ar, Set& set, const vector<KeywordP>& keyword)
|
||||
: KeywordListAction(set)
|
||||
, action(ar, keywords, set.keywords)
|
||||
: KeywordListAction(set)
|
||||
, action(ar, keywords, set.keywords)
|
||||
{}*/
|
||||
|
||||
String AddKeywordAction::getName(bool to_undo) const {
|
||||
return action.getName();
|
||||
return action.getName();
|
||||
}
|
||||
|
||||
void AddKeywordAction::perform(bool to_undo) {
|
||||
action.perform(set.keywords, to_undo);
|
||||
set.keyword_db.clear();
|
||||
action.perform(set.keywords, to_undo);
|
||||
set.keyword_db.clear();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Changing keywords
|
||||
|
||||
KeywordReminderTextValue::KeywordReminderTextValue(Set& set, const TextFieldP& field, Keyword* keyword, bool editable)
|
||||
: KeywordTextValue(field, keyword, &keyword->reminder.getMutableUnparsed(), editable)
|
||||
, set(set)
|
||||
, keyword(*keyword)
|
||||
: KeywordTextValue(field, keyword, &keyword->reminder.getMutableUnparsed(), editable)
|
||||
, set(set)
|
||||
, keyword(*keyword)
|
||||
{}
|
||||
|
||||
void KeywordReminderTextValue::store() {
|
||||
if (!editable) {
|
||||
retrieve();
|
||||
return;
|
||||
}
|
||||
// new value
|
||||
String new_value = untag(value);
|
||||
// Try to parse the script
|
||||
vector<ScriptParseError> parse_errors;
|
||||
ScriptP new_script = parse(new_value, nullptr, true, parse_errors);
|
||||
if (parse_errors.empty()) {
|
||||
// parsed okay
|
||||
if (checkScript(new_script)) {
|
||||
// also runs okay, assign
|
||||
keyword.reminder.setScriptP(new_script);
|
||||
keyword.reminder.setUnparsed(new_value);
|
||||
}
|
||||
} else {
|
||||
// parse errors, report
|
||||
errors = ScriptParseErrors(parse_errors).what();
|
||||
}
|
||||
// re-highlight input, show errors
|
||||
highlight(new_value, parse_errors);
|
||||
if (!editable) {
|
||||
retrieve();
|
||||
return;
|
||||
}
|
||||
// new value
|
||||
String new_value = untag(value);
|
||||
// Try to parse the script
|
||||
vector<ScriptParseError> parse_errors;
|
||||
ScriptP new_script = parse(new_value, nullptr, true, parse_errors);
|
||||
if (parse_errors.empty()) {
|
||||
// parsed okay
|
||||
if (checkScript(new_script)) {
|
||||
// also runs okay, assign
|
||||
keyword.reminder.setScriptP(new_script);
|
||||
keyword.reminder.setUnparsed(new_value);
|
||||
}
|
||||
} else {
|
||||
// parse errors, report
|
||||
errors = ScriptParseErrors(parse_errors).what();
|
||||
}
|
||||
// re-highlight input, show errors
|
||||
highlight(new_value, parse_errors);
|
||||
}
|
||||
|
||||
void KeywordReminderTextValue::retrieve() {
|
||||
vector<ScriptParseError> no_errors;
|
||||
highlight(*underlying, no_errors);
|
||||
vector<ScriptParseError> no_errors;
|
||||
highlight(*underlying, no_errors);
|
||||
}
|
||||
|
||||
void KeywordReminderTextValue::highlight(const String& code, const vector<ScriptParseError>& errors) {
|
||||
// Add tags to indicate code / syntax highlight
|
||||
// i.e. bla {if code "x" } bla
|
||||
// becomes:
|
||||
// bla <code>{<code-kw>if</code-kw> code "<code-string>x</code-string>" } bla
|
||||
String new_value;
|
||||
vector<int> in_brace; // types of braces we are in, 0 for code brace, 1 for string escapes
|
||||
bool in_string = true;
|
||||
vector<ScriptParseError>::const_iterator error = errors.begin();
|
||||
for (size_t pos = 0 ; pos < code.size() ; ) {
|
||||
// error underlining
|
||||
while (error != errors.end() && error->start == error->end) ++error;
|
||||
if (error != errors.end()) {
|
||||
if (error->start == pos) {
|
||||
new_value += _("<error>");
|
||||
}
|
||||
if (error->end == pos) {
|
||||
++error;
|
||||
if (error == errors.end() || error->start > pos) {
|
||||
new_value += _("</error>");
|
||||
} else {
|
||||
// immediatly open again
|
||||
}
|
||||
}
|
||||
}
|
||||
// process a character
|
||||
Char c = code.GetChar(pos);
|
||||
if (c == _('<')) {
|
||||
new_value += _('\1'); // escape
|
||||
++pos;
|
||||
} else if (c == _('{')) {
|
||||
if (in_string) {
|
||||
new_value += _("<code>");
|
||||
in_brace.push_back(1);
|
||||
in_string = false;
|
||||
} else {
|
||||
in_brace.push_back(0);
|
||||
}
|
||||
new_value += c;
|
||||
++pos;
|
||||
} else if (c == _('}') && !in_string) {
|
||||
new_value += c;
|
||||
if (!in_brace.empty() && in_brace.back() == 1) {
|
||||
new_value += _("</code>");
|
||||
in_string = true;
|
||||
}
|
||||
if (!in_brace.empty()) in_brace.pop_back();
|
||||
++pos;
|
||||
} else if (c == _('"')) {
|
||||
if (in_string) {
|
||||
in_string = false;
|
||||
new_value += _("\"</code-str><code>");
|
||||
} else {
|
||||
in_string = true;
|
||||
new_value += _("</code><code-str>\"");
|
||||
}
|
||||
++pos;
|
||||
} else if (c == _('\\') && in_string && pos + 1 < code.size()) {
|
||||
new_value += c + code.GetChar(pos + 1); // escape code
|
||||
pos += 2;
|
||||
} else if (is_substr(code, pos, _("if ")) && !in_string) {
|
||||
new_value += _("<code-kw>if</code-kw> ");
|
||||
pos += 3;
|
||||
} else if (is_substr(code, pos, _("then ")) && !in_string) {
|
||||
new_value += _("<code-kw>then</code-kw> ");
|
||||
pos += 5;
|
||||
} else if (is_substr(code, pos, _("else ")) && !in_string) {
|
||||
new_value += _("<code-kw>else</code-kw> ");
|
||||
pos += 5;
|
||||
} else if (is_substr(code, pos, _("for ")) && !in_string) {
|
||||
new_value += _("<code-kw>for</code-kw> ");
|
||||
pos += 4;
|
||||
} else if (is_substr(code, pos, _("param")) && !in_string) {
|
||||
// parameter reference
|
||||
size_t end = code.find_first_not_of(_("0123456789"), pos + 5);
|
||||
if (end == String::npos) end = code.size();
|
||||
String param = code.substr(pos, end-pos);
|
||||
new_value += _("<ref-") + param + _(">") + param + _("</ref-") + param + _(">");
|
||||
pos = end;
|
||||
} else {
|
||||
new_value += c;
|
||||
++pos;
|
||||
}
|
||||
}
|
||||
// set
|
||||
value = new_value;
|
||||
// Add tags to indicate code / syntax highlight
|
||||
// i.e. bla {if code "x" } bla
|
||||
// becomes:
|
||||
// bla <code>{<code-kw>if</code-kw> code "<code-string>x</code-string>" } bla
|
||||
String new_value;
|
||||
vector<int> in_brace; // types of braces we are in, 0 for code brace, 1 for string escapes
|
||||
bool in_string = true;
|
||||
vector<ScriptParseError>::const_iterator error = errors.begin();
|
||||
for (size_t pos = 0 ; pos < code.size() ; ) {
|
||||
// error underlining
|
||||
while (error != errors.end() && error->start == error->end) ++error;
|
||||
if (error != errors.end()) {
|
||||
if (error->start == pos) {
|
||||
new_value += _("<error>");
|
||||
}
|
||||
if (error->end == pos) {
|
||||
++error;
|
||||
if (error == errors.end() || error->start > pos) {
|
||||
new_value += _("</error>");
|
||||
} else {
|
||||
// immediatly open again
|
||||
}
|
||||
}
|
||||
}
|
||||
// process a character
|
||||
Char c = code.GetChar(pos);
|
||||
if (c == _('<')) {
|
||||
new_value += _('\1'); // escape
|
||||
++pos;
|
||||
} else if (c == _('{')) {
|
||||
if (in_string) {
|
||||
new_value += _("<code>");
|
||||
in_brace.push_back(1);
|
||||
in_string = false;
|
||||
} else {
|
||||
in_brace.push_back(0);
|
||||
}
|
||||
new_value += c;
|
||||
++pos;
|
||||
} else if (c == _('}') && !in_string) {
|
||||
new_value += c;
|
||||
if (!in_brace.empty() && in_brace.back() == 1) {
|
||||
new_value += _("</code>");
|
||||
in_string = true;
|
||||
}
|
||||
if (!in_brace.empty()) in_brace.pop_back();
|
||||
++pos;
|
||||
} else if (c == _('"')) {
|
||||
if (in_string) {
|
||||
in_string = false;
|
||||
new_value += _("\"</code-str><code>");
|
||||
} else {
|
||||
in_string = true;
|
||||
new_value += _("</code><code-str>\"");
|
||||
}
|
||||
++pos;
|
||||
} else if (c == _('\\') && in_string && pos + 1 < code.size()) {
|
||||
new_value += c + code.GetChar(pos + 1); // escape code
|
||||
pos += 2;
|
||||
} else if (is_substr(code, pos, _("if ")) && !in_string) {
|
||||
new_value += _("<code-kw>if</code-kw> ");
|
||||
pos += 3;
|
||||
} else if (is_substr(code, pos, _("then ")) && !in_string) {
|
||||
new_value += _("<code-kw>then</code-kw> ");
|
||||
pos += 5;
|
||||
} else if (is_substr(code, pos, _("else ")) && !in_string) {
|
||||
new_value += _("<code-kw>else</code-kw> ");
|
||||
pos += 5;
|
||||
} else if (is_substr(code, pos, _("for ")) && !in_string) {
|
||||
new_value += _("<code-kw>for</code-kw> ");
|
||||
pos += 4;
|
||||
} else if (is_substr(code, pos, _("param")) && !in_string) {
|
||||
// parameter reference
|
||||
size_t end = code.find_first_not_of(_("0123456789"), pos + 5);
|
||||
if (end == String::npos) end = code.size();
|
||||
String param = code.substr(pos, end-pos);
|
||||
new_value += _("<ref-") + param + _(">") + param + _("</ref-") + param + _(">");
|
||||
pos = end;
|
||||
} else {
|
||||
new_value += c;
|
||||
++pos;
|
||||
}
|
||||
}
|
||||
// set
|
||||
value = new_value;
|
||||
}
|
||||
|
||||
bool KeywordReminderTextValue::checkScript(const ScriptP& script) {
|
||||
try {
|
||||
Context& ctx = set.cards.empty() ? set.getContext() : set.getContext(set.cards.front());
|
||||
LocalScope scope(ctx);
|
||||
for (size_t i = 0 ; i < keyword.parameters.size() ; ++i) {
|
||||
const KeywordParam& kwp = *keyword.parameters[i];
|
||||
String param_name = String(_("param")) << (int)(i+1);
|
||||
String param_value = _("<atom-kwpph>") + (kwp.placeholder.empty() ? kwp.name : kwp.placeholder) + _("</atom-kwpph>");
|
||||
ctx.setVariable(param_name, intrusive(new KeywordParamValue(kwp.name, _(""), _(""), param_value)));
|
||||
}
|
||||
script->eval(ctx);
|
||||
errors.clear();
|
||||
return true;
|
||||
} catch (const Error& e) {
|
||||
errors = e.what();
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
Context& ctx = set.cards.empty() ? set.getContext() : set.getContext(set.cards.front());
|
||||
LocalScope scope(ctx);
|
||||
for (size_t i = 0 ; i < keyword.parameters.size() ; ++i) {
|
||||
const KeywordParam& kwp = *keyword.parameters[i];
|
||||
String param_name = String(_("param")) << (int)(i+1);
|
||||
String param_value = _("<atom-kwpph>") + (kwp.placeholder.empty() ? kwp.name : kwp.placeholder) + _("</atom-kwpph>");
|
||||
ctx.setVariable(param_name, intrusive(new KeywordParamValue(kwp.name, _(""), _(""), param_value)));
|
||||
}
|
||||
script->eval(ctx);
|
||||
errors.clear();
|
||||
return true;
|
||||
} catch (const Error& e) {
|
||||
errors = e.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Changing keywords : mode
|
||||
|
||||
ChangeKeywordModeAction::ChangeKeywordModeAction(Keyword& keyword, const String& new_mode)
|
||||
: keyword(keyword), mode(new_mode)
|
||||
: keyword(keyword), mode(new_mode)
|
||||
{}
|
||||
|
||||
String ChangeKeywordModeAction::getName(bool to_undo) const {
|
||||
return _("Keyword mode");
|
||||
return _("Keyword mode");
|
||||
}
|
||||
|
||||
void ChangeKeywordModeAction::perform(bool to_undo) {
|
||||
swap(keyword.mode, mode);
|
||||
swap(keyword.mode, mode);
|
||||
}
|
||||
|
||||
+40
-40
@@ -29,23 +29,23 @@ DECLARE_TYPEOF_COLLECTION(GenericAddAction<KeywordP>::Step);
|
||||
/// An Action the changes the keyword list of a set
|
||||
class KeywordListAction : public Action {
|
||||
public:
|
||||
inline KeywordListAction(Set& set) : set(set) {}
|
||||
|
||||
inline KeywordListAction(Set& set) : set(set) {}
|
||||
|
||||
protected:
|
||||
Set& set; // the set owns this action, so the set will not be destroyed before this
|
||||
// therefore we don't need a smart pointer
|
||||
Set& set; // the set owns this action, so the set will not be destroyed before this
|
||||
// therefore we don't need a smart pointer
|
||||
};
|
||||
|
||||
/// Adding or removing a keyword from a set
|
||||
class AddKeywordAction : public KeywordListAction {
|
||||
public:
|
||||
AddKeywordAction(Set& set);
|
||||
AddKeywordAction(AddingOrRemoving, Set& set, const KeywordP& keyword);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
const GenericAddAction<KeywordP> action;
|
||||
AddKeywordAction(Set& set);
|
||||
AddKeywordAction(AddingOrRemoving, Set& set, const KeywordP& keyword);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
const GenericAddAction<KeywordP> action;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Changing keywords
|
||||
@@ -59,46 +59,46 @@ class AddKeywordAction : public KeywordListAction {
|
||||
*/
|
||||
class KeywordTextValue : public FakeTextValue {
|
||||
public:
|
||||
KeywordTextValue(const TextFieldP& field, Keyword* keyword, String* underlying, bool editable, bool untagged = false)
|
||||
: FakeTextValue(field, underlying, editable, untagged)
|
||||
, keyword(*keyword)
|
||||
{}
|
||||
|
||||
Keyword& keyword; ///< The keyword that is being edited
|
||||
KeywordTextValue(const TextFieldP& field, Keyword* keyword, String* underlying, bool editable, bool untagged = false)
|
||||
: FakeTextValue(field, underlying, editable, untagged)
|
||||
, keyword(*keyword)
|
||||
{}
|
||||
|
||||
Keyword& keyword; ///< The keyword that is being edited
|
||||
};
|
||||
|
||||
/// A FakeTextValue that is used to edit reminder text scripts
|
||||
class KeywordReminderTextValue : public KeywordTextValue {
|
||||
public:
|
||||
KeywordReminderTextValue(Set& set, const TextFieldP& field, Keyword* keyword, bool editable);
|
||||
|
||||
String errors; ///< Errors in the script
|
||||
Set& set; ///< Set this keyword is in (for script checking)
|
||||
Keyword& keyword; ///< The keyword we are the reminder text of
|
||||
|
||||
/// Try to compile the script
|
||||
virtual void store();
|
||||
/// Add some tags, so the script looks nice
|
||||
virtual void retrieve();
|
||||
|
||||
/// Syntax highlight, and store in value
|
||||
void highlight(const String& code, const vector<ScriptParseError>& errors);
|
||||
|
||||
/// Check the script for errors
|
||||
bool checkScript(const ScriptP& script);
|
||||
KeywordReminderTextValue(Set& set, const TextFieldP& field, Keyword* keyword, bool editable);
|
||||
|
||||
String errors; ///< Errors in the script
|
||||
Set& set; ///< Set this keyword is in (for script checking)
|
||||
Keyword& keyword; ///< The keyword we are the reminder text of
|
||||
|
||||
/// Try to compile the script
|
||||
virtual void store();
|
||||
/// Add some tags, so the script looks nice
|
||||
virtual void retrieve();
|
||||
|
||||
/// Syntax highlight, and store in value
|
||||
void highlight(const String& code, const vector<ScriptParseError>& errors);
|
||||
|
||||
/// Check the script for errors
|
||||
bool checkScript(const ScriptP& script);
|
||||
};
|
||||
|
||||
/// Changing the mode of a keyword
|
||||
class ChangeKeywordModeAction : public Action {
|
||||
public:
|
||||
ChangeKeywordModeAction(Keyword& keyword, const String& new_mode);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
ChangeKeywordModeAction(Keyword& keyword, const String& new_mode);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
//private:
|
||||
Keyword& keyword;
|
||||
String mode;
|
||||
Keyword& keyword;
|
||||
String mode;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
+67
-67
@@ -22,147 +22,147 @@ DECLARE_TYPEOF_COLLECTION(int);
|
||||
// ----------------------------------------------------------------------------- : Add card
|
||||
|
||||
AddCardAction::AddCardAction(Set& set)
|
||||
: CardListAction(set)
|
||||
, action(ADD, intrusive(new Card(*set.game)), set.cards)
|
||||
: CardListAction(set)
|
||||
, action(ADD, intrusive(new Card(*set.game)), set.cards)
|
||||
{}
|
||||
|
||||
AddCardAction::AddCardAction(AddingOrRemoving ar, Set& set, const CardP& card)
|
||||
: CardListAction(set)
|
||||
, action(ar, card, set.cards)
|
||||
: CardListAction(set)
|
||||
, action(ar, card, set.cards)
|
||||
{}
|
||||
|
||||
AddCardAction::AddCardAction(AddingOrRemoving ar, Set& set, const vector<CardP>& cards)
|
||||
: CardListAction(set)
|
||||
, action(ar, cards, set.cards)
|
||||
: CardListAction(set)
|
||||
, action(ar, cards, set.cards)
|
||||
{}
|
||||
|
||||
String AddCardAction::getName(bool to_undo) const {
|
||||
return action.getName();
|
||||
return action.getName();
|
||||
}
|
||||
|
||||
void AddCardAction::perform(bool to_undo) {
|
||||
action.perform(set.cards, to_undo);
|
||||
action.perform(set.cards, to_undo);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------- : Reorder cards
|
||||
|
||||
ReorderCardsAction::ReorderCardsAction(Set& set, size_t card_id1, size_t card_id2)
|
||||
: CardListAction(set), card_id1(card_id1), card_id2(card_id2)
|
||||
: CardListAction(set), card_id1(card_id1), card_id2(card_id2)
|
||||
{}
|
||||
|
||||
String ReorderCardsAction::getName(bool to_undo) const {
|
||||
return _("Reorder cards");
|
||||
return _("Reorder cards");
|
||||
}
|
||||
|
||||
void ReorderCardsAction::perform(bool to_undo) {
|
||||
#ifdef _DEBUG
|
||||
assert(card_id1 < set.cards.size());
|
||||
assert(card_id2 < set.cards.size());
|
||||
#endif
|
||||
if (card_id1 >= set.cards.size() || card_id2 < set.cards.size()) {
|
||||
// TODO : Too lazy to fix this right now.
|
||||
return;
|
||||
}
|
||||
swap(set.cards[card_id1], set.cards[card_id2]);
|
||||
#ifdef _DEBUG
|
||||
assert(card_id1 < set.cards.size());
|
||||
assert(card_id2 < set.cards.size());
|
||||
#endif
|
||||
if (card_id1 >= set.cards.size() || card_id2 < set.cards.size()) {
|
||||
// TODO : Too lazy to fix this right now.
|
||||
return;
|
||||
}
|
||||
swap(set.cards[card_id1], set.cards[card_id2]);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Change stylesheet
|
||||
|
||||
String DisplayChangeAction::getName(bool to_undo) const {
|
||||
assert(false);
|
||||
return _("");
|
||||
assert(false);
|
||||
return _("");
|
||||
}
|
||||
void DisplayChangeAction::perform(bool to_undo) {
|
||||
assert(false);
|
||||
assert(false);
|
||||
}
|
||||
|
||||
|
||||
ChangeCardStyleAction::ChangeCardStyleAction(const CardP& card, const StyleSheetP& stylesheet)
|
||||
: card(card), stylesheet(stylesheet), has_styling(false) // styling_data(empty)
|
||||
: card(card), stylesheet(stylesheet), has_styling(false) // styling_data(empty)
|
||||
{}
|
||||
String ChangeCardStyleAction::getName(bool to_undo) const {
|
||||
return _("Change style");
|
||||
return _("Change style");
|
||||
}
|
||||
void ChangeCardStyleAction::perform(bool to_undo) {
|
||||
swap(card->stylesheet, stylesheet);
|
||||
swap(card->has_styling, has_styling);
|
||||
swap(card->styling_data, styling_data);
|
||||
swap(card->stylesheet, stylesheet);
|
||||
swap(card->has_styling, has_styling);
|
||||
swap(card->styling_data, styling_data);
|
||||
}
|
||||
|
||||
|
||||
ChangeSetStyleAction::ChangeSetStyleAction(Set& set, const CardP& card)
|
||||
: set(set), card(card)
|
||||
: set(set), card(card)
|
||||
{}
|
||||
String ChangeSetStyleAction::getName(bool to_undo) const {
|
||||
return _("Change style (all cards)");
|
||||
return _("Change style (all cards)");
|
||||
}
|
||||
void ChangeSetStyleAction::perform(bool to_undo) {
|
||||
if (!to_undo) {
|
||||
// backup has_styling
|
||||
has_styling.clear();
|
||||
FOR_EACH(card, set.cards) {
|
||||
has_styling.push_back(card->has_styling);
|
||||
if (!card->stylesheet) {
|
||||
card->has_styling = false; // this card has custom style options for the default stylesheet
|
||||
}
|
||||
}
|
||||
stylesheet = set.stylesheet;
|
||||
set.stylesheet = card->stylesheet;
|
||||
card->stylesheet = StyleSheetP();
|
||||
} else {
|
||||
card->stylesheet = set.stylesheet;
|
||||
set.stylesheet = stylesheet;
|
||||
// restore has_styling
|
||||
FOR_EACH_2(card, set.cards, has, has_styling) {
|
||||
card->has_styling = has;
|
||||
}
|
||||
}
|
||||
if (!to_undo) {
|
||||
// backup has_styling
|
||||
has_styling.clear();
|
||||
FOR_EACH(card, set.cards) {
|
||||
has_styling.push_back(card->has_styling);
|
||||
if (!card->stylesheet) {
|
||||
card->has_styling = false; // this card has custom style options for the default stylesheet
|
||||
}
|
||||
}
|
||||
stylesheet = set.stylesheet;
|
||||
set.stylesheet = card->stylesheet;
|
||||
card->stylesheet = StyleSheetP();
|
||||
} else {
|
||||
card->stylesheet = set.stylesheet;
|
||||
set.stylesheet = stylesheet;
|
||||
// restore has_styling
|
||||
FOR_EACH_2(card, set.cards, has, has_styling) {
|
||||
card->has_styling = has;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ChangeCardHasStylingAction::ChangeCardHasStylingAction(Set& set, const CardP& card)
|
||||
: set(set), card(card)
|
||||
: set(set), card(card)
|
||||
{
|
||||
if (!card->has_styling) {
|
||||
// copy of the set's styling data
|
||||
styling_data.cloneFrom( set.stylingDataFor(set.stylesheetFor(card)) );
|
||||
} else {
|
||||
// the new styling data is empty
|
||||
}
|
||||
if (!card->has_styling) {
|
||||
// copy of the set's styling data
|
||||
styling_data.cloneFrom( set.stylingDataFor(set.stylesheetFor(card)) );
|
||||
} else {
|
||||
// the new styling data is empty
|
||||
}
|
||||
}
|
||||
String ChangeCardHasStylingAction::getName(bool to_undo) const {
|
||||
return _("Use custom style");
|
||||
return _("Use custom style");
|
||||
}
|
||||
void ChangeCardHasStylingAction::perform(bool to_undo) {
|
||||
card->has_styling = !card->has_styling;
|
||||
swap(card->styling_data, styling_data);
|
||||
card->has_styling = !card->has_styling;
|
||||
swap(card->styling_data, styling_data);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Pack types
|
||||
|
||||
AddPackAction::AddPackAction(AddingOrRemoving ar, Set& set, const PackTypeP& pack)
|
||||
: PackTypesAction(set)
|
||||
, action(ar, pack, set.pack_types)
|
||||
: PackTypesAction(set)
|
||||
, action(ar, pack, set.pack_types)
|
||||
{}
|
||||
|
||||
String AddPackAction::getName(bool to_undo) const {
|
||||
return action.getName();
|
||||
return action.getName();
|
||||
}
|
||||
|
||||
void AddPackAction::perform(bool to_undo) {
|
||||
action.perform(set.pack_types, to_undo);
|
||||
action.perform(set.pack_types, to_undo);
|
||||
}
|
||||
|
||||
|
||||
ChangePackAction::ChangePackAction(Set& set, size_t pos, const PackTypeP& pack)
|
||||
: PackTypesAction(set)
|
||||
, pack(pack), pos(pos)
|
||||
: PackTypesAction(set)
|
||||
, pack(pack), pos(pos)
|
||||
{}
|
||||
|
||||
String ChangePackAction::getName(bool to_undo) const {
|
||||
return _ACTION_1_("change",type_name(pack));
|
||||
return _ACTION_1_("change",type_name(pack));
|
||||
}
|
||||
|
||||
void ChangePackAction::perform(bool to_undo) {
|
||||
swap(set.pack_types.at(pos), pack);
|
||||
swap(set.pack_types.at(pos), pack);
|
||||
}
|
||||
|
||||
+66
-66
@@ -32,25 +32,25 @@ DECLARE_TYPEOF_COLLECTION(GenericAddAction<PackTypeP>::Step);
|
||||
/// An Action the changes the card list of a set
|
||||
class CardListAction : public Action {
|
||||
public:
|
||||
inline CardListAction(Set& set) : set(set) {}
|
||||
|
||||
inline CardListAction(Set& set) : set(set) {}
|
||||
|
||||
protected:
|
||||
Set& set; // the set owns this action, so the set will not be destroyed before this
|
||||
// therefore we don't need a smart pointer
|
||||
Set& set; // the set owns this action, so the set will not be destroyed before this
|
||||
// therefore we don't need a smart pointer
|
||||
};
|
||||
|
||||
/// Adding a new card to a set
|
||||
class AddCardAction : public CardListAction {
|
||||
public:
|
||||
/// Add a newly allocated card
|
||||
AddCardAction(Set& set);
|
||||
AddCardAction(AddingOrRemoving, Set& set, const CardP& card);
|
||||
AddCardAction(AddingOrRemoving, Set& set, const vector<CardP>& cards);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
const GenericAddAction<CardP> action;
|
||||
/// Add a newly allocated card
|
||||
AddCardAction(Set& set);
|
||||
AddCardAction(AddingOrRemoving, Set& set, const CardP& card);
|
||||
AddCardAction(AddingOrRemoving, Set& set, const vector<CardP>& cards);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
const GenericAddAction<CardP> action;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Reorder cards
|
||||
@@ -58,13 +58,13 @@ class AddCardAction : public CardListAction {
|
||||
/// Change the position of a card in the card list by swapping two cards
|
||||
class ReorderCardsAction : public CardListAction {
|
||||
public:
|
||||
ReorderCardsAction(Set& set, size_t card_id1, size_t card_id2);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
ReorderCardsAction(Set& set, size_t card_id1, size_t card_id2);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
//private:
|
||||
const size_t card_id1, card_id2; ///< Positions of the two cards to swap
|
||||
const size_t card_id1, card_id2; ///< Positions of the two cards to swap
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Change stylesheet
|
||||
@@ -72,53 +72,53 @@ class ReorderCardsAction : public CardListAction {
|
||||
/// An action that affects the rendering/display/look of a set or cards in the set
|
||||
class DisplayChangeAction : public Action {
|
||||
public:
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
};
|
||||
|
||||
/// Changing the style of a a card
|
||||
class ChangeCardStyleAction : public DisplayChangeAction {
|
||||
public:
|
||||
ChangeCardStyleAction(const CardP& card, const StyleSheetP& stylesheet);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
ChangeCardStyleAction(const CardP& card, const StyleSheetP& stylesheet);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
//private:
|
||||
CardP card; ///< The affected card
|
||||
StyleSheetP stylesheet; ///< Its old stylesheet
|
||||
bool has_styling; ///< Its old has_styling
|
||||
IndexMap<FieldP,ValueP> styling_data; ///< Its old styling data
|
||||
CardP card; ///< The affected card
|
||||
StyleSheetP stylesheet; ///< Its old stylesheet
|
||||
bool has_styling; ///< Its old has_styling
|
||||
IndexMap<FieldP,ValueP> styling_data; ///< Its old styling data
|
||||
};
|
||||
|
||||
/// Changing the style of a set to that of a card
|
||||
class ChangeSetStyleAction : public DisplayChangeAction {
|
||||
public:
|
||||
ChangeSetStyleAction(Set& set, const CardP& card);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
ChangeSetStyleAction(Set& set, const CardP& card);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
private:
|
||||
Set& set; ///< The affected set
|
||||
CardP card; ///< The card whos stylesheet is copied to the set
|
||||
StyleSheetP stylesheet; ///< The old stylesheet of the set
|
||||
vector<int> has_styling; ///< The old has_styling values of all cards (vector<bool> is evil)
|
||||
Set& set; ///< The affected set
|
||||
CardP card; ///< The card whos stylesheet is copied to the set
|
||||
StyleSheetP stylesheet; ///< The old stylesheet of the set
|
||||
vector<int> has_styling; ///< The old has_styling values of all cards (vector<bool> is evil)
|
||||
};
|
||||
|
||||
/// Changing the styling of a card to become custom/non-custom
|
||||
/** i.e. toggle card->has_styling */
|
||||
class ChangeCardHasStylingAction : public DisplayChangeAction {
|
||||
public:
|
||||
ChangeCardHasStylingAction(Set& set, const CardP& card);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
ChangeCardHasStylingAction(Set& set, const CardP& card);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
//private:
|
||||
Set& set; ///< The set to copy styling from
|
||||
CardP card; ///< The affected card
|
||||
IndexMap<FieldP,ValueP> styling_data; ///< The old styling of the card
|
||||
Set& set; ///< The set to copy styling from
|
||||
CardP card; ///< The affected card
|
||||
IndexMap<FieldP,ValueP> styling_data; ///< The old styling of the card
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Pack types
|
||||
@@ -126,37 +126,37 @@ class ChangeCardHasStylingAction : public DisplayChangeAction {
|
||||
/// An Action the changes the pack types of a set
|
||||
class PackTypesAction : public Action {
|
||||
public:
|
||||
inline PackTypesAction(Set& set) : set(set) {}
|
||||
|
||||
inline PackTypesAction(Set& set) : set(set) {}
|
||||
|
||||
protected:
|
||||
Set& set; // the set owns this action, so the set will not be destroyed before this
|
||||
// therefore we don't need a smart pointer
|
||||
Set& set; // the set owns this action, so the set will not be destroyed before this
|
||||
// therefore we don't need a smart pointer
|
||||
};
|
||||
|
||||
/// Adding/removing a pack from a Set
|
||||
class AddPackAction : public PackTypesAction {
|
||||
public:
|
||||
/// Add a newly allocated card
|
||||
AddPackAction(AddingOrRemoving, Set& set, const PackTypeP& pack);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
const GenericAddAction<PackTypeP> action;
|
||||
/// Add a newly allocated card
|
||||
AddPackAction(AddingOrRemoving, Set& set, const PackTypeP& pack);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
const GenericAddAction<PackTypeP> action;
|
||||
};
|
||||
|
||||
/// Updating a pack in a Set
|
||||
class ChangePackAction : public PackTypesAction {
|
||||
public:
|
||||
/// Add a newly allocated card
|
||||
ChangePackAction(Set& set, size_t pos, const PackTypeP& new_pack);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
/// Add a newly allocated card
|
||||
ChangePackAction(Set& set, size_t pos, const PackTypeP& new_pack);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
private:
|
||||
PackTypeP pack;
|
||||
size_t pos;
|
||||
PackTypeP pack;
|
||||
size_t pos;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
+334
-334
@@ -20,173 +20,173 @@ DECLARE_TYPEOF_COLLECTION(ControlPointP);
|
||||
// ----------------------------------------------------------------------------- : Utility
|
||||
|
||||
String action_name_for(const set<SymbolPartP>& parts, const String& action) {
|
||||
return format_string(action, parts.size() == 1 ? (*parts.begin())->name : _TYPE_("shapes"));
|
||||
return format_string(action, parts.size() == 1 ? (*parts.begin())->name : _TYPE_("shapes"));
|
||||
}
|
||||
|
||||
SymbolPartsAction::SymbolPartsAction(const set<SymbolPartP>& parts)
|
||||
: parts(parts)
|
||||
: parts(parts)
|
||||
{}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Moving symbol parts
|
||||
|
||||
SymbolPartMoveAction::SymbolPartMoveAction(const set<SymbolPartP>& parts, const Vector2D& delta)
|
||||
: SymbolPartsAction(parts)
|
||||
, delta(delta), moved(-delta)
|
||||
, constrain(false)
|
||||
, snap(0)
|
||||
: SymbolPartsAction(parts)
|
||||
, delta(delta), moved(-delta)
|
||||
, constrain(false)
|
||||
, snap(0)
|
||||
{
|
||||
// Determine min/max_pos
|
||||
FOR_EACH(p, parts) {
|
||||
bounds.update(p->bounds);
|
||||
}
|
||||
// Determine min/max_pos
|
||||
FOR_EACH(p, parts) {
|
||||
bounds.update(p->bounds);
|
||||
}
|
||||
}
|
||||
|
||||
String SymbolPartMoveAction::getName(bool to_undo) const {
|
||||
return action_name_for(parts, _ACTION_("move"));
|
||||
return action_name_for(parts, _ACTION_("move"));
|
||||
}
|
||||
|
||||
void SymbolPartMoveAction::perform(bool to_undo) {
|
||||
// move the points back
|
||||
FOR_EACH(p, parts) {
|
||||
movePart(*p);
|
||||
}
|
||||
moved = -moved;
|
||||
// move the points back
|
||||
FOR_EACH(p, parts) {
|
||||
movePart(*p);
|
||||
}
|
||||
moved = -moved;
|
||||
}
|
||||
void SymbolPartMoveAction::movePart(SymbolPart& part) {
|
||||
part.bounds.min -= moved;
|
||||
part.bounds.max -= moved;
|
||||
if (SymbolShape* s = part.isSymbolShape()) {
|
||||
FOR_EACH(pnt, s->points) {
|
||||
pnt->pos -= moved;
|
||||
}
|
||||
} else if (SymbolSymmetry* s = part.isSymbolSymmetry()) {
|
||||
s->center -= moved;
|
||||
}
|
||||
if (SymbolGroup* g = part.isSymbolGroup()) {
|
||||
FOR_EACH(p, g->parts) {
|
||||
movePart(*p);
|
||||
}
|
||||
}
|
||||
part.bounds.min -= moved;
|
||||
part.bounds.max -= moved;
|
||||
if (SymbolShape* s = part.isSymbolShape()) {
|
||||
FOR_EACH(pnt, s->points) {
|
||||
pnt->pos -= moved;
|
||||
}
|
||||
} else if (SymbolSymmetry* s = part.isSymbolSymmetry()) {
|
||||
s->center -= moved;
|
||||
}
|
||||
if (SymbolGroup* g = part.isSymbolGroup()) {
|
||||
FOR_EACH(p, g->parts) {
|
||||
movePart(*p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SymbolPartMoveAction::move(const Vector2D& deltaDelta) {
|
||||
delta += deltaDelta;
|
||||
// Determine actual delta, possibly constrained and snapped
|
||||
Vector2D d = constrain_snap_vector_offset(bounds.min, bounds.max, delta, constrain, snap);
|
||||
Vector2D dd = d - moved; // move this much more
|
||||
// Move each point by d
|
||||
moved = -dd;
|
||||
perform(false); // (ab)use perform to move by +dd
|
||||
moved = d;
|
||||
delta += deltaDelta;
|
||||
// Determine actual delta, possibly constrained and snapped
|
||||
Vector2D d = constrain_snap_vector_offset(bounds.min, bounds.max, delta, constrain, snap);
|
||||
Vector2D dd = d - moved; // move this much more
|
||||
// Move each point by d
|
||||
moved = -dd;
|
||||
perform(false); // (ab)use perform to move by +dd
|
||||
moved = d;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Rotating symbol parts
|
||||
|
||||
SymbolPartMatrixAction::SymbolPartMatrixAction(const set<SymbolPartP>& parts, const Vector2D& center)
|
||||
: SymbolPartsAction(parts)
|
||||
, center(center)
|
||||
: SymbolPartsAction(parts)
|
||||
, center(center)
|
||||
{}
|
||||
|
||||
void SymbolPartMatrixAction::transform(const Matrix2D& m) {
|
||||
// Transform each part
|
||||
FOR_EACH(p, parts) {
|
||||
transform(*p, m);
|
||||
p->updateBounds();
|
||||
}
|
||||
// Transform each part
|
||||
FOR_EACH(p, parts) {
|
||||
transform(*p, m);
|
||||
p->updateBounds();
|
||||
}
|
||||
}
|
||||
void SymbolPartMatrixAction::transform(SymbolPart& part, const Matrix2D& m) {
|
||||
if (SymbolShape* s = part.isSymbolShape()) {
|
||||
FOR_EACH(pnt, s->points) {
|
||||
pnt->pos = ((pnt->pos - center) * m) + center;
|
||||
pnt->delta_before = pnt->delta_before * m;
|
||||
pnt->delta_after = pnt->delta_after * m;
|
||||
}
|
||||
} else if (SymbolSymmetry* s = part.isSymbolSymmetry()) {
|
||||
s->center = (s->center - center) * m + center;
|
||||
s->handle = s->handle * m;
|
||||
}
|
||||
if (SymbolGroup* g = part.isSymbolGroup()) {
|
||||
FOR_EACH(p, g->parts) {
|
||||
transform(*p, m);
|
||||
}
|
||||
}
|
||||
if (SymbolShape* s = part.isSymbolShape()) {
|
||||
FOR_EACH(pnt, s->points) {
|
||||
pnt->pos = ((pnt->pos - center) * m) + center;
|
||||
pnt->delta_before = pnt->delta_before * m;
|
||||
pnt->delta_after = pnt->delta_after * m;
|
||||
}
|
||||
} else if (SymbolSymmetry* s = part.isSymbolSymmetry()) {
|
||||
s->center = (s->center - center) * m + center;
|
||||
s->handle = s->handle * m;
|
||||
}
|
||||
if (SymbolGroup* g = part.isSymbolGroup()) {
|
||||
FOR_EACH(p, g->parts) {
|
||||
transform(*p, m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SymbolPartRotateAction::SymbolPartRotateAction(const set<SymbolPartP>& parts, const Vector2D& center)
|
||||
: SymbolPartMatrixAction(parts, center)
|
||||
, angle(0)
|
||||
, constrain(false)
|
||||
: SymbolPartMatrixAction(parts, center)
|
||||
, angle(0)
|
||||
, constrain(false)
|
||||
{}
|
||||
|
||||
String SymbolPartRotateAction::getName(bool to_undo) const {
|
||||
return action_name_for(parts, _ACTION_("rotate"));
|
||||
return action_name_for(parts, _ACTION_("rotate"));
|
||||
}
|
||||
|
||||
void SymbolPartRotateAction::perform(bool to_undo) {
|
||||
// move the points back
|
||||
rotateBy(-angle);
|
||||
angle = -angle;
|
||||
// move the points back
|
||||
rotateBy(-angle);
|
||||
angle = -angle;
|
||||
}
|
||||
|
||||
void SymbolPartRotateAction::rotateTo(Radians newAngle) {
|
||||
Radians oldAngle = angle;
|
||||
angle = newAngle;
|
||||
// constrain?
|
||||
if (constrain) {
|
||||
// multiples of 2pi/24 i.e. 24 stops
|
||||
double mult = (2 * M_PI) / 24;
|
||||
angle = floor(angle / mult + 0.5) * mult;
|
||||
}
|
||||
if (oldAngle != angle) rotateBy(angle - oldAngle);
|
||||
Radians oldAngle = angle;
|
||||
angle = newAngle;
|
||||
// constrain?
|
||||
if (constrain) {
|
||||
// multiples of 2pi/24 i.e. 24 stops
|
||||
double mult = (2 * M_PI) / 24;
|
||||
angle = floor(angle / mult + 0.5) * mult;
|
||||
}
|
||||
if (oldAngle != angle) rotateBy(angle - oldAngle);
|
||||
}
|
||||
|
||||
void SymbolPartRotateAction::rotateBy(Radians deltaAngle) {
|
||||
// Rotation 'matrix'
|
||||
transform(
|
||||
Matrix2D(cos(deltaAngle), -sin(deltaAngle)
|
||||
,sin(deltaAngle), cos(deltaAngle))
|
||||
);
|
||||
// Rotation 'matrix'
|
||||
transform(
|
||||
Matrix2D(cos(deltaAngle), -sin(deltaAngle)
|
||||
,sin(deltaAngle), cos(deltaAngle))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------- : Shearing symbol parts
|
||||
|
||||
SymbolPartShearAction::SymbolPartShearAction(const set<SymbolPartP>& parts, const Vector2D& center)
|
||||
: SymbolPartMatrixAction(parts, center)
|
||||
// , constrain(false)
|
||||
, snap(0)
|
||||
: SymbolPartMatrixAction(parts, center)
|
||||
// , constrain(false)
|
||||
, snap(0)
|
||||
{}
|
||||
|
||||
String SymbolPartShearAction::getName(bool to_undo) const {
|
||||
return action_name_for(parts, _ACTION_("shear"));
|
||||
return action_name_for(parts, _ACTION_("shear"));
|
||||
}
|
||||
|
||||
void SymbolPartShearAction::perform(bool to_undo) {
|
||||
// move the points back
|
||||
// the vector shear = (x,y) is used as:
|
||||
// <1 x>
|
||||
// <y 1>
|
||||
// inverse is:
|
||||
// <1 -x> /
|
||||
// <-y 1> / (1 - xy)
|
||||
// we have: xy = 0 => (1 - xy) = 1
|
||||
shearBy(-moved);
|
||||
// move the points back
|
||||
// the vector shear = (x,y) is used as:
|
||||
// <1 x>
|
||||
// <y 1>
|
||||
// inverse is:
|
||||
// <1 -x> /
|
||||
// <-y 1> / (1 - xy)
|
||||
// we have: xy = 0 => (1 - xy) = 1
|
||||
shearBy(-moved);
|
||||
}
|
||||
|
||||
void SymbolPartShearAction::move(const Vector2D& deltaShear) {
|
||||
shear += deltaShear;
|
||||
Vector2D d = snap_vector(shear - moved, snap);
|
||||
shearBy(d);
|
||||
moved += d;
|
||||
shear += deltaShear;
|
||||
Vector2D d = snap_vector(shear - moved, snap);
|
||||
shearBy(d);
|
||||
moved += d;
|
||||
}
|
||||
|
||||
void SymbolPartShearAction::shearBy(const Vector2D& shear) {
|
||||
// Shear 'matrix'
|
||||
transform(
|
||||
Matrix2D(1, shear.x
|
||||
,shear.y, 1)
|
||||
);
|
||||
// Shear 'matrix'
|
||||
transform(
|
||||
Matrix2D(1, shear.x
|
||||
,shear.y, 1)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -194,366 +194,366 @@ void SymbolPartShearAction::shearBy(const Vector2D& shear) {
|
||||
|
||||
|
||||
SymbolPartScaleAction::SymbolPartScaleAction(const set<SymbolPartP>& parts, int scaleX, int scaleY)
|
||||
: SymbolPartsAction(parts)
|
||||
, scaleX(scaleX), scaleY(scaleY)
|
||||
, constrain(false)
|
||||
, snap(0)
|
||||
: SymbolPartsAction(parts)
|
||||
, scaleX(scaleX), scaleY(scaleY)
|
||||
, constrain(false)
|
||||
, snap(0)
|
||||
{
|
||||
// Find min and max coordinates
|
||||
Bounds bounds;
|
||||
FOR_EACH(p, parts) {
|
||||
bounds.update(p->bounds);
|
||||
}
|
||||
// new == old
|
||||
new_min = new_real_min = old_min = bounds.min;
|
||||
new_size = new_real_size = old_size = bounds.max - bounds.min;
|
||||
// Find min and max coordinates
|
||||
Bounds bounds;
|
||||
FOR_EACH(p, parts) {
|
||||
bounds.update(p->bounds);
|
||||
}
|
||||
// new == old
|
||||
new_min = new_real_min = old_min = bounds.min;
|
||||
new_size = new_real_size = old_size = bounds.max - bounds.min;
|
||||
}
|
||||
|
||||
String SymbolPartScaleAction::getName(bool to_undo) const {
|
||||
return action_name_for(parts, _ACTION_("scale"));
|
||||
return action_name_for(parts, _ACTION_("scale"));
|
||||
}
|
||||
|
||||
void SymbolPartScaleAction::perform(bool to_undo) {
|
||||
swap(old_min, new_min);
|
||||
swap(old_size, new_size);
|
||||
transformAll();
|
||||
swap(old_min, new_min);
|
||||
swap(old_size, new_size);
|
||||
transformAll();
|
||||
}
|
||||
|
||||
void SymbolPartScaleAction::move(const Vector2D& delta_min, const Vector2D& delta_max) {
|
||||
new_real_min += delta_min;
|
||||
new_real_size += delta_max - delta_min;
|
||||
update();
|
||||
new_real_min += delta_min;
|
||||
new_real_size += delta_max - delta_min;
|
||||
update();
|
||||
}
|
||||
|
||||
void SymbolPartScaleAction::update() {
|
||||
// Move each point so the range [old_min...old_max] maps to [new_min...new_max]
|
||||
// we have already moved to the current [new_min...new_max]
|
||||
Vector2D tmp_min = old_min, tmp_size = old_size; // the size before any scaling
|
||||
old_min = new_min; old_size = new_size; // the size before this move
|
||||
// the size after the move
|
||||
new_min = new_real_min; new_size = new_real_size;
|
||||
if (constrain && scaleX != 0 && scaleY != 0) {
|
||||
Vector2D scale = new_size.div(tmp_size);
|
||||
scale = constrain_vector(scale, true, true);
|
||||
new_size = tmp_size.mul(scale);
|
||||
new_min += (new_real_size - new_size).mul(Vector2D(scaleX == -1 ? 1 : 0, scaleY == -1 ? 1 : 0));
|
||||
// TODO : snapping
|
||||
} else if (snap >= 0) {
|
||||
if (scaleX + scaleY < 0) {
|
||||
new_min = snap_vector(new_min, snap);
|
||||
new_size += new_real_min - new_min;
|
||||
} else {
|
||||
Vector2D new_max = snap_vector(new_min + new_size, snap);
|
||||
new_size = new_max - new_min;
|
||||
}
|
||||
}
|
||||
// now move all points
|
||||
transformAll();
|
||||
// restore old_min/size
|
||||
old_min = tmp_min; old_size = tmp_size;
|
||||
// Move each point so the range [old_min...old_max] maps to [new_min...new_max]
|
||||
// we have already moved to the current [new_min...new_max]
|
||||
Vector2D tmp_min = old_min, tmp_size = old_size; // the size before any scaling
|
||||
old_min = new_min; old_size = new_size; // the size before this move
|
||||
// the size after the move
|
||||
new_min = new_real_min; new_size = new_real_size;
|
||||
if (constrain && scaleX != 0 && scaleY != 0) {
|
||||
Vector2D scale = new_size.div(tmp_size);
|
||||
scale = constrain_vector(scale, true, true);
|
||||
new_size = tmp_size.mul(scale);
|
||||
new_min += (new_real_size - new_size).mul(Vector2D(scaleX == -1 ? 1 : 0, scaleY == -1 ? 1 : 0));
|
||||
// TODO : snapping
|
||||
} else if (snap >= 0) {
|
||||
if (scaleX + scaleY < 0) {
|
||||
new_min = snap_vector(new_min, snap);
|
||||
new_size += new_real_min - new_min;
|
||||
} else {
|
||||
Vector2D new_max = snap_vector(new_min + new_size, snap);
|
||||
new_size = new_max - new_min;
|
||||
}
|
||||
}
|
||||
// now move all points
|
||||
transformAll();
|
||||
// restore old_min/size
|
||||
old_min = tmp_min; old_size = tmp_size;
|
||||
}
|
||||
|
||||
void SymbolPartScaleAction::transformAll() {
|
||||
FOR_EACH(p, parts) {
|
||||
transformPart(*p);
|
||||
}
|
||||
FOR_EACH(p, parts) {
|
||||
transformPart(*p);
|
||||
}
|
||||
}
|
||||
void SymbolPartScaleAction::transformPart(SymbolPart& part) {
|
||||
// update bounds
|
||||
part.bounds.min = transform(part.bounds.min);
|
||||
part.bounds.max = transform(part.bounds.max);
|
||||
// make sure that max >= min
|
||||
if (part.bounds.min.x > part.bounds.max.x) swap(part.bounds.min.x, part.bounds.max.x);
|
||||
if (part.bounds.min.y > part.bounds.max.y) swap(part.bounds.min.y, part.bounds.max.y);
|
||||
if (SymbolShape* s = part.isSymbolShape()) {
|
||||
// scale all points
|
||||
Vector2D scale = new_size.div(old_size);
|
||||
FOR_EACH(pnt, s->points) {
|
||||
pnt->pos = transform(pnt->pos);
|
||||
// also scale handles
|
||||
pnt->delta_before = pnt->delta_before.mul(scale);
|
||||
pnt->delta_after = pnt->delta_after .mul(scale);
|
||||
}
|
||||
} else if (SymbolSymmetry* s = part.isSymbolSymmetry()) {
|
||||
transform(s->center);
|
||||
s->handle.mul(new_size.div(old_size));
|
||||
}
|
||||
if (SymbolGroup* g = part.isSymbolGroup()) {
|
||||
FOR_EACH(p, g->parts) {
|
||||
transformPart(*p);
|
||||
}
|
||||
}
|
||||
// update bounds
|
||||
part.bounds.min = transform(part.bounds.min);
|
||||
part.bounds.max = transform(part.bounds.max);
|
||||
// make sure that max >= min
|
||||
if (part.bounds.min.x > part.bounds.max.x) swap(part.bounds.min.x, part.bounds.max.x);
|
||||
if (part.bounds.min.y > part.bounds.max.y) swap(part.bounds.min.y, part.bounds.max.y);
|
||||
if (SymbolShape* s = part.isSymbolShape()) {
|
||||
// scale all points
|
||||
Vector2D scale = new_size.div(old_size);
|
||||
FOR_EACH(pnt, s->points) {
|
||||
pnt->pos = transform(pnt->pos);
|
||||
// also scale handles
|
||||
pnt->delta_before = pnt->delta_before.mul(scale);
|
||||
pnt->delta_after = pnt->delta_after .mul(scale);
|
||||
}
|
||||
} else if (SymbolSymmetry* s = part.isSymbolSymmetry()) {
|
||||
transform(s->center);
|
||||
s->handle.mul(new_size.div(old_size));
|
||||
}
|
||||
if (SymbolGroup* g = part.isSymbolGroup()) {
|
||||
FOR_EACH(p, g->parts) {
|
||||
transformPart(*p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vector2D SymbolPartScaleAction::transform(const Vector2D& v) {
|
||||
// TODO: prevent div by 0
|
||||
return (v - old_min).div(old_size).mul(new_size) + new_min;
|
||||
// TODO: prevent div by 0
|
||||
return (v - old_min).div(old_size).mul(new_size) + new_min;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Change combine mode
|
||||
|
||||
CombiningModeAction::CombiningModeAction(const set<SymbolPartP>& parts, SymbolShapeCombine mode)
|
||||
: SymbolPartsAction(parts)
|
||||
: SymbolPartsAction(parts)
|
||||
{
|
||||
FOR_EACH(p, parts) {
|
||||
add(p,mode);
|
||||
}
|
||||
FOR_EACH(p, parts) {
|
||||
add(p,mode);
|
||||
}
|
||||
}
|
||||
void CombiningModeAction::add(const SymbolPartP& part, SymbolShapeCombine mode) {
|
||||
if (part->isSymbolShape()) {
|
||||
this->parts.push_back(make_pair(static_pointer_cast<SymbolShape>(part),mode));
|
||||
} else if (SymbolGroup* g = part->isSymbolGroup()) {
|
||||
FOR_EACH(p, g->parts) add(p, mode);
|
||||
}
|
||||
if (part->isSymbolShape()) {
|
||||
this->parts.push_back(make_pair(static_pointer_cast<SymbolShape>(part),mode));
|
||||
} else if (SymbolGroup* g = part->isSymbolGroup()) {
|
||||
FOR_EACH(p, g->parts) add(p, mode);
|
||||
}
|
||||
}
|
||||
|
||||
String CombiningModeAction::getName(bool to_undo) const {
|
||||
return _ACTION_("change combine mode");
|
||||
return _ACTION_("change combine mode");
|
||||
}
|
||||
|
||||
void CombiningModeAction::perform(bool to_undo) {
|
||||
FOR_EACH(pm, parts) {
|
||||
swap(pm.first->combine, pm.second);
|
||||
}
|
||||
FOR_EACH(pm, parts) {
|
||||
swap(pm.first->combine, pm.second);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Change name
|
||||
|
||||
SymbolPartNameAction::SymbolPartNameAction(const SymbolPartP& part, const String& name, size_t old_cursor, size_t new_cursor)
|
||||
: part(part), part_name(name)
|
||||
, old_cursor(new_cursor), new_cursor(old_cursor) // will be swapped
|
||||
: part(part), part_name(name)
|
||||
, old_cursor(new_cursor), new_cursor(old_cursor) // will be swapped
|
||||
{}
|
||||
|
||||
String SymbolPartNameAction::getName(bool to_undo) const {
|
||||
return _ACTION_("change shape name");
|
||||
return _ACTION_("change shape name");
|
||||
}
|
||||
bool SymbolPartNameAction::merge(const Action& action) {
|
||||
TYPE_CASE(action, SymbolPartNameAction) {
|
||||
if (action.part == part) {
|
||||
// adjacent actions on the same part, discard the other one,
|
||||
// because it only keeps an intermediate value
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
TYPE_CASE(action, SymbolPartNameAction) {
|
||||
if (action.part == part) {
|
||||
// adjacent actions on the same part, discard the other one,
|
||||
// because it only keeps an intermediate value
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SymbolPartNameAction::perform(bool to_undo) {
|
||||
swap(part->name, part_name);
|
||||
swap(old_cursor, new_cursor);
|
||||
swap(part->name, part_name);
|
||||
swap(old_cursor, new_cursor);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Add symbol part
|
||||
|
||||
AddSymbolPartAction::AddSymbolPartAction(Symbol& symbol, const SymbolPartP& part)
|
||||
: symbol(symbol), part(part)
|
||||
: symbol(symbol), part(part)
|
||||
{}
|
||||
|
||||
String AddSymbolPartAction::getName(bool to_undo) const {
|
||||
return _ACTION_1_("add item", part->name);
|
||||
return _ACTION_1_("add item", part->name);
|
||||
}
|
||||
|
||||
void AddSymbolPartAction::perform(bool to_undo) {
|
||||
if (to_undo) {
|
||||
assert(!symbol.parts.empty());
|
||||
symbol.parts.erase (symbol.parts.begin());
|
||||
} else {
|
||||
symbol.parts.insert(symbol.parts.begin(), part);
|
||||
}
|
||||
if (to_undo) {
|
||||
assert(!symbol.parts.empty());
|
||||
symbol.parts.erase (symbol.parts.begin());
|
||||
} else {
|
||||
symbol.parts.insert(symbol.parts.begin(), part);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Remove symbol part
|
||||
|
||||
RemoveSymbolPartsAction::RemoveSymbolPartsAction(Symbol& symbol, const set<SymbolPartP>& parts)
|
||||
: symbol(symbol)
|
||||
: symbol(symbol)
|
||||
{
|
||||
check(symbol, parts);
|
||||
check(symbol, parts);
|
||||
}
|
||||
|
||||
void RemoveSymbolPartsAction::check(SymbolGroup& group, const set<SymbolPartP>& parts) {
|
||||
size_t index = 0;
|
||||
size_t removed = 0;
|
||||
FOR_EACH(p, group.parts) {
|
||||
if (parts.find(p) != parts.end()) {
|
||||
removals.push_back(Removal(group, index, p)); // remove this part
|
||||
++ removed;
|
||||
} else if (SymbolGroup* g = p->isSymbolGroup()) {
|
||||
check(*g, parts);
|
||||
}
|
||||
++index;
|
||||
}
|
||||
if (!group.isSymbolSymmetry() && &group != &symbol) {
|
||||
// remove empty groups
|
||||
// TODO
|
||||
}
|
||||
size_t index = 0;
|
||||
size_t removed = 0;
|
||||
FOR_EACH(p, group.parts) {
|
||||
if (parts.find(p) != parts.end()) {
|
||||
removals.push_back(Removal(group, index, p)); // remove this part
|
||||
++ removed;
|
||||
} else if (SymbolGroup* g = p->isSymbolGroup()) {
|
||||
check(*g, parts);
|
||||
}
|
||||
++index;
|
||||
}
|
||||
if (!group.isSymbolSymmetry() && &group != &symbol) {
|
||||
// remove empty groups
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
|
||||
String RemoveSymbolPartsAction::getName(bool to_undo) const {
|
||||
return _ACTION_1_("remove item", removals.size() == 1 ? _TYPE_("shape") : _TYPE_("shapes"));
|
||||
return _ACTION_1_("remove item", removals.size() == 1 ? _TYPE_("shape") : _TYPE_("shapes"));
|
||||
}
|
||||
|
||||
void RemoveSymbolPartsAction::perform(bool to_undo) {
|
||||
if (to_undo) {
|
||||
// reinsert the parts
|
||||
// ascending order, this is the reverse of removal
|
||||
FOR_EACH(r, removals) {
|
||||
assert(r.pos <= r.parent->parts.size());
|
||||
r.parent->parts.insert(r.parent->parts.begin() + r.pos, r.removed);
|
||||
}
|
||||
} else {
|
||||
// remove the parts
|
||||
// descending order, because earlier removals shift the rest of the vector
|
||||
FOR_EACH_REVERSE(r, removals) {
|
||||
assert(r.pos < r.parent->parts.size());
|
||||
r.parent->parts.erase(r.parent->parts.begin() + r.pos);
|
||||
}
|
||||
}
|
||||
if (to_undo) {
|
||||
// reinsert the parts
|
||||
// ascending order, this is the reverse of removal
|
||||
FOR_EACH(r, removals) {
|
||||
assert(r.pos <= r.parent->parts.size());
|
||||
r.parent->parts.insert(r.parent->parts.begin() + r.pos, r.removed);
|
||||
}
|
||||
} else {
|
||||
// remove the parts
|
||||
// descending order, because earlier removals shift the rest of the vector
|
||||
FOR_EACH_REVERSE(r, removals) {
|
||||
assert(r.pos < r.parent->parts.size());
|
||||
r.parent->parts.erase(r.parent->parts.begin() + r.pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Duplicate symbol parts
|
||||
|
||||
DuplicateSymbolPartsAction::DuplicateSymbolPartsAction(Symbol& symbol, const set<SymbolPartP>& parts)
|
||||
: symbol(symbol)
|
||||
: symbol(symbol)
|
||||
{
|
||||
UInt index = 0;
|
||||
FOR_EACH(p, symbol.parts) {
|
||||
index += 1;
|
||||
if (parts.find(p) != parts.end()) {
|
||||
// duplicate this part
|
||||
duplications.push_back(make_pair(p->clone(), index));
|
||||
index += 1; // the clone also takes up space on the vector
|
||||
}
|
||||
}
|
||||
UInt index = 0;
|
||||
FOR_EACH(p, symbol.parts) {
|
||||
index += 1;
|
||||
if (parts.find(p) != parts.end()) {
|
||||
// duplicate this part
|
||||
duplications.push_back(make_pair(p->clone(), index));
|
||||
index += 1; // the clone also takes up space on the vector
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String DuplicateSymbolPartsAction::getName(bool to_undo) const {
|
||||
return _ACTION_1_("duplicate", duplications.size() == 1 ? _TYPE_("shape") : _TYPE_("shapes"));
|
||||
return _ACTION_1_("duplicate", duplications.size() == 1 ? _TYPE_("shape") : _TYPE_("shapes"));
|
||||
}
|
||||
|
||||
void DuplicateSymbolPartsAction::perform(bool to_undo) {
|
||||
if (to_undo) {
|
||||
// remove the clones
|
||||
// walk in reverse order, otherwise we will shift the vector
|
||||
FOR_EACH_REVERSE(d, duplications) {
|
||||
assert(d.second < symbol.parts.size());
|
||||
symbol.parts.erase(symbol.parts.begin() + d.second);
|
||||
}
|
||||
} else {
|
||||
// insert the clones
|
||||
FOR_EACH(d, duplications) {
|
||||
assert(d.second <= symbol.parts.size());
|
||||
symbol.parts.insert(symbol.parts.begin() + d.second, d.first);
|
||||
}
|
||||
}
|
||||
if (to_undo) {
|
||||
// remove the clones
|
||||
// walk in reverse order, otherwise we will shift the vector
|
||||
FOR_EACH_REVERSE(d, duplications) {
|
||||
assert(d.second < symbol.parts.size());
|
||||
symbol.parts.erase(symbol.parts.begin() + d.second);
|
||||
}
|
||||
} else {
|
||||
// insert the clones
|
||||
FOR_EACH(d, duplications) {
|
||||
assert(d.second <= symbol.parts.size());
|
||||
symbol.parts.insert(symbol.parts.begin() + d.second, d.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DuplicateSymbolPartsAction::getParts(set<SymbolPartP>& parts) {
|
||||
parts.clear();
|
||||
FOR_EACH(d, duplications) {
|
||||
parts.insert(d.first);
|
||||
}
|
||||
parts.clear();
|
||||
FOR_EACH(d, duplications) {
|
||||
parts.insert(d.first);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Reorder symbol parts
|
||||
|
||||
ReorderSymbolPartsAction::ReorderSymbolPartsAction(SymbolGroup& old_parent, size_t old_position, SymbolGroup& new_parent, size_t new_position)
|
||||
: old_parent(&old_parent), new_parent(&new_parent)
|
||||
, old_position(old_position), new_position(new_position)
|
||||
: old_parent(&old_parent), new_parent(&new_parent)
|
||||
, old_position(old_position), new_position(new_position)
|
||||
{}
|
||||
|
||||
String ReorderSymbolPartsAction::getName(bool to_undo) const {
|
||||
return _ACTION_("reorder parts");
|
||||
return _ACTION_("reorder parts");
|
||||
}
|
||||
|
||||
void ReorderSymbolPartsAction::perform(bool to_undo) {
|
||||
// remove from old
|
||||
assert(old_position < old_parent->parts.size());
|
||||
SymbolPartP part = old_parent->parts.at(old_position);
|
||||
old_parent->parts.erase( old_parent->parts.begin() + old_position);
|
||||
// add to new
|
||||
assert(new_position <= new_parent->parts.size());
|
||||
new_parent->parts.insert(new_parent->parts.begin() + new_position, part);
|
||||
// next time the other way around
|
||||
swap(old_parent, new_parent);
|
||||
swap(old_position, new_position);
|
||||
// remove from old
|
||||
assert(old_position < old_parent->parts.size());
|
||||
SymbolPartP part = old_parent->parts.at(old_position);
|
||||
old_parent->parts.erase( old_parent->parts.begin() + old_position);
|
||||
// add to new
|
||||
assert(new_position <= new_parent->parts.size());
|
||||
new_parent->parts.insert(new_parent->parts.begin() + new_position, part);
|
||||
// next time the other way around
|
||||
swap(old_parent, new_parent);
|
||||
swap(old_position, new_position);
|
||||
}
|
||||
|
||||
|
||||
UngroupReorderSymbolPartsAction::UngroupReorderSymbolPartsAction(SymbolGroup& group_parent, size_t group_pos, SymbolGroup& target_parent, size_t target_pos)
|
||||
: group_parent(group_parent), group_pos(group_pos)
|
||||
, target_parent(target_parent), target_pos(target_pos)
|
||||
: group_parent(group_parent), group_pos(group_pos)
|
||||
, target_parent(target_parent), target_pos(target_pos)
|
||||
{
|
||||
group = dynamic_pointer_cast<SymbolGroup>(group_parent.parts.at(group_pos));
|
||||
assert(group);
|
||||
group = dynamic_pointer_cast<SymbolGroup>(group_parent.parts.at(group_pos));
|
||||
assert(group);
|
||||
}
|
||||
|
||||
String UngroupReorderSymbolPartsAction::getName(bool to_undo) const {
|
||||
return _ACTION_("reorder parts");
|
||||
return _ACTION_("reorder parts");
|
||||
}
|
||||
|
||||
void UngroupReorderSymbolPartsAction::perform(bool to_undo) {
|
||||
if (!to_undo) {
|
||||
group_parent.parts.erase(group_parent.parts.begin() + group_pos);
|
||||
target_parent.parts.insert(target_parent.parts.begin() + target_pos, group->parts.begin(), group->parts.end());
|
||||
} else {
|
||||
target_parent.parts.erase(target_parent.parts.begin() + target_pos, target_parent.parts.begin() + target_pos + group->parts.size());
|
||||
group_parent.parts.insert(group_parent.parts.begin() + group_pos, group);
|
||||
}
|
||||
if (!to_undo) {
|
||||
group_parent.parts.erase(group_parent.parts.begin() + group_pos);
|
||||
target_parent.parts.insert(target_parent.parts.begin() + target_pos, group->parts.begin(), group->parts.end());
|
||||
} else {
|
||||
target_parent.parts.erase(target_parent.parts.begin() + target_pos, target_parent.parts.begin() + target_pos + group->parts.size());
|
||||
group_parent.parts.insert(group_parent.parts.begin() + group_pos, group);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------- : Group symbol parts
|
||||
|
||||
GroupSymbolPartsActionBase::GroupSymbolPartsActionBase(SymbolGroup& root)
|
||||
: root(root)
|
||||
: root(root)
|
||||
{}
|
||||
void GroupSymbolPartsActionBase::perform(bool to_undo) {
|
||||
swap(root.parts, old_part_list);
|
||||
swap(root.parts, old_part_list);
|
||||
}
|
||||
|
||||
GroupSymbolPartsAction::GroupSymbolPartsAction(SymbolGroup& root, const set<SymbolPartP>& parts, const SymbolGroupP& group)
|
||||
: GroupSymbolPartsActionBase(root)
|
||||
, group(group)
|
||||
: GroupSymbolPartsActionBase(root)
|
||||
, group(group)
|
||||
{
|
||||
// group parts in the old parts list
|
||||
bool done = false;
|
||||
FOR_EACH(p, root.parts) {
|
||||
assert(p != group);
|
||||
if (parts.find(p) != parts.end()) {
|
||||
// add to group instead
|
||||
group->parts.push_back(p);
|
||||
if (!done) {
|
||||
done = true;
|
||||
old_part_list.push_back(group);
|
||||
}
|
||||
} else {
|
||||
// not affected
|
||||
old_part_list.push_back(p);
|
||||
}
|
||||
}
|
||||
group->updateBounds();
|
||||
// group parts in the old parts list
|
||||
bool done = false;
|
||||
FOR_EACH(p, root.parts) {
|
||||
assert(p != group);
|
||||
if (parts.find(p) != parts.end()) {
|
||||
// add to group instead
|
||||
group->parts.push_back(p);
|
||||
if (!done) {
|
||||
done = true;
|
||||
old_part_list.push_back(group);
|
||||
}
|
||||
} else {
|
||||
// not affected
|
||||
old_part_list.push_back(p);
|
||||
}
|
||||
}
|
||||
group->updateBounds();
|
||||
}
|
||||
String GroupSymbolPartsAction::getName(bool to_undo) const {
|
||||
return group->isSymbolSymmetry() ? _ACTION_("add symmetry") : _ACTION_("group parts");
|
||||
return group->isSymbolSymmetry() ? _ACTION_("add symmetry") : _ACTION_("group parts");
|
||||
}
|
||||
|
||||
UngroupSymbolPartsAction::UngroupSymbolPartsAction(SymbolGroup& root, const set<SymbolPartP>& parts)
|
||||
: GroupSymbolPartsActionBase(root)
|
||||
: GroupSymbolPartsActionBase(root)
|
||||
{
|
||||
// break up the parts in the old parts list
|
||||
FOR_EACH(p, root.parts) {
|
||||
if (parts.find(p) != parts.end() && p->isSymbolGroup()) {
|
||||
// break up the group
|
||||
SymbolGroup* g = p->isSymbolGroup();
|
||||
FOR_EACH(p, g->parts) {
|
||||
old_part_list.push_back(p);
|
||||
}
|
||||
} else {
|
||||
// not affected
|
||||
old_part_list.push_back(p);
|
||||
}
|
||||
}
|
||||
// break up the parts in the old parts list
|
||||
FOR_EACH(p, root.parts) {
|
||||
if (parts.find(p) != parts.end() && p->isSymbolGroup()) {
|
||||
// break up the group
|
||||
SymbolGroup* g = p->isSymbolGroup();
|
||||
FOR_EACH(p, g->parts) {
|
||||
old_part_list.push_back(p);
|
||||
}
|
||||
} else {
|
||||
// not affected
|
||||
old_part_list.push_back(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
String UngroupSymbolPartsAction::getName(bool to_undo) const {
|
||||
return _ACTION_("ungroup parts");
|
||||
return _ACTION_("ungroup parts");
|
||||
}
|
||||
|
||||
+161
-161
@@ -26,9 +26,9 @@ class SymbolPartAction : public Action {};
|
||||
/// Anything that changes a set of parts
|
||||
class SymbolPartsAction : public SymbolPartAction {
|
||||
public:
|
||||
SymbolPartsAction(const set<SymbolPartP>& parts);
|
||||
|
||||
const set<SymbolPartP> parts; ///< Affected parts
|
||||
SymbolPartsAction(const set<SymbolPartP>& parts);
|
||||
|
||||
const set<SymbolPartP> parts; ///< Affected parts
|
||||
};
|
||||
|
||||
/// Anything that changes a part as displayed in the part list
|
||||
@@ -39,23 +39,23 @@ class SymbolPartListAction : public SymbolPartAction {};
|
||||
/// Move some symbol parts
|
||||
class SymbolPartMoveAction : public SymbolPartsAction {
|
||||
public:
|
||||
SymbolPartMoveAction(const set<SymbolPartP>& parts, const Vector2D& delta = Vector2D());
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
/// Update this action to move some more
|
||||
void move(const Vector2D& delta);
|
||||
|
||||
SymbolPartMoveAction(const set<SymbolPartP>& parts, const Vector2D& delta = Vector2D());
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
/// Update this action to move some more
|
||||
void move(const Vector2D& delta);
|
||||
|
||||
private:
|
||||
Vector2D delta; ///< How much to move
|
||||
Vector2D moved; ///< How much has been moved
|
||||
Bounds bounds; ///< Bounding box of the thing we are moving
|
||||
|
||||
void movePart(SymbolPart& part); ///< Move a single part
|
||||
Vector2D delta; ///< How much to move
|
||||
Vector2D moved; ///< How much has been moved
|
||||
Bounds bounds; ///< Bounding box of the thing we are moving
|
||||
|
||||
void movePart(SymbolPart& part); ///< Move a single part
|
||||
public:
|
||||
bool constrain; ///< Constrain movement?
|
||||
int snap; ///< Snap to grid?
|
||||
bool constrain; ///< Constrain movement?
|
||||
int snap; ///< Snap to grid?
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Rotating symbol parts
|
||||
@@ -63,37 +63,37 @@ class SymbolPartMoveAction : public SymbolPartsAction {
|
||||
/// Transforming symbol parts using a matrix
|
||||
class SymbolPartMatrixAction : public SymbolPartsAction {
|
||||
public:
|
||||
SymbolPartMatrixAction(const set<SymbolPartP>& parts, const Vector2D& center);
|
||||
|
||||
/// Update this action to move some more
|
||||
void move(const Vector2D& delta);
|
||||
|
||||
SymbolPartMatrixAction(const set<SymbolPartP>& parts, const Vector2D& center);
|
||||
|
||||
/// Update this action to move some more
|
||||
void move(const Vector2D& delta);
|
||||
|
||||
protected:
|
||||
/// Perform the transformation using the given matrix
|
||||
void transform(const Matrix2D& m);
|
||||
void transform(SymbolPart& part, const Matrix2D& m);
|
||||
|
||||
Vector2D center; ///< Center to transform around
|
||||
/// Perform the transformation using the given matrix
|
||||
void transform(const Matrix2D& m);
|
||||
void transform(SymbolPart& part, const Matrix2D& m);
|
||||
|
||||
Vector2D center; ///< Center to transform around
|
||||
};
|
||||
|
||||
/// Rotate some symbol parts
|
||||
class SymbolPartRotateAction : public SymbolPartMatrixAction {
|
||||
public:
|
||||
SymbolPartRotateAction(const set<SymbolPartP>& parts, const Vector2D& center);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
/// Update this action to rotate to a different angle
|
||||
void rotateTo(Radians newAngle);
|
||||
|
||||
/// Update this action to rotate by a deltaAngle
|
||||
void rotateBy(Radians deltaAngle);
|
||||
|
||||
SymbolPartRotateAction(const set<SymbolPartP>& parts, const Vector2D& center);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
/// Update this action to rotate to a different angle
|
||||
void rotateTo(Radians newAngle);
|
||||
|
||||
/// Update this action to rotate by a deltaAngle
|
||||
void rotateBy(Radians deltaAngle);
|
||||
|
||||
private:
|
||||
Radians angle; ///< How much to rotate?
|
||||
Radians angle; ///< How much to rotate?
|
||||
public:
|
||||
bool constrain; ///< Constrain movement?
|
||||
bool constrain; ///< Constrain movement?
|
||||
};
|
||||
|
||||
|
||||
@@ -102,21 +102,21 @@ class SymbolPartRotateAction : public SymbolPartMatrixAction {
|
||||
/// Shear some symbol parts
|
||||
class SymbolPartShearAction : public SymbolPartMatrixAction {
|
||||
public:
|
||||
SymbolPartShearAction(const set<SymbolPartP>& parts, const Vector2D& center);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
/// Change shear by a given amount
|
||||
void move(const Vector2D& deltaShear);
|
||||
|
||||
SymbolPartShearAction(const set<SymbolPartP>& parts, const Vector2D& center);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
/// Change shear by a given amount
|
||||
void move(const Vector2D& deltaShear);
|
||||
|
||||
private:
|
||||
Vector2D shear; ///< Shearing, shear.x == 0 || shear.y == 0
|
||||
Vector2D moved;
|
||||
void shearBy(const Vector2D& shear);
|
||||
Vector2D shear; ///< Shearing, shear.x == 0 || shear.y == 0
|
||||
Vector2D moved;
|
||||
void shearBy(const Vector2D& shear);
|
||||
public:
|
||||
// bool constrain; ///< Constrain movement?
|
||||
int snap; ///< Snap to grid?
|
||||
// bool constrain; ///< Constrain movement?
|
||||
int snap; ///< Snap to grid?
|
||||
};
|
||||
|
||||
|
||||
@@ -125,29 +125,29 @@ class SymbolPartShearAction : public SymbolPartMatrixAction {
|
||||
/// Scale some symbol parts
|
||||
class SymbolPartScaleAction : public SymbolPartsAction {
|
||||
public:
|
||||
SymbolPartScaleAction(const set<SymbolPartP>& parts, int scaleX, int scaleY);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
/// Change min and max coordinates
|
||||
void move(const Vector2D& delta_min, const Vector2D& delta_max);
|
||||
/// Update the action's effect
|
||||
void update();
|
||||
|
||||
SymbolPartScaleAction(const set<SymbolPartP>& parts, int scaleX, int scaleY);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
/// Change min and max coordinates
|
||||
void move(const Vector2D& delta_min, const Vector2D& delta_max);
|
||||
/// Update the action's effect
|
||||
void update();
|
||||
|
||||
private:
|
||||
Vector2D old_min, old_size; ///< the original pos/size
|
||||
Vector2D new_real_min, new_real_size; ///< the target pos/sizevoid shearBy(const Vector2D& shear)
|
||||
Vector2D new_min, new_size; ///< the target pos/size after applying constrains
|
||||
int scaleX, scaleY; ///< to what corner are we attached?
|
||||
/// Transform everything in the parts
|
||||
void transformAll();
|
||||
void transformPart(SymbolPart&);
|
||||
/// Transform a single vector
|
||||
inline Vector2D transform(const Vector2D& v);
|
||||
Vector2D old_min, old_size; ///< the original pos/size
|
||||
Vector2D new_real_min, new_real_size; ///< the target pos/sizevoid shearBy(const Vector2D& shear)
|
||||
Vector2D new_min, new_size; ///< the target pos/size after applying constrains
|
||||
int scaleX, scaleY; ///< to what corner are we attached?
|
||||
/// Transform everything in the parts
|
||||
void transformAll();
|
||||
void transformPart(SymbolPart&);
|
||||
/// Transform a single vector
|
||||
inline Vector2D transform(const Vector2D& v);
|
||||
public:
|
||||
bool constrain; ///< Constrain movement?
|
||||
int snap; ///< Snap to grid?
|
||||
bool constrain; ///< Constrain movement?
|
||||
int snap; ///< Snap to grid?
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Change combine mode
|
||||
@@ -155,15 +155,15 @@ class SymbolPartScaleAction : public SymbolPartsAction {
|
||||
/// Change the name of a symbol part
|
||||
class CombiningModeAction : public SymbolPartsAction {
|
||||
public:
|
||||
// All parts must be SymbolParts
|
||||
CombiningModeAction(const set<SymbolPartP>& parts, SymbolShapeCombine mode);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
// All parts must be SymbolParts
|
||||
CombiningModeAction(const set<SymbolPartP>& parts, SymbolShapeCombine mode);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
private:
|
||||
void add(const SymbolPartP&, SymbolShapeCombine mode);
|
||||
vector<pair<SymbolShapeP,SymbolShapeCombine> > parts; ///< Affected parts with new combining modes
|
||||
void add(const SymbolPartP&, SymbolShapeCombine mode);
|
||||
vector<pair<SymbolShapeP,SymbolShapeCombine> > parts; ///< Affected parts with new combining modes
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Change name
|
||||
@@ -171,17 +171,17 @@ class CombiningModeAction : public SymbolPartsAction {
|
||||
/// Change the name of a symbol part
|
||||
class SymbolPartNameAction : public SymbolPartAction {
|
||||
public:
|
||||
SymbolPartNameAction(const SymbolPartP& part, const String& name, size_t old_cursor, size_t new_cursor);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
virtual bool merge(const Action& action);
|
||||
|
||||
SymbolPartNameAction(const SymbolPartP& part, const String& name, size_t old_cursor, size_t new_cursor);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
virtual bool merge(const Action& action);
|
||||
|
||||
public:
|
||||
SymbolPartP part; ///< Affected part
|
||||
String part_name; ///< New name
|
||||
size_t old_cursor; ///< Cursor position
|
||||
size_t new_cursor; ///< Cursor position
|
||||
SymbolPartP part; ///< Affected part
|
||||
String part_name; ///< New name
|
||||
size_t old_cursor; ///< Cursor position
|
||||
size_t new_cursor; ///< Cursor position
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Add symbol part
|
||||
@@ -189,14 +189,14 @@ class SymbolPartNameAction : public SymbolPartAction {
|
||||
/// Adding a part to a symbol, added at the front of the list (drawn on top)
|
||||
class AddSymbolPartAction : public SymbolPartListAction {
|
||||
public:
|
||||
AddSymbolPartAction(Symbol& symbol, const SymbolPartP& part);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
AddSymbolPartAction(Symbol& symbol, const SymbolPartP& part);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
private:
|
||||
Symbol& symbol; ///< Symbol to add the part to
|
||||
SymbolPartP part; ///< Part to add
|
||||
Symbol& symbol; ///< Symbol to add the part to
|
||||
SymbolPartP part; ///< Part to add
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Remove symbol part
|
||||
@@ -204,28 +204,28 @@ class AddSymbolPartAction : public SymbolPartListAction {
|
||||
/// Removing parts from a symbol
|
||||
class RemoveSymbolPartsAction : public SymbolPartListAction {
|
||||
public:
|
||||
RemoveSymbolPartsAction(Symbol& symbol, const set<SymbolPartP>& parts);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
RemoveSymbolPartsAction(Symbol& symbol, const set<SymbolPartP>& parts);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
private:
|
||||
Symbol& symbol;
|
||||
/// Check for removals in a group
|
||||
void check(SymbolGroup& group, const set<SymbolPartP>& parts);
|
||||
Symbol& symbol;
|
||||
/// Check for removals in a group
|
||||
void check(SymbolGroup& group, const set<SymbolPartP>& parts);
|
||||
public:
|
||||
/// A removal step
|
||||
struct Removal {
|
||||
inline Removal(SymbolGroup& parent, size_t pos, const SymbolPartP& removed)
|
||||
: parent(&parent), pos(pos), removed(removed)
|
||||
{}
|
||||
SymbolGroup* parent;
|
||||
size_t pos;
|
||||
SymbolPartP removed;
|
||||
};
|
||||
/// A removal step
|
||||
struct Removal {
|
||||
inline Removal(SymbolGroup& parent, size_t pos, const SymbolPartP& removed)
|
||||
: parent(&parent), pos(pos), removed(removed)
|
||||
{}
|
||||
SymbolGroup* parent;
|
||||
size_t pos;
|
||||
SymbolPartP removed;
|
||||
};
|
||||
private:
|
||||
/// Removed parts, sorted by ascending pos
|
||||
vector<Removal> removals;
|
||||
/// Removed parts, sorted by ascending pos
|
||||
vector<Removal> removals;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Duplicate symbol parts
|
||||
@@ -233,18 +233,18 @@ class RemoveSymbolPartsAction : public SymbolPartListAction {
|
||||
/// Duplicating parts in a symbol
|
||||
class DuplicateSymbolPartsAction : public SymbolPartListAction {
|
||||
public:
|
||||
DuplicateSymbolPartsAction(Symbol& symbol, const set<SymbolPartP>& parts);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
/// Fill a set with all the new parts
|
||||
void getParts(set<SymbolPartP>& parts);
|
||||
|
||||
DuplicateSymbolPartsAction(Symbol& symbol, const set<SymbolPartP>& parts);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
/// Fill a set with all the new parts
|
||||
void getParts(set<SymbolPartP>& parts);
|
||||
|
||||
private:
|
||||
Symbol& symbol;
|
||||
/// Duplicates of parts and their positions, sorted by ascending pos
|
||||
vector<pair<SymbolPartP, size_t> > duplications;
|
||||
Symbol& symbol;
|
||||
/// Duplicates of parts and their positions, sorted by ascending pos
|
||||
vector<pair<SymbolPartP, size_t> > duplications;
|
||||
};
|
||||
|
||||
|
||||
@@ -253,32 +253,32 @@ class DuplicateSymbolPartsAction : public SymbolPartListAction {
|
||||
/// Change the position of a part in a symbol, by moving a part.
|
||||
class ReorderSymbolPartsAction : public SymbolPartListAction {
|
||||
public:
|
||||
ReorderSymbolPartsAction(SymbolGroup& old_parent, size_t old_position, SymbolGroup& new_parent, size_t new_position);
|
||||
ReorderSymbolPartsAction(SymbolGroup& old_parent, size_t old_position, SymbolGroup& new_parent, size_t new_position);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
private:
|
||||
SymbolGroup* old_parent, *new_parent;///< Parents to move from and to
|
||||
SymbolGroup* old_parent, *new_parent;///< Parents to move from and to
|
||||
public:
|
||||
size_t old_position, new_position; ///< Positions to move from and to
|
||||
size_t old_position, new_position; ///< Positions to move from and to
|
||||
};
|
||||
|
||||
/// Break up a single group, and put its contents at a specific position
|
||||
class UngroupReorderSymbolPartsAction : public SymbolPartListAction {
|
||||
public:
|
||||
/// Remove all the given groups
|
||||
UngroupReorderSymbolPartsAction(SymbolGroup& group_parent, size_t group_pos, SymbolGroup& target_parent, size_t target_pos);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
/// Remove all the given groups
|
||||
UngroupReorderSymbolPartsAction(SymbolGroup& group_parent, size_t group_pos, SymbolGroup& target_parent, size_t target_pos);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
private:
|
||||
SymbolGroup& group_parent;
|
||||
size_t group_pos;
|
||||
SymbolGroupP group; ///< Group to destroy
|
||||
SymbolGroup& target_parent;
|
||||
size_t target_pos;
|
||||
SymbolGroup& group_parent;
|
||||
size_t group_pos;
|
||||
SymbolGroupP group; ///< Group to destroy
|
||||
SymbolGroup& target_parent;
|
||||
size_t target_pos;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Group symbol parts
|
||||
@@ -286,32 +286,32 @@ class UngroupReorderSymbolPartsAction : public SymbolPartListAction {
|
||||
/// Group multiple symbol parts together
|
||||
class GroupSymbolPartsActionBase : public SymbolPartListAction {
|
||||
public:
|
||||
GroupSymbolPartsActionBase(SymbolGroup& root);
|
||||
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
GroupSymbolPartsActionBase(SymbolGroup& root);
|
||||
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
protected:
|
||||
SymbolGroup& root; ///< Symbol or group to group stuff in
|
||||
vector<SymbolPartP> old_part_list; ///< Old part list of the symbol
|
||||
SymbolGroup& root; ///< Symbol or group to group stuff in
|
||||
vector<SymbolPartP> old_part_list; ///< Old part list of the symbol
|
||||
};
|
||||
|
||||
/// Group multiple symbol parts together
|
||||
class GroupSymbolPartsAction : public GroupSymbolPartsActionBase {
|
||||
public:
|
||||
GroupSymbolPartsAction(SymbolGroup& root, const set<SymbolPartP>& parts, const SymbolGroupP& group);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
GroupSymbolPartsAction(SymbolGroup& root, const set<SymbolPartP>& parts, const SymbolGroupP& group);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
private:
|
||||
SymbolGroupP group;
|
||||
SymbolGroupP group;
|
||||
};
|
||||
|
||||
/// Break up one or more SymbolGroups
|
||||
class UngroupSymbolPartsAction : public GroupSymbolPartsActionBase {
|
||||
public:
|
||||
/// Remove all the given groups
|
||||
UngroupSymbolPartsAction(SymbolGroup& root, const set<SymbolPartP>& groups);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
/// Remove all the given groups
|
||||
UngroupSymbolPartsAction(SymbolGroup& root, const set<SymbolPartP>& groups);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
+311
-311
@@ -18,270 +18,270 @@ DECLARE_TYPEOF_COLLECTION(ControlPointP);
|
||||
inline double sgn(double v) { return v > 0 ? 1 : -1; }
|
||||
|
||||
Vector2D constrain_vector(const Vector2D& v, bool constrain, bool only_diagonal) {
|
||||
if (!constrain) return v;
|
||||
double ax = fabs(v.x), ay = fabs(v.y);
|
||||
if (ax * 2 < ay && !only_diagonal) {
|
||||
return Vector2D(0, v.y); // vertical
|
||||
} else if(ay * 2 < ax && !only_diagonal) {
|
||||
return Vector2D(v.x, 0); // horizontal
|
||||
} else {
|
||||
return Vector2D( // diagonal
|
||||
sgn(v.x) * (ax + ay) / 2,
|
||||
sgn(v.y) * (ax + ay) / 2
|
||||
);
|
||||
}
|
||||
if (!constrain) return v;
|
||||
double ax = fabs(v.x), ay = fabs(v.y);
|
||||
if (ax * 2 < ay && !only_diagonal) {
|
||||
return Vector2D(0, v.y); // vertical
|
||||
} else if(ay * 2 < ax && !only_diagonal) {
|
||||
return Vector2D(v.x, 0); // horizontal
|
||||
} else {
|
||||
return Vector2D( // diagonal
|
||||
sgn(v.x) * (ax + ay) / 2,
|
||||
sgn(v.y) * (ax + ay) / 2
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
inline double snap(double x, int steps) {
|
||||
return steps <= 0 ? x : floor(x * steps + 0.5) / steps;
|
||||
return steps <= 0 ? x : floor(x * steps + 0.5) / steps;
|
||||
}
|
||||
|
||||
Vector2D snap_vector(const Vector2D& v, int steps) {
|
||||
return Vector2D(snap(v.x, steps), snap(v.y, steps));
|
||||
return Vector2D(snap(v.x, steps), snap(v.y, steps));
|
||||
}
|
||||
|
||||
Vector2D constrain_snap_vector(const Vector2D& v, const Vector2D& d, bool constrain, int steps) {
|
||||
if (!constrain) return snap_vector(v+d, steps);
|
||||
double ax = fabs(d.x), ay = fabs(d.y);
|
||||
if (ax * 2 < ay) {
|
||||
return Vector2D(v.x, snap(d.y + v.y, steps)); // vertical
|
||||
} else if(ay * 2 < ax) {
|
||||
return Vector2D(snap(d.x + v.x, steps), v.y); // horizontal
|
||||
} else {
|
||||
double dc = (ax + ay) / 2; // delta in both directions
|
||||
double dxs = snap(v.x + dc, steps) - v.x; // snapped to x
|
||||
double dys = snap(v.y + dc, steps) - v.y; // snapped to y
|
||||
if (fabs(dxs-dc) < fabs(dys-dc)) {
|
||||
// take the one that is closest to the unsnaped delta
|
||||
return Vector2D(v.x + sgn(d.x) * dxs, v.y + sgn(d.y) * dxs);
|
||||
} else {
|
||||
return Vector2D(v.x + sgn(d.x) * dys, v.y + sgn(d.y) * dys);
|
||||
}
|
||||
}
|
||||
if (!constrain) return snap_vector(v+d, steps);
|
||||
double ax = fabs(d.x), ay = fabs(d.y);
|
||||
if (ax * 2 < ay) {
|
||||
return Vector2D(v.x, snap(d.y + v.y, steps)); // vertical
|
||||
} else if(ay * 2 < ax) {
|
||||
return Vector2D(snap(d.x + v.x, steps), v.y); // horizontal
|
||||
} else {
|
||||
double dc = (ax + ay) / 2; // delta in both directions
|
||||
double dxs = snap(v.x + dc, steps) - v.x; // snapped to x
|
||||
double dys = snap(v.y + dc, steps) - v.y; // snapped to y
|
||||
if (fabs(dxs-dc) < fabs(dys-dc)) {
|
||||
// take the one that is closest to the unsnaped delta
|
||||
return Vector2D(v.x + sgn(d.x) * dxs, v.y + sgn(d.y) * dxs);
|
||||
} else {
|
||||
return Vector2D(v.x + sgn(d.x) * dys, v.y + sgn(d.y) * dys);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vector2D constrain_snap_vector_offset(const Vector2D& off1, const Vector2D& d, bool constrain, int steps) {
|
||||
return constrain_snap_vector(off1, d, constrain, steps) - off1;
|
||||
return constrain_snap_vector(off1, d, constrain, steps) - off1;
|
||||
}
|
||||
// calculate constrained delta for the given offset, store in output if it is better
|
||||
void constrain_snap_vector_offset_(const Vector2D& off, const Vector2D& d, bool constrain, int steps, Vector2D& best, double& best_length) {
|
||||
Vector2D d2 = constrain_snap_vector_offset(off, d, constrain, steps);
|
||||
double l2 = d2.lengthSqr();
|
||||
if (l2 < best_length) {
|
||||
best_length = l2;
|
||||
best = d2;
|
||||
}
|
||||
Vector2D d2 = constrain_snap_vector_offset(off, d, constrain, steps);
|
||||
double l2 = d2.lengthSqr();
|
||||
if (l2 < best_length) {
|
||||
best_length = l2;
|
||||
best = d2;
|
||||
}
|
||||
}
|
||||
Vector2D constrain_snap_vector_offset(const Vector2D& off1, const Vector2D& off2, const Vector2D& d, bool constrain, int steps) {
|
||||
Vector2D dd; double l = numeric_limits<double>::infinity();
|
||||
constrain_snap_vector_offset_(off1, d, constrain, steps, dd, l);
|
||||
constrain_snap_vector_offset_(off2, d, constrain, steps, dd, l);
|
||||
constrain_snap_vector_offset_(Vector2D(off1.x,off2.y), d, constrain, steps, dd, l);
|
||||
constrain_snap_vector_offset_(Vector2D(off2.x,off1.y), d, constrain, steps, dd, l);
|
||||
return dd;
|
||||
Vector2D dd; double l = numeric_limits<double>::infinity();
|
||||
constrain_snap_vector_offset_(off1, d, constrain, steps, dd, l);
|
||||
constrain_snap_vector_offset_(off2, d, constrain, steps, dd, l);
|
||||
constrain_snap_vector_offset_(Vector2D(off1.x,off2.y), d, constrain, steps, dd, l);
|
||||
constrain_snap_vector_offset_(Vector2D(off2.x,off1.y), d, constrain, steps, dd, l);
|
||||
return dd;
|
||||
}
|
||||
|
||||
String action_name_for(const set<ControlPointP>& points, const String& action) {
|
||||
return format_string(action, points.size() == 1 ? _TYPE_("point") : _TYPE_("points"));
|
||||
return format_string(action, points.size() == 1 ? _TYPE_("point") : _TYPE_("points"));
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------- : Move control point
|
||||
|
||||
ControlPointMoveAction::ControlPointMoveAction(const set<ControlPointP>& points)
|
||||
: points(points)
|
||||
, constrain(false)
|
||||
, snap(0)
|
||||
: points(points)
|
||||
, constrain(false)
|
||||
, snap(0)
|
||||
{
|
||||
oldValues.reserve(points.size());
|
||||
FOR_EACH(p, points) {
|
||||
oldValues.push_back(p->pos);
|
||||
}
|
||||
oldValues.reserve(points.size());
|
||||
FOR_EACH(p, points) {
|
||||
oldValues.push_back(p->pos);
|
||||
}
|
||||
}
|
||||
|
||||
String ControlPointMoveAction::getName(bool to_undo) const {
|
||||
return action_name_for(points, _ACTION_("move"));
|
||||
return action_name_for(points, _ACTION_("move"));
|
||||
}
|
||||
|
||||
void ControlPointMoveAction::perform(bool to_undo) {
|
||||
FOR_EACH_2(p,points, op,oldValues) {
|
||||
swap(p->pos, op);
|
||||
}
|
||||
FOR_EACH_2(p,points, op,oldValues) {
|
||||
swap(p->pos, op);
|
||||
}
|
||||
}
|
||||
|
||||
void ControlPointMoveAction::move (const Vector2D& deltaDelta) {
|
||||
delta += deltaDelta;
|
||||
// Move each point by delta, possibly constrained
|
||||
set<ControlPointP>::const_iterator it = points.begin();
|
||||
vector<Vector2D> ::iterator it2 = oldValues.begin();
|
||||
for( ; it != points.end() && it2 != oldValues.end() ; ++it, ++it2) {
|
||||
(*it)->pos = constrain_snap_vector(*it2, delta, constrain, snap);
|
||||
}
|
||||
delta += deltaDelta;
|
||||
// Move each point by delta, possibly constrained
|
||||
set<ControlPointP>::const_iterator it = points.begin();
|
||||
vector<Vector2D> ::iterator it2 = oldValues.begin();
|
||||
for( ; it != points.end() && it2 != oldValues.end() ; ++it, ++it2) {
|
||||
(*it)->pos = constrain_snap_vector(*it2, delta, constrain, snap);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------- : Move handle
|
||||
|
||||
HandleMoveAction::HandleMoveAction(const SelectedHandle& handle)
|
||||
: handle(handle)
|
||||
, old_handle(handle.getHandle())
|
||||
, old_other (handle.getOther())
|
||||
, constrain(false)
|
||||
, snap(0)
|
||||
: handle(handle)
|
||||
, old_handle(handle.getHandle())
|
||||
, old_other (handle.getOther())
|
||||
, constrain(false)
|
||||
, snap(0)
|
||||
{}
|
||||
|
||||
String HandleMoveAction::getName(bool to_undo) const {
|
||||
return _ACTION_("move handle");
|
||||
return _ACTION_("move handle");
|
||||
}
|
||||
|
||||
void HandleMoveAction::perform(bool to_undo) {
|
||||
swap(old_handle, handle.getHandle());
|
||||
swap(old_other, handle.getOther());
|
||||
swap(old_handle, handle.getHandle());
|
||||
swap(old_other, handle.getOther());
|
||||
}
|
||||
|
||||
void HandleMoveAction::move(const Vector2D& deltaDelta) {
|
||||
delta += deltaDelta;
|
||||
handle.getHandle() = constrain_snap_vector_offset(handle.point->pos, old_handle + delta, constrain, snap);
|
||||
handle.getOther() = old_other;
|
||||
handle.onUpdateHandle();
|
||||
delta += deltaDelta;
|
||||
handle.getHandle() = constrain_snap_vector_offset(handle.point->pos, old_handle + delta, constrain, snap);
|
||||
handle.getOther() = old_other;
|
||||
handle.onUpdateHandle();
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------- : Segment mode
|
||||
|
||||
ControlPointUpdate::ControlPointUpdate(const ControlPointP& pnt)
|
||||
: other(*pnt)
|
||||
, point(pnt)
|
||||
: other(*pnt)
|
||||
, point(pnt)
|
||||
{}
|
||||
|
||||
void ControlPointUpdate::perform() {
|
||||
swap(other, *point);
|
||||
swap(other, *point);
|
||||
}
|
||||
|
||||
|
||||
SegmentModeAction::SegmentModeAction(const ControlPointP& p1, const ControlPointP& p2, SegmentMode mode)
|
||||
: point1(p1), point2(p2)
|
||||
: point1(p1), point2(p2)
|
||||
{
|
||||
if (p1->segment_after == mode) return;
|
||||
point1.other.segment_after = point2.other.segment_before = mode;
|
||||
if (mode == SEGMENT_LINE) {
|
||||
point1.other.delta_after = Vector2D(0,0);
|
||||
point2.other.delta_before = Vector2D(0,0);
|
||||
point1.other.lock = LOCK_FREE;
|
||||
point2.other.lock = LOCK_FREE;
|
||||
} else if (mode == SEGMENT_CURVE) {
|
||||
point1.other.delta_after = (p2->pos - p1->pos) / 3.0f;
|
||||
point2.other.delta_before = (p1->pos - p2->pos) / 3.0f;
|
||||
}
|
||||
if (p1->segment_after == mode) return;
|
||||
point1.other.segment_after = point2.other.segment_before = mode;
|
||||
if (mode == SEGMENT_LINE) {
|
||||
point1.other.delta_after = Vector2D(0,0);
|
||||
point2.other.delta_before = Vector2D(0,0);
|
||||
point1.other.lock = LOCK_FREE;
|
||||
point2.other.lock = LOCK_FREE;
|
||||
} else if (mode == SEGMENT_CURVE) {
|
||||
point1.other.delta_after = (p2->pos - p1->pos) / 3.0f;
|
||||
point2.other.delta_before = (p1->pos - p2->pos) / 3.0f;
|
||||
}
|
||||
}
|
||||
String SegmentModeAction::getName(bool to_undo) const {
|
||||
SegmentMode mode = to_undo ? point1.point->segment_after : point1.other.segment_after;
|
||||
if (mode == SEGMENT_LINE) return _ACTION_("convert to line");
|
||||
else return _ACTION_("convert to curve");
|
||||
SegmentMode mode = to_undo ? point1.point->segment_after : point1.other.segment_after;
|
||||
if (mode == SEGMENT_LINE) return _ACTION_("convert to line");
|
||||
else return _ACTION_("convert to curve");
|
||||
}
|
||||
|
||||
void SegmentModeAction::perform(bool to_undo) {
|
||||
point1.perform();
|
||||
point2.perform();
|
||||
point1.perform();
|
||||
point2.perform();
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------- : Locking mode
|
||||
|
||||
LockModeAction::LockModeAction(const ControlPointP& p, LockMode lock)
|
||||
: point(p)
|
||||
: point(p)
|
||||
{
|
||||
point.other.lock = lock;
|
||||
point.other.onUpdateLock();
|
||||
point.other.lock = lock;
|
||||
point.other.onUpdateLock();
|
||||
}
|
||||
|
||||
String LockModeAction::getName(bool to_undo) const {
|
||||
return _ACTION_("lock point");
|
||||
return _ACTION_("lock point");
|
||||
}
|
||||
|
||||
void LockModeAction::perform(bool to_undo) {
|
||||
point.perform();
|
||||
point.perform();
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------- : Move curve
|
||||
|
||||
CurveDragAction::CurveDragAction(const ControlPointP& point1, const ControlPointP& point2)
|
||||
: SegmentModeAction(point1, point2, SEGMENT_CURVE)
|
||||
: SegmentModeAction(point1, point2, SEGMENT_CURVE)
|
||||
{}
|
||||
|
||||
String CurveDragAction::getName(bool to_undo) const {
|
||||
return _ACTION_("move curve");
|
||||
return _ACTION_("move curve");
|
||||
}
|
||||
|
||||
void CurveDragAction::perform(bool to_undo) {
|
||||
SegmentModeAction::perform(to_undo);
|
||||
SegmentModeAction::perform(to_undo);
|
||||
}
|
||||
|
||||
void CurveDragAction::move(const Vector2D& delta, double t) {
|
||||
// Logic:
|
||||
// Assuming old point is p, new point is p'
|
||||
// Point on old bezier curve is:
|
||||
// p = a t^3 + 3b (1-t) t^2 + 3c (1-t)^2 t + d (1-t)^2
|
||||
// Point on new bezier curve is:
|
||||
// p_(' = a t^3 + 3b') (1-t) t^2 + 3c' (1-t)^2 t + d (1-t)^2
|
||||
// We now want to change control points b and c, the closer we are to b (t close to 0)
|
||||
// the more effect we have on b, so we substitute:
|
||||
// b' = b + x t
|
||||
// c' = c + x (1-t)
|
||||
// Solving for x we get:
|
||||
// x = (p'-p) / ( t (1-t) ( t^2 + (1-t)^2) )
|
||||
// Naming:
|
||||
// delta = p' - p
|
||||
// pointDelta = x * t * (1-t)
|
||||
Vector2D pointDelta = delta / (3 * (t * t + (1-t) * (1-t)));
|
||||
point1.point->delta_after += pointDelta / t;
|
||||
point2.point->delta_before += pointDelta / (1-t);
|
||||
point1.point->onUpdateHandle(HANDLE_AFTER);
|
||||
point2.point->onUpdateHandle(HANDLE_BEFORE);
|
||||
// Logic:
|
||||
// Assuming old point is p, new point is p'
|
||||
// Point on old bezier curve is:
|
||||
// p = a t^3 + 3b (1-t) t^2 + 3c (1-t)^2 t + d (1-t)^2
|
||||
// Point on new bezier curve is:
|
||||
// p_(' = a t^3 + 3b') (1-t) t^2 + 3c' (1-t)^2 t + d (1-t)^2
|
||||
// We now want to change control points b and c, the closer we are to b (t close to 0)
|
||||
// the more effect we have on b, so we substitute:
|
||||
// b' = b + x t
|
||||
// c' = c + x (1-t)
|
||||
// Solving for x we get:
|
||||
// x = (p'-p) / ( t (1-t) ( t^2 + (1-t)^2) )
|
||||
// Naming:
|
||||
// delta = p' - p
|
||||
// pointDelta = x * t * (1-t)
|
||||
Vector2D pointDelta = delta / (3 * (t * t + (1-t) * (1-t)));
|
||||
point1.point->delta_after += pointDelta / t;
|
||||
point2.point->delta_before += pointDelta / (1-t);
|
||||
point1.point->onUpdateHandle(HANDLE_AFTER);
|
||||
point2.point->onUpdateHandle(HANDLE_BEFORE);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------- : Add control point
|
||||
|
||||
ControlPointAddAction::ControlPointAddAction(const SymbolShapeP& shape, UInt insert_after, double t)
|
||||
: shape(shape)
|
||||
, new_point(new ControlPoint())
|
||||
, insert_after(insert_after)
|
||||
, point1(shape->getPoint(insert_after))
|
||||
, point2(shape->getPoint(insert_after + 1))
|
||||
: shape(shape)
|
||||
, new_point(new ControlPoint())
|
||||
, insert_after(insert_after)
|
||||
, point1(shape->getPoint(insert_after))
|
||||
, point2(shape->getPoint(insert_after + 1))
|
||||
{
|
||||
// calculate new point
|
||||
if (point1.other.segment_after == SEGMENT_CURVE) {
|
||||
// calculate new handles using de Casteljau's subdivision algorithm
|
||||
deCasteljau(point1.other, point2.other, t, *new_point);
|
||||
// unlock if needed
|
||||
if (point1.other.lock == LOCK_SIZE) point1.other.lock = LOCK_DIR;
|
||||
if (point2.other.lock == LOCK_SIZE) point2.other.lock = LOCK_DIR;
|
||||
new_point->lock = LOCK_DIR;
|
||||
new_point->segment_before = SEGMENT_CURVE;
|
||||
new_point->segment_after = SEGMENT_CURVE;
|
||||
} else {
|
||||
new_point->pos = point1.other.pos * (1 - t) + point2.other.pos * t;
|
||||
new_point->lock = LOCK_FREE;
|
||||
new_point->segment_before = SEGMENT_LINE;
|
||||
new_point->segment_after = SEGMENT_LINE;
|
||||
}
|
||||
// calculate new point
|
||||
if (point1.other.segment_after == SEGMENT_CURVE) {
|
||||
// calculate new handles using de Casteljau's subdivision algorithm
|
||||
deCasteljau(point1.other, point2.other, t, *new_point);
|
||||
// unlock if needed
|
||||
if (point1.other.lock == LOCK_SIZE) point1.other.lock = LOCK_DIR;
|
||||
if (point2.other.lock == LOCK_SIZE) point2.other.lock = LOCK_DIR;
|
||||
new_point->lock = LOCK_DIR;
|
||||
new_point->segment_before = SEGMENT_CURVE;
|
||||
new_point->segment_after = SEGMENT_CURVE;
|
||||
} else {
|
||||
new_point->pos = point1.other.pos * (1 - t) + point2.other.pos * t;
|
||||
new_point->lock = LOCK_FREE;
|
||||
new_point->segment_before = SEGMENT_LINE;
|
||||
new_point->segment_after = SEGMENT_LINE;
|
||||
}
|
||||
}
|
||||
|
||||
String ControlPointAddAction::getName(bool to_undo) const {
|
||||
return _ACTION_("add control point");
|
||||
return _ACTION_("add control point");
|
||||
}
|
||||
|
||||
void ControlPointAddAction::perform(bool to_undo) {
|
||||
if (to_undo) { // remove the point
|
||||
shape->points.erase( shape->points.begin() + insert_after + 1);
|
||||
} else {
|
||||
shape->points.insert(shape->points.begin() + insert_after + 1, new_point);
|
||||
}
|
||||
// update points before/after
|
||||
point1.perform();
|
||||
point2.perform();
|
||||
if (to_undo) { // remove the point
|
||||
shape->points.erase( shape->points.begin() + insert_after + 1);
|
||||
} else {
|
||||
shape->points.insert(shape->points.begin() + insert_after + 1, new_point);
|
||||
}
|
||||
// update points before/after
|
||||
point1.perform();
|
||||
point2.perform();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Remove control point
|
||||
@@ -289,89 +289,89 @@ void ControlPointAddAction::perform(bool to_undo) {
|
||||
/// Sqaure root that caries the sign over the root
|
||||
/// or formally: ssqrt(x) = Re<sqrt(x)> - Im<sqrt(x)> = x / sqrt(|x|)
|
||||
double ssqrt(double x) {
|
||||
if (x > 0) return sqrt(x);
|
||||
else return -sqrt(-x);
|
||||
if (x > 0) return sqrt(x);
|
||||
else return -sqrt(-x);
|
||||
}
|
||||
|
||||
// Remove a single control point
|
||||
class SinglePointRemoveAction : public Action, public IntrusivePtrBase<SinglePointRemoveAction> {
|
||||
public:
|
||||
SinglePointRemoveAction(const SymbolShapeP& shape, UInt position);
|
||||
|
||||
virtual String getName(bool to_undo) const { return _("Delete point"); }
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
SinglePointRemoveAction(const SymbolShapeP& shape, UInt position);
|
||||
|
||||
virtual String getName(bool to_undo) const { return _("Delete point"); }
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
private:
|
||||
SymbolShapeP shape;
|
||||
UInt position;
|
||||
ControlPointP point; ///< Removed point
|
||||
ControlPointUpdate point1, point2; ///< Points before/after
|
||||
SymbolShapeP shape;
|
||||
UInt position;
|
||||
ControlPointP point; ///< Removed point
|
||||
ControlPointUpdate point1, point2; ///< Points before/after
|
||||
};
|
||||
|
||||
SinglePointRemoveAction::SinglePointRemoveAction(const SymbolShapeP& shape, UInt position)
|
||||
: shape(shape)
|
||||
, position(position)
|
||||
, point (shape->getPoint(position))
|
||||
, point1(shape->getPoint(position - 1))
|
||||
, point2(shape->getPoint(position + 1))
|
||||
: shape(shape)
|
||||
, position(position)
|
||||
, point (shape->getPoint(position))
|
||||
, point1(shape->getPoint(position - 1))
|
||||
, point2(shape->getPoint(position + 1))
|
||||
{
|
||||
if (point1.other.segment_after == SEGMENT_CURVE || point2.other.segment_before == SEGMENT_CURVE) {
|
||||
// try to preserve curve
|
||||
Vector2D before = point->delta_before;
|
||||
Vector2D after = point->delta_after;
|
||||
|
||||
// convert both segments to curves first
|
||||
if (point1.other.segment_after != SEGMENT_CURVE) {
|
||||
before = (point1.other.pos - point->pos) / 3.0;
|
||||
point1.other.delta_after = -before;
|
||||
point1.other.segment_after = SEGMENT_CURVE;
|
||||
}
|
||||
if (point2.other.segment_before != SEGMENT_CURVE) {
|
||||
after = (point2.other.pos - point->pos) / 3.0;
|
||||
point2.other.delta_before = -after;
|
||||
point2.other.segment_before = SEGMENT_CURVE;
|
||||
}
|
||||
|
||||
// The inverse of adding a point, reconstruct the original handles
|
||||
// before being subdivided using de Casteljau's algorithm
|
||||
// length of handles
|
||||
double bl = before.length() + 0.00000001; // prevent division by 0
|
||||
double al = after .length() + 0.00000001;
|
||||
double totl = bl + al;
|
||||
// set new handle sizes
|
||||
point1.other.delta_after *= totl / bl;
|
||||
point2.other.delta_before *= totl / al;
|
||||
|
||||
// Also take in acount cases where the point does not correspond to a freshly added point.
|
||||
// distance from the point to the curve as it would be in the above case can be used,
|
||||
// in the case of a point just added this distance = 0
|
||||
BezierCurve c(point1.other, point2.other);
|
||||
double t = bl / totl;
|
||||
Vector2D p = c.pointAt(t);
|
||||
Vector2D distP = point->pos - p;
|
||||
// adjust handle sizes
|
||||
point1.other.delta_after *= ssqrt(dot(distP, point1.other.delta_after) /point1.other.delta_after.lengthSqr()) + 1;
|
||||
point2.other.delta_before *= ssqrt(dot(distP, point2.other.delta_before)/point2.other.delta_before.lengthSqr()) + 1;
|
||||
|
||||
// unlock if needed
|
||||
if (point1.other.lock == LOCK_SIZE) point1.other.lock = LOCK_DIR;
|
||||
if (point2.other.lock == LOCK_SIZE) point2.other.lock = LOCK_DIR;
|
||||
} else {
|
||||
// just lines, keep it that way
|
||||
}
|
||||
if (point1.other.segment_after == SEGMENT_CURVE || point2.other.segment_before == SEGMENT_CURVE) {
|
||||
// try to preserve curve
|
||||
Vector2D before = point->delta_before;
|
||||
Vector2D after = point->delta_after;
|
||||
|
||||
// convert both segments to curves first
|
||||
if (point1.other.segment_after != SEGMENT_CURVE) {
|
||||
before = (point1.other.pos - point->pos) / 3.0;
|
||||
point1.other.delta_after = -before;
|
||||
point1.other.segment_after = SEGMENT_CURVE;
|
||||
}
|
||||
if (point2.other.segment_before != SEGMENT_CURVE) {
|
||||
after = (point2.other.pos - point->pos) / 3.0;
|
||||
point2.other.delta_before = -after;
|
||||
point2.other.segment_before = SEGMENT_CURVE;
|
||||
}
|
||||
|
||||
// The inverse of adding a point, reconstruct the original handles
|
||||
// before being subdivided using de Casteljau's algorithm
|
||||
// length of handles
|
||||
double bl = before.length() + 0.00000001; // prevent division by 0
|
||||
double al = after .length() + 0.00000001;
|
||||
double totl = bl + al;
|
||||
// set new handle sizes
|
||||
point1.other.delta_after *= totl / bl;
|
||||
point2.other.delta_before *= totl / al;
|
||||
|
||||
// Also take in acount cases where the point does not correspond to a freshly added point.
|
||||
// distance from the point to the curve as it would be in the above case can be used,
|
||||
// in the case of a point just added this distance = 0
|
||||
BezierCurve c(point1.other, point2.other);
|
||||
double t = bl / totl;
|
||||
Vector2D p = c.pointAt(t);
|
||||
Vector2D distP = point->pos - p;
|
||||
// adjust handle sizes
|
||||
point1.other.delta_after *= ssqrt(dot(distP, point1.other.delta_after) /point1.other.delta_after.lengthSqr()) + 1;
|
||||
point2.other.delta_before *= ssqrt(dot(distP, point2.other.delta_before)/point2.other.delta_before.lengthSqr()) + 1;
|
||||
|
||||
// unlock if needed
|
||||
if (point1.other.lock == LOCK_SIZE) point1.other.lock = LOCK_DIR;
|
||||
if (point2.other.lock == LOCK_SIZE) point2.other.lock = LOCK_DIR;
|
||||
} else {
|
||||
// just lines, keep it that way
|
||||
}
|
||||
}
|
||||
|
||||
void SinglePointRemoveAction::perform(bool to_undo) {
|
||||
if (to_undo) {
|
||||
// reinsert the point
|
||||
shape->points.insert(shape->points.begin() + position, point);
|
||||
} else {
|
||||
// remove the point
|
||||
shape->points.erase( shape->points.begin() + position);
|
||||
}
|
||||
// update points around removed point
|
||||
point1.perform();
|
||||
point2.perform();
|
||||
if (to_undo) {
|
||||
// reinsert the point
|
||||
shape->points.insert(shape->points.begin() + position, point);
|
||||
} else {
|
||||
// remove the point
|
||||
shape->points.erase( shape->points.begin() + position);
|
||||
}
|
||||
// update points around removed point
|
||||
point1.perform();
|
||||
point2.perform();
|
||||
}
|
||||
|
||||
DECLARE_POINTER_TYPE(SinglePointRemoveAction);
|
||||
@@ -383,50 +383,50 @@ DECLARE_TYPEOF_COLLECTION(SinglePointRemoveActionP);
|
||||
// Not all points mat be removed, at least two points must remain.
|
||||
class ControlPointRemoveAction : public Action {
|
||||
public:
|
||||
ControlPointRemoveAction(const SymbolShapeP& shape, const set<ControlPointP>& to_delete);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
ControlPointRemoveAction(const SymbolShapeP& shape, const set<ControlPointP>& to_delete);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
private:
|
||||
vector<SinglePointRemoveActionP> removals;
|
||||
vector<SinglePointRemoveActionP> removals;
|
||||
};
|
||||
|
||||
ControlPointRemoveAction::ControlPointRemoveAction(const SymbolShapeP& shape, const set<ControlPointP>& to_delete) {
|
||||
int index = 0;
|
||||
// find points to remove, in reverse order
|
||||
FOR_EACH(point, shape->points) {
|
||||
if (to_delete.find(point) != to_delete.end()) {
|
||||
// remove this point
|
||||
removals.push_back(intrusive(new SinglePointRemoveAction(shape, index)));
|
||||
}
|
||||
++index;
|
||||
}
|
||||
int index = 0;
|
||||
// find points to remove, in reverse order
|
||||
FOR_EACH(point, shape->points) {
|
||||
if (to_delete.find(point) != to_delete.end()) {
|
||||
// remove this point
|
||||
removals.push_back(intrusive(new SinglePointRemoveAction(shape, index)));
|
||||
}
|
||||
++index;
|
||||
}
|
||||
}
|
||||
|
||||
String ControlPointRemoveAction::getName(bool to_undo) const {
|
||||
return removals.size() == 1 ? _ACTION_("delete point") : _ACTION_("delete points");
|
||||
return removals.size() == 1 ? _ACTION_("delete point") : _ACTION_("delete points");
|
||||
}
|
||||
|
||||
void ControlPointRemoveAction::perform(bool to_undo) {
|
||||
if (to_undo) {
|
||||
FOR_EACH(r, removals) r->perform(to_undo);
|
||||
} else {
|
||||
// in reverse order, because positions of later points will
|
||||
// change after removal of earlier points.
|
||||
FOR_EACH_REVERSE(r, removals) r->perform(to_undo);
|
||||
}
|
||||
if (to_undo) {
|
||||
FOR_EACH(r, removals) r->perform(to_undo);
|
||||
} else {
|
||||
// in reverse order, because positions of later points will
|
||||
// change after removal of earlier points.
|
||||
FOR_EACH_REVERSE(r, removals) r->perform(to_undo);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Action* control_point_remove_action(const SymbolShapeP& shape, const set<ControlPointP>& to_delete) {
|
||||
if (shape->points.size() - to_delete.size() < 2) {
|
||||
// TODO : remove part?
|
||||
//intrusive(new ControlPointRemoveAllAction(part));
|
||||
return 0; // no action
|
||||
} else {
|
||||
return new ControlPointRemoveAction(shape, to_delete);
|
||||
}
|
||||
if (shape->points.size() - to_delete.size() < 2) {
|
||||
// TODO : remove part?
|
||||
//intrusive(new ControlPointRemoveAllAction(part));
|
||||
return 0; // no action
|
||||
} else {
|
||||
return new ControlPointRemoveAction(shape, to_delete);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -434,95 +434,95 @@ Action* control_point_remove_action(const SymbolShapeP& shape, const set<Control
|
||||
// ----------------------------------------------------------------------------- : Move symmetry center/handle
|
||||
|
||||
SymmetryMoveAction::SymmetryMoveAction(SymbolSymmetry& symmetry, bool is_handle)
|
||||
: symmetry(symmetry)
|
||||
, is_handle(is_handle)
|
||||
, original(is_handle ? symmetry.handle : symmetry.center)
|
||||
, constrain(false)
|
||||
, snap(0)
|
||||
: symmetry(symmetry)
|
||||
, is_handle(is_handle)
|
||||
, original(is_handle ? symmetry.handle : symmetry.center)
|
||||
, constrain(false)
|
||||
, snap(0)
|
||||
{}
|
||||
|
||||
String SymmetryMoveAction::getName(bool to_undo) const {
|
||||
return is_handle ? _ACTION_("move symmetry handle") : _ACTION_("move symmetry center");
|
||||
return is_handle ? _ACTION_("move symmetry handle") : _ACTION_("move symmetry center");
|
||||
}
|
||||
|
||||
void SymmetryMoveAction::perform(bool to_undo) {
|
||||
if (is_handle) {
|
||||
swap(symmetry.handle, original);
|
||||
} else {
|
||||
swap(symmetry.center, original);
|
||||
}
|
||||
if (is_handle) {
|
||||
swap(symmetry.handle, original);
|
||||
} else {
|
||||
swap(symmetry.center, original);
|
||||
}
|
||||
}
|
||||
|
||||
void SymmetryMoveAction::move(const Vector2D& deltaDelta) {
|
||||
delta += deltaDelta;
|
||||
if (is_handle) {
|
||||
symmetry.handle = snap_vector(symmetry.center + original + delta, snap) - symmetry.center;
|
||||
if (constrain) {
|
||||
// constrain to multiples of 2pi/24 i.e. 24 stops
|
||||
Radians angle = atan2(symmetry.handle.y, symmetry.handle.x);
|
||||
Radians mult = (2 * M_PI) / 24;
|
||||
angle = floor(angle / mult + 0.5) * mult;
|
||||
symmetry.handle = Vector2D(cos(angle), sin(angle)) * symmetry.handle.length();
|
||||
}
|
||||
} else {
|
||||
// Determine actual delta, possibly constrained and snapped
|
||||
symmetry.center = constrain_snap_vector(original, delta, constrain, snap);
|
||||
}
|
||||
delta += deltaDelta;
|
||||
if (is_handle) {
|
||||
symmetry.handle = snap_vector(symmetry.center + original + delta, snap) - symmetry.center;
|
||||
if (constrain) {
|
||||
// constrain to multiples of 2pi/24 i.e. 24 stops
|
||||
Radians angle = atan2(symmetry.handle.y, symmetry.handle.x);
|
||||
Radians mult = (2 * M_PI) / 24;
|
||||
angle = floor(angle / mult + 0.5) * mult;
|
||||
symmetry.handle = Vector2D(cos(angle), sin(angle)) * symmetry.handle.length();
|
||||
}
|
||||
} else {
|
||||
// Determine actual delta, possibly constrained and snapped
|
||||
symmetry.center = constrain_snap_vector(original, delta, constrain, snap);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Change symmetry kind
|
||||
|
||||
SymmetryTypeAction::SymmetryTypeAction(SymbolSymmetry& symmetry, SymbolSymmetryType type)
|
||||
: symmetry(symmetry), type(type)
|
||||
, old_name(symmetry.name)
|
||||
, copies(symmetry.copies)
|
||||
: symmetry(symmetry), type(type)
|
||||
, old_name(symmetry.name)
|
||||
, copies(symmetry.copies)
|
||||
{
|
||||
if (type == SYMMETRY_REFLECTION && symmetry.copies % 2 == 1) {
|
||||
// make sure it is a multiple of two
|
||||
copies = copies / 2 * 2;
|
||||
}
|
||||
// update name?
|
||||
if (old_name == symmetry.expectedName()) {
|
||||
swap(symmetry.kind, type);
|
||||
old_name = symmetry.expectedName();
|
||||
swap(symmetry.kind, type);
|
||||
}
|
||||
if (type == SYMMETRY_REFLECTION && symmetry.copies % 2 == 1) {
|
||||
// make sure it is a multiple of two
|
||||
copies = copies / 2 * 2;
|
||||
}
|
||||
// update name?
|
||||
if (old_name == symmetry.expectedName()) {
|
||||
swap(symmetry.kind, type);
|
||||
old_name = symmetry.expectedName();
|
||||
swap(symmetry.kind, type);
|
||||
}
|
||||
}
|
||||
|
||||
String SymmetryTypeAction::getName(bool to_undo) const {
|
||||
return _ACTION_("change symmetry type");
|
||||
return _ACTION_("change symmetry type");
|
||||
}
|
||||
|
||||
void SymmetryTypeAction::perform(bool to_undo) {
|
||||
swap(symmetry.kind, type);
|
||||
swap(symmetry.copies, copies);
|
||||
swap(symmetry.name, old_name);
|
||||
swap(symmetry.kind, type);
|
||||
swap(symmetry.copies, copies);
|
||||
swap(symmetry.name, old_name);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Change symmetry copies
|
||||
|
||||
SymmetryCopiesAction::SymmetryCopiesAction(SymbolSymmetry& symmetry, int copies_)
|
||||
: symmetry(symmetry), copies(copies_)
|
||||
, old_name(symmetry.name)
|
||||
: symmetry(symmetry), copies(copies_)
|
||||
, old_name(symmetry.name)
|
||||
{
|
||||
if (symmetry.kind == SYMMETRY_REFLECTION && copies % 2 == 1) {
|
||||
// make sure it is a multiple of two
|
||||
if (copies > symmetry.copies) copies++;
|
||||
else copies--;
|
||||
}
|
||||
// update name?
|
||||
if (old_name == symmetry.expectedName()) {
|
||||
swap(symmetry.copies, copies);
|
||||
old_name = symmetry.expectedName();
|
||||
swap(symmetry.copies, copies);
|
||||
}
|
||||
if (symmetry.kind == SYMMETRY_REFLECTION && copies % 2 == 1) {
|
||||
// make sure it is a multiple of two
|
||||
if (copies > symmetry.copies) copies++;
|
||||
else copies--;
|
||||
}
|
||||
// update name?
|
||||
if (old_name == symmetry.expectedName()) {
|
||||
swap(symmetry.copies, copies);
|
||||
old_name = symmetry.expectedName();
|
||||
swap(symmetry.copies, copies);
|
||||
}
|
||||
}
|
||||
|
||||
String SymmetryCopiesAction::getName(bool to_undo) const {
|
||||
return _ACTION_("change symmetry copies");
|
||||
return _ACTION_("change symmetry copies");
|
||||
}
|
||||
|
||||
void SymmetryCopiesAction::perform(bool to_undo) {
|
||||
swap(symmetry.copies, copies);
|
||||
swap(symmetry.name, old_name);
|
||||
swap(symmetry.copies, copies);
|
||||
swap(symmetry.name, old_name);
|
||||
}
|
||||
|
||||
@@ -45,21 +45,21 @@ Vector2D constrain_snap_vector_offset(const Vector2D& off1, const Vector2D& off2
|
||||
/// Moving a control point in a symbol
|
||||
class ControlPointMoveAction : public Action {
|
||||
public:
|
||||
ControlPointMoveAction(const set<ControlPointP>& points);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
/// Update this action to move some more
|
||||
void move(const Vector2D& delta);
|
||||
|
||||
ControlPointMoveAction(const set<ControlPointP>& points);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
/// Update this action to move some more
|
||||
void move(const Vector2D& delta);
|
||||
|
||||
private:
|
||||
set<ControlPointP> points; ///< Points to move
|
||||
vector<Vector2D> oldValues; ///< Their old positions
|
||||
Vector2D delta; ///< Amount we moved
|
||||
set<ControlPointP> points; ///< Points to move
|
||||
vector<Vector2D> oldValues; ///< Their old positions
|
||||
Vector2D delta; ///< Amount we moved
|
||||
public:
|
||||
bool constrain; ///< Constrain movement?
|
||||
int snap; ///< Snap to grid?
|
||||
bool constrain; ///< Constrain movement?
|
||||
int snap; ///< Snap to grid?
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Move handle
|
||||
@@ -67,22 +67,22 @@ class ControlPointMoveAction : public Action {
|
||||
/// Moving a handle(before/after) of a control point in a symbol
|
||||
class HandleMoveAction : public Action {
|
||||
public:
|
||||
HandleMoveAction(const SelectedHandle& handle);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
/// Update this action to move some more
|
||||
void move(const Vector2D& delta);
|
||||
|
||||
HandleMoveAction(const SelectedHandle& handle);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
/// Update this action to move some more
|
||||
void move(const Vector2D& delta);
|
||||
|
||||
private:
|
||||
SelectedHandle handle; ///< The handle to move
|
||||
Vector2D old_handle; ///< Old value of this handle
|
||||
Vector2D old_other; ///< Old value of other handle, needed for contraints
|
||||
Vector2D delta; ///< Amount we moved
|
||||
SelectedHandle handle; ///< The handle to move
|
||||
Vector2D old_handle; ///< Old value of this handle
|
||||
Vector2D old_other; ///< Old value of other handle, needed for contraints
|
||||
Vector2D delta; ///< Amount we moved
|
||||
public:
|
||||
bool constrain; ///< Constrain movement?
|
||||
int snap; ///< Snap to grid?
|
||||
bool constrain; ///< Constrain movement?
|
||||
int snap; ///< Snap to grid?
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Segment mode
|
||||
@@ -90,29 +90,29 @@ class HandleMoveAction : public Action {
|
||||
/// Utility class to update a control point
|
||||
class ControlPointUpdate {
|
||||
public:
|
||||
ControlPointUpdate(const ControlPointP& pnt);
|
||||
|
||||
/// Perform or undo an update on this control point
|
||||
void perform();
|
||||
|
||||
/// Other value that is swapped with the current one.
|
||||
/// Should be changed to make perform have an effect
|
||||
ControlPoint other;
|
||||
/// The point that is to be changed, should not be updated before perform()
|
||||
ControlPointP point;
|
||||
ControlPointUpdate(const ControlPointP& pnt);
|
||||
|
||||
/// Perform or undo an update on this control point
|
||||
void perform();
|
||||
|
||||
/// Other value that is swapped with the current one.
|
||||
/// Should be changed to make perform have an effect
|
||||
ControlPoint other;
|
||||
/// The point that is to be changed, should not be updated before perform()
|
||||
ControlPointP point;
|
||||
};
|
||||
|
||||
|
||||
/// Changing a line to a curve and vice versa
|
||||
class SegmentModeAction : public Action {
|
||||
public:
|
||||
SegmentModeAction(const ControlPointP& p1, const ControlPointP& p2, SegmentMode mode);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
SegmentModeAction(const ControlPointP& p1, const ControlPointP& p2, SegmentMode mode);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
protected:
|
||||
ControlPointUpdate point1, point2;
|
||||
ControlPointUpdate point1, point2;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Locking mode
|
||||
@@ -120,13 +120,13 @@ class SegmentModeAction : public Action {
|
||||
/// Locking a control point
|
||||
class LockModeAction : public Action {
|
||||
public:
|
||||
LockModeAction(const ControlPointP& p, LockMode mode);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
LockModeAction(const ControlPointP& p, LockMode mode);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
private:
|
||||
ControlPointUpdate point; ///< The affected point
|
||||
ControlPointUpdate point; ///< The affected point
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Move curve
|
||||
@@ -136,13 +136,13 @@ class LockModeAction : public Action {
|
||||
*/
|
||||
class CurveDragAction : public SegmentModeAction {
|
||||
public:
|
||||
CurveDragAction(const ControlPointP& point1, const ControlPointP& point2);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
// Move the curve by this much, it is grabbed at time t
|
||||
void move(const Vector2D& delta, double t);
|
||||
CurveDragAction(const ControlPointP& point1, const ControlPointP& point2);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
// Move the curve by this much, it is grabbed at time t
|
||||
void move(const Vector2D& delta, double t);
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Add control point
|
||||
@@ -150,19 +150,19 @@ class CurveDragAction : public SegmentModeAction {
|
||||
/// Insert a new point in a symbol shape
|
||||
class ControlPointAddAction : public Action {
|
||||
public:
|
||||
/// Insert a new point in shape, after position insertAfter_, at the time t on the segment
|
||||
ControlPointAddAction(const SymbolShapeP& shape, UInt insert_after, double t);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
inline ControlPointP getNewPoint() const { return new_point; }
|
||||
|
||||
/// Insert a new point in shape, after position insertAfter_, at the time t on the segment
|
||||
ControlPointAddAction(const SymbolShapeP& shape, UInt insert_after, double t);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
inline ControlPointP getNewPoint() const { return new_point; }
|
||||
|
||||
private:
|
||||
SymbolShapeP shape; ///< SymbolShape we are in
|
||||
ControlPointP new_point; ///< The point to insert
|
||||
UInt insert_after; ///< Insert after index .. in the array
|
||||
ControlPointUpdate point1, point2; ///< Update the points around the new point
|
||||
SymbolShapeP shape; ///< SymbolShape we are in
|
||||
ControlPointP new_point; ///< The point to insert
|
||||
UInt insert_after; ///< Insert after index .. in the array
|
||||
ControlPointUpdate point1, point2; ///< Update the points around the new point
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Remove control point
|
||||
@@ -179,22 +179,22 @@ Action* control_point_remove_action(const SymbolShapeP& shape, const set<Control
|
||||
/// Moving the handle or the center of a symbol symmetry
|
||||
class SymmetryMoveAction : public Action {
|
||||
public:
|
||||
SymmetryMoveAction(SymbolSymmetry& symmetry, bool is_handle);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
/// Update this action to move some more
|
||||
void move(const Vector2D& delta);
|
||||
|
||||
SymmetryMoveAction(SymbolSymmetry& symmetry, bool is_handle);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
/// Update this action to move some more
|
||||
void move(const Vector2D& delta);
|
||||
|
||||
private:
|
||||
SymbolSymmetry& symmetry; ///< Affected part
|
||||
bool is_handle; ///< Move the handle or the center?
|
||||
Vector2D delta; ///< Amount we moved
|
||||
Vector2D original; ///< Original value
|
||||
SymbolSymmetry& symmetry; ///< Affected part
|
||||
bool is_handle; ///< Move the handle or the center?
|
||||
Vector2D delta; ///< Amount we moved
|
||||
Vector2D original; ///< Original value
|
||||
public:
|
||||
bool constrain; ///< Constrain movement?
|
||||
int snap; ///< Snap to grid?
|
||||
bool constrain; ///< Constrain movement?
|
||||
int snap; ///< Snap to grid?
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Change symmetry kind
|
||||
@@ -202,15 +202,15 @@ class SymmetryMoveAction : public Action {
|
||||
/// Change the type of symmetry
|
||||
class SymmetryTypeAction : public Action {
|
||||
public:
|
||||
SymmetryTypeAction(SymbolSymmetry& symmetry, SymbolSymmetryType type);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
SymmetryTypeAction(SymbolSymmetry& symmetry, SymbolSymmetryType type);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
private:
|
||||
SymbolSymmetry& symmetry;
|
||||
SymbolSymmetryType type;
|
||||
String old_name;
|
||||
int copies; /// may be changed to make it a multiple of two
|
||||
SymbolSymmetry& symmetry;
|
||||
SymbolSymmetryType type;
|
||||
String old_name;
|
||||
int copies; /// may be changed to make it a multiple of two
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Change symmetry copies
|
||||
@@ -218,14 +218,14 @@ class SymmetryTypeAction : public Action {
|
||||
/// Change the number of copies of a symmetry
|
||||
class SymmetryCopiesAction : public Action {
|
||||
public:
|
||||
SymmetryCopiesAction(SymbolSymmetry& symmetry, int copies);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
SymmetryCopiesAction(SymbolSymmetry& symmetry, int copies);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
private:
|
||||
SymbolSymmetry& symmetry;
|
||||
int copies;
|
||||
String old_name;
|
||||
SymbolSymmetry& symmetry;
|
||||
int copies;
|
||||
String old_name;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
+136
-136
@@ -23,17 +23,17 @@
|
||||
// ----------------------------------------------------------------------------- : ValueAction
|
||||
|
||||
String ValueAction::getName(bool to_undo) const {
|
||||
return _ACTION_1_("change", valueP->fieldP->name);
|
||||
return _ACTION_1_("change", valueP->fieldP->name);
|
||||
}
|
||||
|
||||
void ValueAction::perform(bool to_undo) {
|
||||
if (card) {
|
||||
swap(const_cast<Card*>(card)->time_modified, old_time_modified);
|
||||
}
|
||||
if (card) {
|
||||
swap(const_cast<Card*>(card)->time_modified, old_time_modified);
|
||||
}
|
||||
}
|
||||
|
||||
void ValueAction::isOnCard(Card* card) {
|
||||
this->card = card;
|
||||
this->card = card;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Simple
|
||||
@@ -46,38 +46,38 @@ inline void swap_value(SymbolValue& a, SymbolValue ::ValueType& b
|
||||
inline void swap_value(TextValue& a, TextValue ::ValueType& b) { swap(a.value, b); a.last_update.update(); }
|
||||
inline void swap_value(PackageChoiceValue& a, PackageChoiceValue ::ValueType& b) { swap(a.package_name, b); }
|
||||
inline void swap_value(MultipleChoiceValue& a, MultipleChoiceValue::ValueType& b) {
|
||||
swap(a.value, b.value);
|
||||
swap(a.last_change, b.last_change);
|
||||
swap(a.value, b.value);
|
||||
swap(a.last_change, b.last_change);
|
||||
}
|
||||
|
||||
/// A ValueAction that swaps between old and new values
|
||||
template <typename T, bool ALLOW_MERGE>
|
||||
class SimpleValueAction : public ValueAction {
|
||||
public:
|
||||
inline SimpleValueAction(const intrusive_ptr<T>& value, const typename T::ValueType& new_value)
|
||||
: ValueAction(value), new_value(new_value)
|
||||
{}
|
||||
|
||||
virtual void perform(bool to_undo) {
|
||||
ValueAction::perform(to_undo);
|
||||
swap_value(static_cast<T&>(*valueP), new_value);
|
||||
valueP->onAction(*this, to_undo); // notify value
|
||||
}
|
||||
|
||||
virtual bool merge(const Action& action) {
|
||||
if (!ALLOW_MERGE) return false;
|
||||
TYPE_CASE(action, SimpleValueAction) {
|
||||
if (action.valueP == valueP) {
|
||||
// adjacent actions on the same value, discard the other one,
|
||||
// because it only keeps an intermediate value
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
inline SimpleValueAction(const intrusive_ptr<T>& value, const typename T::ValueType& new_value)
|
||||
: ValueAction(value), new_value(new_value)
|
||||
{}
|
||||
|
||||
virtual void perform(bool to_undo) {
|
||||
ValueAction::perform(to_undo);
|
||||
swap_value(static_cast<T&>(*valueP), new_value);
|
||||
valueP->onAction(*this, to_undo); // notify value
|
||||
}
|
||||
|
||||
virtual bool merge(const Action& action) {
|
||||
if (!ALLOW_MERGE) return false;
|
||||
TYPE_CASE(action, SimpleValueAction) {
|
||||
if (action.valueP == valueP) {
|
||||
// adjacent actions on the same value, discard the other one,
|
||||
// because it only keeps an intermediate value
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
typename T::ValueType new_value;
|
||||
typename T::ValueType new_value;
|
||||
};
|
||||
|
||||
ValueAction* value_action(const ChoiceValueP& value, const Defaultable<String>& new_value) { return new SimpleValueAction<ChoiceValue, true> (value, new_value); }
|
||||
@@ -86,171 +86,171 @@ ValueAction* value_action(const ImageValueP& value, const FileName&
|
||||
ValueAction* value_action(const SymbolValueP& value, const FileName& new_value) { return new SimpleValueAction<SymbolValue, false>(value, new_value); }
|
||||
ValueAction* value_action(const PackageChoiceValueP& value, const String& new_value) { return new SimpleValueAction<PackageChoiceValue, false>(value, new_value); }
|
||||
ValueAction* value_action(const MultipleChoiceValueP& value, const Defaultable<String>& new_value, const String& last_change) {
|
||||
MultipleChoiceValue::ValueType v = { new_value, last_change };
|
||||
return new SimpleValueAction<MultipleChoiceValue, false>(value, v);
|
||||
MultipleChoiceValue::ValueType v = { new_value, last_change };
|
||||
return new SimpleValueAction<MultipleChoiceValue, false>(value, v);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------- : Text
|
||||
|
||||
TextValueAction::TextValueAction(const TextValueP& value, size_t start, size_t end, size_t new_end, const Defaultable<String>& new_value, const String& name)
|
||||
: ValueAction(value)
|
||||
, selection_start(start), selection_end(end), new_selection_end(new_end)
|
||||
, new_value(new_value)
|
||||
, name(name)
|
||||
: ValueAction(value)
|
||||
, selection_start(start), selection_end(end), new_selection_end(new_end)
|
||||
, new_value(new_value)
|
||||
, name(name)
|
||||
{}
|
||||
|
||||
String TextValueAction::getName(bool to_undo) const { return name; }
|
||||
|
||||
void TextValueAction::perform(bool to_undo) {
|
||||
ValueAction::perform(to_undo);
|
||||
swap_value(value(), new_value);
|
||||
swap(selection_end, new_selection_end);
|
||||
valueP->onAction(*this, to_undo); // notify value
|
||||
ValueAction::perform(to_undo);
|
||||
swap_value(value(), new_value);
|
||||
swap(selection_end, new_selection_end);
|
||||
valueP->onAction(*this, to_undo); // notify value
|
||||
}
|
||||
|
||||
bool TextValueAction::merge(const Action& action) {
|
||||
TYPE_CASE(action, TextValueAction) {
|
||||
if (&action.value() == &value() && action.name == name) {
|
||||
if (action.selection_start == selection_end) {
|
||||
// adjacent edits, keep old value of this, it is older
|
||||
selection_end = action.selection_end;
|
||||
return true;
|
||||
} else if (action.new_selection_end == selection_start && name == _ACTION_("backspace")) {
|
||||
// adjacent backspaces
|
||||
selection_start = action.selection_start;
|
||||
selection_end = action.selection_end;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
TYPE_CASE(action, TextValueAction) {
|
||||
if (&action.value() == &value() && action.name == name) {
|
||||
if (action.selection_start == selection_end) {
|
||||
// adjacent edits, keep old value of this, it is older
|
||||
selection_end = action.selection_end;
|
||||
return true;
|
||||
} else if (action.new_selection_end == selection_start && name == _ACTION_("backspace")) {
|
||||
// adjacent backspaces
|
||||
selection_start = action.selection_start;
|
||||
selection_end = action.selection_end;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
TextValue& TextValueAction::value() const {
|
||||
return static_cast<TextValue&>(*valueP);
|
||||
return static_cast<TextValue&>(*valueP);
|
||||
}
|
||||
|
||||
|
||||
TextValueAction* toggle_format_action(const TextValueP& value, const String& tag, size_t start_i, size_t end_i, size_t start, size_t end, const String& action_name) {
|
||||
if (start > end) {
|
||||
swap(start, end);
|
||||
swap(start_i, end_i);
|
||||
}
|
||||
String new_value;
|
||||
const String& str = value->value();
|
||||
// Are we inside the tag we are toggling?
|
||||
if (!is_in_tag(str, _("<") + tag, start_i, end_i)) {
|
||||
// we are not inside this tag, add it
|
||||
new_value = str.substr(0, start_i);
|
||||
new_value += _("<") + tag + _(">");
|
||||
new_value += str.substr(start_i, end_i - start_i);
|
||||
new_value += _("</") + tag + _(">");
|
||||
new_value += str.substr(end_i);
|
||||
} else {
|
||||
// we are inside this tag, 'remove' it
|
||||
new_value = str.substr(0, start_i);
|
||||
new_value += _("</") + tag + _(">");
|
||||
new_value += str.substr(start_i, end_i - start_i);
|
||||
new_value += _("<") + tag + _(">");
|
||||
new_value += str.substr(end_i);
|
||||
}
|
||||
// Build action
|
||||
if (start != end) {
|
||||
// don't simplify if start == end, this way we insert <b></b>, allowing the
|
||||
// user to press Ctrl+B and start typing bold text
|
||||
new_value = simplify_tagged(new_value);
|
||||
}
|
||||
if (value->value() == new_value) {
|
||||
return nullptr; // no changes
|
||||
} else {
|
||||
return new TextValueAction(value, start, end, end, new_value, action_name);
|
||||
}
|
||||
if (start > end) {
|
||||
swap(start, end);
|
||||
swap(start_i, end_i);
|
||||
}
|
||||
String new_value;
|
||||
const String& str = value->value();
|
||||
// Are we inside the tag we are toggling?
|
||||
if (!is_in_tag(str, _("<") + tag, start_i, end_i)) {
|
||||
// we are not inside this tag, add it
|
||||
new_value = str.substr(0, start_i);
|
||||
new_value += _("<") + tag + _(">");
|
||||
new_value += str.substr(start_i, end_i - start_i);
|
||||
new_value += _("</") + tag + _(">");
|
||||
new_value += str.substr(end_i);
|
||||
} else {
|
||||
// we are inside this tag, 'remove' it
|
||||
new_value = str.substr(0, start_i);
|
||||
new_value += _("</") + tag + _(">");
|
||||
new_value += str.substr(start_i, end_i - start_i);
|
||||
new_value += _("<") + tag + _(">");
|
||||
new_value += str.substr(end_i);
|
||||
}
|
||||
// Build action
|
||||
if (start != end) {
|
||||
// don't simplify if start == end, this way we insert <b></b>, allowing the
|
||||
// user to press Ctrl+B and start typing bold text
|
||||
new_value = simplify_tagged(new_value);
|
||||
}
|
||||
if (value->value() == new_value) {
|
||||
return nullptr; // no changes
|
||||
} else {
|
||||
return new TextValueAction(value, start, end, end, new_value, action_name);
|
||||
}
|
||||
}
|
||||
|
||||
TextValueAction* typing_action(const TextValueP& value, size_t start_i, size_t end_i, size_t start, size_t end, const String& replacement, const String& action_name) {
|
||||
bool reverse = start > end;
|
||||
if (reverse) {
|
||||
swap(start, end);
|
||||
swap(start_i, end_i);
|
||||
}
|
||||
String new_value = tagged_substr_replace(value->value(), start_i, end_i, replacement);
|
||||
if (value->value() == new_value) {
|
||||
// no change
|
||||
return nullptr;
|
||||
} else {
|
||||
if (reverse) {
|
||||
return new TextValueAction(value, end, start, start+untag(replacement).size(), new_value, action_name);
|
||||
} else {
|
||||
return new TextValueAction(value, start, end, start+untag(replacement).size(), new_value, action_name);
|
||||
}
|
||||
}
|
||||
bool reverse = start > end;
|
||||
if (reverse) {
|
||||
swap(start, end);
|
||||
swap(start_i, end_i);
|
||||
}
|
||||
String new_value = tagged_substr_replace(value->value(), start_i, end_i, replacement);
|
||||
if (value->value() == new_value) {
|
||||
// no change
|
||||
return nullptr;
|
||||
} else {
|
||||
if (reverse) {
|
||||
return new TextValueAction(value, end, start, start+untag(replacement).size(), new_value, action_name);
|
||||
} else {
|
||||
return new TextValueAction(value, start, end, start+untag(replacement).size(), new_value, action_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Reminder text
|
||||
|
||||
TextToggleReminderAction::TextToggleReminderAction(const TextValueP& value, size_t pos_in)
|
||||
: ValueAction(value)
|
||||
: ValueAction(value)
|
||||
{
|
||||
pos = in_tag(value->value(), _("<kw-"), pos_in, pos_in);
|
||||
if (pos == String::npos) {
|
||||
throw InternalError(_("TextToggleReminderAction: not in <kw- tag"));
|
||||
}
|
||||
Char c = value->value().GetChar(pos + 4);
|
||||
enable = !(c == _('1') || c == _('A')); // if it was not enabled, then enable it
|
||||
old = enable ? _('1') : _('0');
|
||||
pos = in_tag(value->value(), _("<kw-"), pos_in, pos_in);
|
||||
if (pos == String::npos) {
|
||||
throw InternalError(_("TextToggleReminderAction: not in <kw- tag"));
|
||||
}
|
||||
Char c = value->value().GetChar(pos + 4);
|
||||
enable = !(c == _('1') || c == _('A')); // if it was not enabled, then enable it
|
||||
old = enable ? _('1') : _('0');
|
||||
}
|
||||
String TextToggleReminderAction::getName(bool to_undo) const {
|
||||
return enable ? _("Show reminder text") : _("Hide reminder text");
|
||||
return enable ? _("Show reminder text") : _("Hide reminder text");
|
||||
}
|
||||
|
||||
void TextToggleReminderAction::perform(bool to_undo) {
|
||||
ValueAction::perform(to_undo);
|
||||
TextValue& value = static_cast<TextValue&>(*valueP);
|
||||
String& val = value.value.mutate();
|
||||
assert(pos + 4 < val.size());
|
||||
size_t end = match_close_tag(val, pos);
|
||||
Char& c = val[pos + 4];
|
||||
swap(c, old);
|
||||
if (end != String::npos && end + 5 < val.size()) {
|
||||
val[end + 5] = c; // </kw-c>
|
||||
}
|
||||
value.last_update.update();
|
||||
value.onAction(*this, to_undo); // notify value
|
||||
ValueAction::perform(to_undo);
|
||||
TextValue& value = static_cast<TextValue&>(*valueP);
|
||||
String& val = value.value.mutate();
|
||||
assert(pos + 4 < val.size());
|
||||
size_t end = match_close_tag(val, pos);
|
||||
Char& c = val[pos + 4];
|
||||
swap(c, old);
|
||||
if (end != String::npos && end + 5 < val.size()) {
|
||||
val[end + 5] = c; // </kw-c>
|
||||
}
|
||||
value.last_update.update();
|
||||
value.onAction(*this, to_undo); // notify value
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------- : Event
|
||||
|
||||
String ScriptValueEvent::getName(bool) const {
|
||||
assert(false); // this action is just an event, getName shouldn't be called
|
||||
throw InternalError(_("ScriptValueEvent::getName"));
|
||||
assert(false); // this action is just an event, getName shouldn't be called
|
||||
throw InternalError(_("ScriptValueEvent::getName"));
|
||||
}
|
||||
void ScriptValueEvent::perform(bool) {
|
||||
assert(false); // this action is just an event, it should not be performed
|
||||
assert(false); // this action is just an event, it should not be performed
|
||||
}
|
||||
|
||||
|
||||
String ScriptStyleEvent::getName(bool) const {
|
||||
assert(false); // this action is just an event, getName shouldn't be called
|
||||
throw InternalError(_("ScriptStyleEvent::getName"));
|
||||
assert(false); // this action is just an event, getName shouldn't be called
|
||||
throw InternalError(_("ScriptStyleEvent::getName"));
|
||||
}
|
||||
void ScriptStyleEvent::perform(bool) {
|
||||
assert(false); // this action is just an event, it should not be performed
|
||||
assert(false); // this action is just an event, it should not be performed
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Action performer
|
||||
|
||||
ValueActionPerformer::ValueActionPerformer(const ValueP& value, Card* card, const SetP& set)
|
||||
: value(value), card(card), set(set)
|
||||
: value(value), card(card), set(set)
|
||||
{}
|
||||
ValueActionPerformer::~ValueActionPerformer() {}
|
||||
|
||||
void ValueActionPerformer::addAction(ValueAction* action) {
|
||||
action->isOnCard(card);
|
||||
set->actions.addAction(action);
|
||||
action->isOnCard(card);
|
||||
set->actions.addAction(action);
|
||||
}
|
||||
|
||||
Package& ValueActionPerformer::getLocalPackage() {
|
||||
return *set;
|
||||
return *set;
|
||||
}
|
||||
|
||||
+72
-72
@@ -36,20 +36,20 @@ DECLARE_POINTER_TYPE(PackageChoiceValue);
|
||||
/// An Action the changes a Value
|
||||
class ValueAction : public Action {
|
||||
public:
|
||||
inline ValueAction(const ValueP& value)
|
||||
: valueP(value), card(nullptr), old_time_modified(wxDateTime::Now())
|
||||
{}
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
/// We know that the value is on the given card, add that information
|
||||
void isOnCard(Card* card);
|
||||
|
||||
const ValueP valueP; ///< The modified value
|
||||
const Card* card; ///< The card the value is on, or null if it is not a card value
|
||||
inline ValueAction(const ValueP& value)
|
||||
: valueP(value), card(nullptr), old_time_modified(wxDateTime::Now())
|
||||
{}
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
/// We know that the value is on the given card, add that information
|
||||
void isOnCard(Card* card);
|
||||
|
||||
const ValueP valueP; ///< The modified value
|
||||
const Card* card; ///< The card the value is on, or null if it is not a card value
|
||||
private:
|
||||
wxDateTime old_time_modified;
|
||||
wxDateTime old_time_modified;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Simple
|
||||
@@ -67,22 +67,22 @@ ValueAction* value_action(const PackageChoiceValueP& value, const String&
|
||||
/// An action that changes a TextValue
|
||||
class TextValueAction : public ValueAction {
|
||||
public:
|
||||
TextValueAction(const TextValueP& value, size_t start, size_t end, size_t new_end, const Defaultable<String>& new_value, const String& name);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
virtual bool merge(const Action& action);
|
||||
|
||||
inline const String& newValue() const { return new_value(); }
|
||||
|
||||
/// The modified selection
|
||||
size_t selection_start, selection_end;
|
||||
TextValueAction(const TextValueP& value, size_t start, size_t end, size_t new_end, const Defaultable<String>& new_value, const String& name);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
virtual bool merge(const Action& action);
|
||||
|
||||
inline const String& newValue() const { return new_value(); }
|
||||
|
||||
/// The modified selection
|
||||
size_t selection_start, selection_end;
|
||||
private:
|
||||
inline TextValue& value() const;
|
||||
|
||||
size_t new_selection_end;
|
||||
Defaultable<String> new_value;
|
||||
String name;
|
||||
inline TextValue& value() const;
|
||||
|
||||
size_t new_selection_end;
|
||||
Defaultable<String> new_value;
|
||||
String name;
|
||||
};
|
||||
|
||||
/// Action for toggling some formating tag on or off in some range
|
||||
@@ -97,15 +97,15 @@ TextValueAction* typing_action(const TextValueP& value, size_t start_i, size_t e
|
||||
/// Toggle reminder text for a keyword on or off
|
||||
class TextToggleReminderAction : public ValueAction {
|
||||
public:
|
||||
TextToggleReminderAction(const TextValueP& value, size_t pos);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
private:
|
||||
size_t pos; ///< Position of "<kw-"
|
||||
bool enable; ///< Should the reminder text be turned on or off?
|
||||
Char old; ///< Old value of the <kw- tag
|
||||
TextToggleReminderAction(const TextValueP& value, size_t pos);
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
private:
|
||||
size_t pos; ///< Position of "<kw-"
|
||||
bool enable; ///< Should the reminder text be turned on or off?
|
||||
Char old; ///< Old value of the <kw- tag
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Replace all
|
||||
@@ -113,22 +113,22 @@ class TextToggleReminderAction : public ValueAction {
|
||||
/// A TextValueAction without the start and end stuff
|
||||
class SimpleTextValueAction : public ValueAction {
|
||||
public:
|
||||
SimpleTextValueAction(const Card* card, const TextValueP& value, const Defaultable<String>& new_value);
|
||||
virtual void perform(bool to_undo);
|
||||
bool merge(const SimpleTextValueAction& action);
|
||||
SimpleTextValueAction(const Card* card, const TextValueP& value, const Defaultable<String>& new_value);
|
||||
virtual void perform(bool to_undo);
|
||||
bool merge(const SimpleTextValueAction& action);
|
||||
private:
|
||||
Defaultable<String> new_value;
|
||||
Defaultable<String> new_value;
|
||||
};
|
||||
|
||||
/// An action from "Replace All"; just a bunch of value actions performed in sequence
|
||||
class ReplaceAllAction : public Action {
|
||||
public:
|
||||
~ReplaceAllAction();
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
vector<SimpleTextValueAction> actions;
|
||||
~ReplaceAllAction();
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
vector<SimpleTextValueAction> actions;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Event
|
||||
@@ -136,27 +136,27 @@ class ReplaceAllAction : public Action {
|
||||
/// Notification that a script caused a value to change
|
||||
class ScriptValueEvent : public Action {
|
||||
public:
|
||||
inline ScriptValueEvent(const Card* card, const Value* value) : card(card), value(value) {}
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
const Card* card; ///< Card the value is on
|
||||
const Value* value; ///< The modified value
|
||||
inline ScriptValueEvent(const Card* card, const Value* value) : card(card), value(value) {}
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
const Card* card; ///< Card the value is on
|
||||
const Value* value; ///< The modified value
|
||||
};
|
||||
|
||||
/// Notification that a script caused a style to change
|
||||
class ScriptStyleEvent : public Action {
|
||||
public:
|
||||
inline ScriptStyleEvent(const StyleSheet* stylesheet, const Style* style)
|
||||
: stylesheet(stylesheet), style(style)
|
||||
{}
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
const StyleSheet* stylesheet; ///< StyleSheet the style is for
|
||||
const Style* style; ///< The modified style
|
||||
inline ScriptStyleEvent(const StyleSheet* stylesheet, const Style* style)
|
||||
: stylesheet(stylesheet), style(style)
|
||||
{}
|
||||
|
||||
virtual String getName(bool to_undo) const;
|
||||
virtual void perform(bool to_undo);
|
||||
|
||||
const StyleSheet* stylesheet; ///< StyleSheet the style is for
|
||||
const Style* style; ///< The modified style
|
||||
};
|
||||
|
||||
|
||||
@@ -166,16 +166,16 @@ class ScriptStyleEvent : public Action {
|
||||
/** Used to reduce coupling */
|
||||
class ValueActionPerformer {
|
||||
public:
|
||||
ValueActionPerformer(const ValueP& value, Card* card, const SetP& set);
|
||||
~ValueActionPerformer();
|
||||
/// Perform an action. The performer takes ownerwhip of the action.
|
||||
void addAction(ValueAction* action);
|
||||
|
||||
const ValueP value; ///< The value
|
||||
Package& getLocalPackage();
|
||||
ValueActionPerformer(const ValueP& value, Card* card, const SetP& set);
|
||||
~ValueActionPerformer();
|
||||
/// Perform an action. The performer takes ownerwhip of the action.
|
||||
void addAction(ValueAction* action);
|
||||
|
||||
const ValueP value; ///< The value
|
||||
Package& getLocalPackage();
|
||||
private:
|
||||
Card* card; ///< Card the value is on (if any)
|
||||
SetP set; ///< Set for the actions
|
||||
Card* card; ///< Card the value is on (if any)
|
||||
SetP set; ///< Set for the actions
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
@@ -16,37 +16,37 @@
|
||||
// ----------------------------------------------------------------------------- : AddCardsScript
|
||||
|
||||
IMPLEMENT_REFLECTION_NO_SCRIPT(AddCardsScript) {
|
||||
REFLECT(name);
|
||||
REFLECT(description);
|
||||
REFLECT(enabled);
|
||||
REFLECT(script);
|
||||
REFLECT(name);
|
||||
REFLECT(description);
|
||||
REFLECT(enabled);
|
||||
REFLECT(script);
|
||||
}
|
||||
|
||||
|
||||
void AddCardsScript::perform(Set& set, vector<CardP>& out) {
|
||||
// Perform script
|
||||
Context& ctx = set.getContext();
|
||||
ScriptValueP result = script.invoke(ctx);
|
||||
// Add cards to out
|
||||
ScriptValueP it = result->makeIterator(result);
|
||||
while (ScriptValueP item = it->next()) {
|
||||
CardP card = from_script<CardP>(item);
|
||||
// is this a new card?
|
||||
if (contains(set.cards,card) || contains(out,card)) {
|
||||
// make copy
|
||||
card = intrusive(new Card(*card));
|
||||
}
|
||||
out.push_back(card);
|
||||
}
|
||||
// Perform script
|
||||
Context& ctx = set.getContext();
|
||||
ScriptValueP result = script.invoke(ctx);
|
||||
// Add cards to out
|
||||
ScriptValueP it = result->makeIterator(result);
|
||||
while (ScriptValueP item = it->next()) {
|
||||
CardP card = from_script<CardP>(item);
|
||||
// is this a new card?
|
||||
if (contains(set.cards,card) || contains(out,card)) {
|
||||
// make copy
|
||||
card = intrusive(new Card(*card));
|
||||
}
|
||||
out.push_back(card);
|
||||
}
|
||||
}
|
||||
|
||||
void AddCardsScript::perform(Set& set) {
|
||||
// Perform script
|
||||
vector<CardP> cards;
|
||||
perform(set,cards);
|
||||
// Add to set
|
||||
if (!cards.empty()) {
|
||||
// TODO: change the name of the action somehow
|
||||
set.actions.addAction(new AddCardAction(ADD, set, cards));
|
||||
}
|
||||
// Perform script
|
||||
vector<CardP> cards;
|
||||
perform(set,cards);
|
||||
// Add to set
|
||||
if (!cards.empty()) {
|
||||
// TODO: change the name of the action somehow
|
||||
set.actions.addAction(new AddCardAction(ADD, set, cards));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,17 +20,17 @@ DECLARE_POINTER_TYPE(Card);
|
||||
/// A script to add one or more cards to a set
|
||||
class AddCardsScript : public IntrusivePtrBase<AddCardsScript> {
|
||||
public:
|
||||
String name;
|
||||
String description;
|
||||
Scriptable<bool> enabled;
|
||||
OptionalScript script;
|
||||
|
||||
/// Perform the script; return the cards (if any)
|
||||
void perform(Set& set, vector<CardP>& out);
|
||||
/// Perform the script; add cards to the set
|
||||
void perform(Set& set);
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
String name;
|
||||
String description;
|
||||
Scriptable<bool> enabled;
|
||||
OptionalScript script;
|
||||
|
||||
/// Perform the script; return the cards (if any)
|
||||
void perform(Set& set, vector<CardP>& out);
|
||||
/// Perform the script; add cards to the set
|
||||
void perform(Set& set);
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
|
||||
|
||||
+49
-49
@@ -21,73 +21,73 @@ DECLARE_TYPEOF_NO_REV(IndexMap<FieldP COMMA ValueP>);
|
||||
// ----------------------------------------------------------------------------- : Card
|
||||
|
||||
Card::Card()
|
||||
// for files made before we saved these times, set the time to 'yesterday'
|
||||
: time_created (wxDateTime::Now().Subtract(wxDateSpan::Day()).ResetTime())
|
||||
, time_modified(wxDateTime::Now().Subtract(wxDateSpan::Day()).ResetTime())
|
||||
, has_styling(false)
|
||||
// for files made before we saved these times, set the time to 'yesterday'
|
||||
: time_created (wxDateTime::Now().Subtract(wxDateSpan::Day()).ResetTime())
|
||||
, time_modified(wxDateTime::Now().Subtract(wxDateSpan::Day()).ResetTime())
|
||||
, has_styling(false)
|
||||
{
|
||||
if (!game_for_reading()) {
|
||||
throw InternalError(_("game_for_reading not set"));
|
||||
}
|
||||
data.init(game_for_reading()->card_fields);
|
||||
if (!game_for_reading()) {
|
||||
throw InternalError(_("game_for_reading not set"));
|
||||
}
|
||||
data.init(game_for_reading()->card_fields);
|
||||
}
|
||||
|
||||
Card::Card(const Game& game)
|
||||
: time_created (wxDateTime::Now())
|
||||
, time_modified(wxDateTime::Now())
|
||||
, has_styling(false)
|
||||
: time_created (wxDateTime::Now())
|
||||
, time_modified(wxDateTime::Now())
|
||||
, has_styling(false)
|
||||
{
|
||||
data.init(game.card_fields);
|
||||
data.init(game.card_fields);
|
||||
}
|
||||
|
||||
String Card::identification() const {
|
||||
// an identifying field
|
||||
FOR_EACH_CONST(v, data) {
|
||||
if (v->fieldP->identifying) {
|
||||
return v->toString();
|
||||
}
|
||||
}
|
||||
// otherwise the first field
|
||||
if (!data.empty()) {
|
||||
return data.at(0)->toString();
|
||||
} else {
|
||||
return wxEmptyString;
|
||||
}
|
||||
// an identifying field
|
||||
FOR_EACH_CONST(v, data) {
|
||||
if (v->fieldP->identifying) {
|
||||
return v->toString();
|
||||
}
|
||||
}
|
||||
// otherwise the first field
|
||||
if (!data.empty()) {
|
||||
return data.at(0)->toString();
|
||||
} else {
|
||||
return wxEmptyString;
|
||||
}
|
||||
}
|
||||
|
||||
bool Card::contains(String const& query) const {
|
||||
FOR_EACH_CONST(v, data) {
|
||||
if (find_i(v->toString(),query) != String::npos) return true;
|
||||
}
|
||||
if (find_i(notes,query) != String::npos) return true;
|
||||
return false;
|
||||
FOR_EACH_CONST(v, data) {
|
||||
if (find_i(v->toString(),query) != String::npos) return true;
|
||||
}
|
||||
if (find_i(notes,query) != String::npos) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void mark_dependency_member(const Card& card, const String& name, const Dependency& dep) {
|
||||
mark_dependency_member(card.data, name, dep);
|
||||
mark_dependency_member(card.data, name, dep);
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION(Card) {
|
||||
REFLECT(stylesheet);
|
||||
REFLECT(has_styling);
|
||||
if (has_styling) {
|
||||
if (stylesheet) {
|
||||
REFLECT_IF_READING styling_data.init(stylesheet->styling_fields);
|
||||
REFLECT(styling_data);
|
||||
} else if (stylesheet_for_reading()) {
|
||||
REFLECT_IF_READING styling_data.init(stylesheet_for_reading()->styling_fields);
|
||||
REFLECT(styling_data);
|
||||
} else if (tag.reading()) {
|
||||
has_styling = false; // We don't know the style, this can be because of copy/pasting
|
||||
}
|
||||
}
|
||||
REFLECT(notes);
|
||||
REFLECT(time_created);
|
||||
REFLECT(time_modified);
|
||||
REFLECT(extra_data); // don't allow scripts to depend on style specific data
|
||||
REFLECT_NAMELESS(data);
|
||||
REFLECT(stylesheet);
|
||||
REFLECT(has_styling);
|
||||
if (has_styling) {
|
||||
if (stylesheet) {
|
||||
REFLECT_IF_READING styling_data.init(stylesheet->styling_fields);
|
||||
REFLECT(styling_data);
|
||||
} else if (stylesheet_for_reading()) {
|
||||
REFLECT_IF_READING styling_data.init(stylesheet_for_reading()->styling_fields);
|
||||
REFLECT(styling_data);
|
||||
} else if (tag.reading()) {
|
||||
has_styling = false; // We don't know the style, this can be because of copy/pasting
|
||||
}
|
||||
}
|
||||
REFLECT(notes);
|
||||
REFLECT(time_created);
|
||||
REFLECT(time_modified);
|
||||
REFLECT(extra_data); // don't allow scripts to depend on style specific data
|
||||
REFLECT_NAMELESS(data);
|
||||
}
|
||||
|
||||
+64
-64
@@ -27,75 +27,75 @@ DECLARE_POINTER_TYPE(StyleSheet);
|
||||
/// A card from a card Set
|
||||
class Card : public IntrusivePtrVirtualBase {
|
||||
public:
|
||||
/// Default constructor, uses game_for_new_cards to make the game
|
||||
Card();
|
||||
/// Creates a card using the given game
|
||||
Card(const Game& game);
|
||||
|
||||
/// The values on the fields of the card.
|
||||
/** The indices should correspond to the card_fields in the Game */
|
||||
IndexMap<FieldP, ValueP> data;
|
||||
/// Notes for this card
|
||||
String notes;
|
||||
/// Time the card was created/last modified
|
||||
wxDateTime time_created, time_modified;
|
||||
/// Alternative style to use for this card
|
||||
/** Optional; if not set use the card style from the set */
|
||||
StyleSheetP stylesheet;
|
||||
/// Alternative options to use for this card, for this card's stylesheet
|
||||
/** Optional; if not set use the styling data from the set.
|
||||
* If stylesheet is set then contains data for the this->stylesheet, otherwise for set->stylesheet
|
||||
*/
|
||||
IndexMap<FieldP,ValueP> styling_data;
|
||||
/// Is the styling_data set?
|
||||
bool has_styling;
|
||||
|
||||
/// Extra values for specitic stylesheets, indexed by stylesheet name
|
||||
DelayedIndexMaps<FieldP,ValueP> extra_data;
|
||||
/// Styling information for a particular stylesheet
|
||||
IndexMap<FieldP, ValueP>& extraDataFor(const StyleSheet& stylesheet);
|
||||
|
||||
/// Keyword usage statistics
|
||||
vector<pair<Value*,const Keyword*> > keyword_usage;
|
||||
|
||||
/// 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;
|
||||
/// Does this card contain each of the words in the query string?
|
||||
bool contains_words(String const& query) const;
|
||||
|
||||
/// Find a value in the data by name and type
|
||||
template <typename T> T& value(const String& name) {
|
||||
for(IndexMap<FieldP, ValueP>::iterator it = data.begin() ; it != data.end() ; ++it) {
|
||||
if ((*it)->fieldP->name == name) {
|
||||
T* ret = dynamic_cast<T*>(it->get());
|
||||
if (!ret) throw InternalError(_("Card field with name '")+name+_("' doesn't have the right type"));
|
||||
return *ret;
|
||||
}
|
||||
}
|
||||
throw InternalError(_("Expected a card field with name '")+name+_("'"));
|
||||
}
|
||||
template <typename T> const T& value(const String& name) const {
|
||||
for(IndexMap<FieldP, ValueP>::const_iterator it = data.begin() ; it != data.end() ; ++it) {
|
||||
if ((*it)->fieldP->name == name) {
|
||||
const T* ret = dynamic_cast<const T*>(it->get());
|
||||
if (!ret) throw InternalError(_("Card field with name '")+name+_("' doesn't have the right type"));
|
||||
return *ret;
|
||||
}
|
||||
}
|
||||
throw InternalError(_("Expected a card field with name '")+name+_("'"));
|
||||
}
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
/// Default constructor, uses game_for_new_cards to make the game
|
||||
Card();
|
||||
/// Creates a card using the given game
|
||||
Card(const Game& game);
|
||||
|
||||
/// The values on the fields of the card.
|
||||
/** The indices should correspond to the card_fields in the Game */
|
||||
IndexMap<FieldP, ValueP> data;
|
||||
/// Notes for this card
|
||||
String notes;
|
||||
/// Time the card was created/last modified
|
||||
wxDateTime time_created, time_modified;
|
||||
/// Alternative style to use for this card
|
||||
/** Optional; if not set use the card style from the set */
|
||||
StyleSheetP stylesheet;
|
||||
/// Alternative options to use for this card, for this card's stylesheet
|
||||
/** Optional; if not set use the styling data from the set.
|
||||
* If stylesheet is set then contains data for the this->stylesheet, otherwise for set->stylesheet
|
||||
*/
|
||||
IndexMap<FieldP,ValueP> styling_data;
|
||||
/// Is the styling_data set?
|
||||
bool has_styling;
|
||||
|
||||
/// Extra values for specitic stylesheets, indexed by stylesheet name
|
||||
DelayedIndexMaps<FieldP,ValueP> extra_data;
|
||||
/// Styling information for a particular stylesheet
|
||||
IndexMap<FieldP, ValueP>& extraDataFor(const StyleSheet& stylesheet);
|
||||
|
||||
/// Keyword usage statistics
|
||||
vector<pair<Value*,const Keyword*> > keyword_usage;
|
||||
|
||||
/// 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;
|
||||
/// Does this card contain each of the words in the query string?
|
||||
bool contains_words(String const& query) const;
|
||||
|
||||
/// Find a value in the data by name and type
|
||||
template <typename T> T& value(const String& name) {
|
||||
for(IndexMap<FieldP, ValueP>::iterator it = data.begin() ; it != data.end() ; ++it) {
|
||||
if ((*it)->fieldP->name == name) {
|
||||
T* ret = dynamic_cast<T*>(it->get());
|
||||
if (!ret) throw InternalError(_("Card field with name '")+name+_("' doesn't have the right type"));
|
||||
return *ret;
|
||||
}
|
||||
}
|
||||
throw InternalError(_("Expected a card field with name '")+name+_("'"));
|
||||
}
|
||||
template <typename T> const T& value(const String& name) const {
|
||||
for(IndexMap<FieldP, ValueP>::const_iterator it = data.begin() ; it != data.end() ; ++it) {
|
||||
if ((*it)->fieldP->name == name) {
|
||||
const T* ret = dynamic_cast<const T*>(it->get());
|
||||
if (!ret) throw InternalError(_("Card field with name '")+name+_("' doesn't have the right type"));
|
||||
return *ret;
|
||||
}
|
||||
}
|
||||
throw InternalError(_("Expected a card field with name '")+name+_("'"));
|
||||
}
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
inline String type_name(const Card&) {
|
||||
return _TYPE_("card");
|
||||
return _TYPE_("card");
|
||||
}
|
||||
inline String type_name(const vector<CardP>&) {
|
||||
return _TYPE_("cards"); // not actually used, only for locale.pl script
|
||||
return _TYPE_("cards"); // not actually used, only for locale.pl script
|
||||
}
|
||||
|
||||
void mark_dependency_member(const Card& value, const String& name, const Dependency& dep);
|
||||
|
||||
@@ -15,14 +15,14 @@
|
||||
|
||||
/// What should be drawn?
|
||||
enum DrawWhat
|
||||
{ DRAW_NOTHING = 0x00
|
||||
, DRAW_NORMAL = 0x01 // draw normal things, like the text
|
||||
, DRAW_BORDERS = 0x10 // draw editor stuff, such as borders/lines, can be disabled.
|
||||
, DRAW_BOXES = 0x20 // draw editor stuff, such as borders/lines, can be disabled.
|
||||
, DRAW_EDITING = 0x40 // draw other editor stuff, can be disabled.
|
||||
, DRAW_ERRORS = 0x80 // draw error indicators, can't be disabled
|
||||
, DRAW_ACTIVE = 0x100 // draw active editor stuff, such as hidden separators and atom highlights
|
||||
, DRAW_NATIVELOOK = 0x200 // use a native look
|
||||
{ DRAW_NOTHING = 0x00
|
||||
, DRAW_NORMAL = 0x01 // draw normal things, like the text
|
||||
, DRAW_BORDERS = 0x10 // draw editor stuff, such as borders/lines, can be disabled.
|
||||
, DRAW_BOXES = 0x20 // draw editor stuff, such as borders/lines, can be disabled.
|
||||
, DRAW_EDITING = 0x40 // draw other editor stuff, can be disabled.
|
||||
, DRAW_ERRORS = 0x80 // draw error indicators, can't be disabled
|
||||
, DRAW_ACTIVE = 0x100 // draw active editor stuff, such as hidden separators and atom highlights
|
||||
, DRAW_NATIVELOOK = 0x200 // use a native look
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
// ----------------------------------------------------------------------------- : Export template, basics
|
||||
|
||||
ExportTemplate::ExportTemplate()
|
||||
: file_type(_("HTML files (*.html)|*.html"))
|
||||
, create_directory(false)
|
||||
: file_type(_("HTML files (*.html)|*.html"))
|
||||
, create_directory(false)
|
||||
{}
|
||||
|
||||
String ExportTemplate::typeNameStatic() { return _("export-template"); }
|
||||
@@ -24,23 +24,23 @@ String ExportTemplate::typeName() const { return _("export-template"); }
|
||||
Version ExportTemplate::fileVersion() const { return file_version_export_template; }
|
||||
|
||||
void ExportTemplate::validate(Version) {
|
||||
if (!game) {
|
||||
throw Error(_ERROR_1_("no game specified",_TYPE_("export template")));
|
||||
}
|
||||
// an export template depends on the game it is made for
|
||||
requireDependency(game.get());
|
||||
if (!game) {
|
||||
throw Error(_ERROR_1_("no game specified",_TYPE_("export template")));
|
||||
}
|
||||
// an export template depends on the game it is made for
|
||||
requireDependency(game.get());
|
||||
}
|
||||
|
||||
|
||||
IMPLEMENT_REFLECTION(ExportTemplate) {
|
||||
REFLECT_BASE(Packaged);
|
||||
REFLECT(game);
|
||||
REFLECT(file_type);
|
||||
REFLECT(create_directory);
|
||||
REFLECT(option_fields);
|
||||
REFLECT_IF_READING option_style.init(option_fields);
|
||||
REFLECT(option_style);
|
||||
REFLECT(script);
|
||||
REFLECT_BASE(Packaged);
|
||||
REFLECT(game);
|
||||
REFLECT(file_type);
|
||||
REFLECT(create_directory);
|
||||
REFLECT(option_fields);
|
||||
REFLECT_IF_READING option_style.init(option_fields);
|
||||
REFLECT(option_style);
|
||||
REFLECT(script);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : ExportInfo
|
||||
|
||||
@@ -25,37 +25,37 @@ DECLARE_POINTER_TYPE(Package);
|
||||
/// A template for exporting sets to HTML or text format
|
||||
class ExportTemplate : public Packaged {
|
||||
public:
|
||||
ExportTemplate();
|
||||
|
||||
GameP game; ///< Game this template is for
|
||||
String file_type; ///< Type of the created file, in "name|*.ext" format
|
||||
bool create_directory; ///< The export creates a directory for additional data files
|
||||
vector<FieldP> option_fields; ///< Options for exporting
|
||||
IndexMap<FieldP,StyleP> option_style; ///< Style of the options
|
||||
OptionalScript script; ///< Export script, for multi file templates and initialization
|
||||
|
||||
static String typeNameStatic();
|
||||
virtual String typeName() const;
|
||||
Version fileVersion() const;
|
||||
virtual void validate(Version = app_version);
|
||||
ExportTemplate();
|
||||
|
||||
GameP game; ///< Game this template is for
|
||||
String file_type; ///< Type of the created file, in "name|*.ext" format
|
||||
bool create_directory; ///< The export creates a directory for additional data files
|
||||
vector<FieldP> option_fields; ///< Options for exporting
|
||||
IndexMap<FieldP,StyleP> option_style; ///< Style of the options
|
||||
OptionalScript script; ///< Export script, for multi file templates and initialization
|
||||
|
||||
static String typeNameStatic();
|
||||
virtual String typeName() const;
|
||||
Version fileVersion() const;
|
||||
virtual void validate(Version = app_version);
|
||||
private:
|
||||
DECLARE_REFLECTION();
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : ExportInfo
|
||||
|
||||
/// Information that can be used by export functions
|
||||
struct ExportInfo {
|
||||
ExportInfo();
|
||||
|
||||
SetP set; ///< The set that is being exported
|
||||
PackageP export_template; ///< The export template used
|
||||
/// When using the CLI, this can be a fake package to allow reading from the cwd
|
||||
String directory_relative; ///< The directory for storing extra files (or "" if !export->create_directory)
|
||||
/// This is just the directory name
|
||||
String directory_absolute; ///< The absolute path of the directory
|
||||
map<String,wxSize> exported_images; ///< Images (from symbol font) already exported, and their size
|
||||
bool allow_writes_outside; ///< Can files outside the directory be written to?
|
||||
ExportInfo();
|
||||
|
||||
SetP set; ///< The set that is being exported
|
||||
PackageP export_template; ///< The export template used
|
||||
/// When using the CLI, this can be a fake package to allow reading from the cwd
|
||||
String directory_relative; ///< The directory for storing extra files (or "" if !export->create_directory)
|
||||
/// This is just the directory name
|
||||
String directory_absolute; ///< The absolute path of the directory
|
||||
map<String,wxSize> exported_images; ///< Images (from symbol font) already exported, and their size
|
||||
bool allow_writes_outside; ///< Can files outside the directory be written to?
|
||||
};
|
||||
|
||||
DECLARE_DYNAMIC_ARG(ExportInfo*, export_info);
|
||||
|
||||
+180
-180
@@ -24,239 +24,239 @@ DECLARE_TYPEOF_COLLECTION(StyleListener*);
|
||||
// ----------------------------------------------------------------------------- : Field
|
||||
|
||||
Field::Field()
|
||||
: index (0) // sensible default?
|
||||
, editable (true)
|
||||
, save_value (true)
|
||||
, show_statistics (true)
|
||||
, position_hint (0)
|
||||
, identifying (false)
|
||||
, card_list_column (100)
|
||||
, card_list_width (100)
|
||||
, card_list_visible(false)
|
||||
, card_list_allow (true)
|
||||
, card_list_align (ALIGN_LEFT)
|
||||
, tab_index (0)
|
||||
: index (0) // sensible default?
|
||||
, editable (true)
|
||||
, save_value (true)
|
||||
, show_statistics (true)
|
||||
, position_hint (0)
|
||||
, identifying (false)
|
||||
, card_list_column (100)
|
||||
, card_list_width (100)
|
||||
, card_list_visible(false)
|
||||
, card_list_allow (true)
|
||||
, card_list_align (ALIGN_LEFT)
|
||||
, tab_index (0)
|
||||
{}
|
||||
|
||||
Field::~Field() {}
|
||||
|
||||
void Field::initDependencies(Context& ctx, const Dependency& dep) const {
|
||||
sort_script.initDependencies(ctx, dep);
|
||||
sort_script.initDependencies(ctx, dep);
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION(Field) {
|
||||
REFLECT_IF_NOT_READING {
|
||||
String type = typeName();
|
||||
REFLECT(type);
|
||||
}
|
||||
REFLECT(name);
|
||||
REFLECT_IF_READING name = canonical_name_form(name);
|
||||
REFLECT(description);
|
||||
REFLECT_N("icon", icon_filename);
|
||||
REFLECT(editable);
|
||||
REFLECT(save_value);
|
||||
REFLECT(show_statistics);
|
||||
REFLECT(position_hint);
|
||||
REFLECT(identifying);
|
||||
REFLECT(card_list_column);
|
||||
REFLECT(card_list_width);
|
||||
REFLECT(card_list_visible);
|
||||
REFLECT(card_list_allow);
|
||||
REFLECT(card_list_name);
|
||||
REFLECT(sort_script);
|
||||
REFLECT_IF_READING if(card_list_name.empty()) card_list_name = name;
|
||||
REFLECT_N("card_list_alignment", card_list_align);
|
||||
REFLECT(tab_index);
|
||||
REFLECT_IF_NOT_READING {
|
||||
String type = typeName();
|
||||
REFLECT(type);
|
||||
}
|
||||
REFLECT(name);
|
||||
REFLECT_IF_READING name = canonical_name_form(name);
|
||||
REFLECT(description);
|
||||
REFLECT_N("icon", icon_filename);
|
||||
REFLECT(editable);
|
||||
REFLECT(save_value);
|
||||
REFLECT(show_statistics);
|
||||
REFLECT(position_hint);
|
||||
REFLECT(identifying);
|
||||
REFLECT(card_list_column);
|
||||
REFLECT(card_list_width);
|
||||
REFLECT(card_list_visible);
|
||||
REFLECT(card_list_allow);
|
||||
REFLECT(card_list_name);
|
||||
REFLECT(sort_script);
|
||||
REFLECT_IF_READING if(card_list_name.empty()) card_list_name = name;
|
||||
REFLECT_N("card_list_alignment", card_list_align);
|
||||
REFLECT(tab_index);
|
||||
}
|
||||
|
||||
template <>
|
||||
intrusive_ptr<Field> read_new<Field>(Reader& reader) {
|
||||
// there must be a type specified
|
||||
String type;
|
||||
reader.handle(_("type"), type);
|
||||
if (type == _("text")) return intrusive(new TextField());
|
||||
else if (type == _("choice")) return intrusive(new ChoiceField());
|
||||
else if (type == _("multiple choice")) return intrusive(new MultipleChoiceField());
|
||||
else if (type == _("boolean")) return intrusive(new BooleanField());
|
||||
else if (type == _("image")) return intrusive(new ImageField());
|
||||
else if (type == _("symbol")) return intrusive(new SymbolField());
|
||||
else if (type == _("color")) return intrusive(new ColorField());
|
||||
else if (type == _("info")) return intrusive(new InfoField());
|
||||
else if (type == _("package choice")) return intrusive(new PackageChoiceField());
|
||||
else if (type.empty()) {
|
||||
reader.warning(_ERROR_1_("expected key", _("type")));
|
||||
throw ParseError(_ERROR_("aborting parsing"));
|
||||
} else {
|
||||
reader.warning(_ERROR_1_("unsupported field type", type));
|
||||
throw ParseError(_ERROR_("aborting parsing"));
|
||||
}
|
||||
// there must be a type specified
|
||||
String type;
|
||||
reader.handle(_("type"), type);
|
||||
if (type == _("text")) return intrusive(new TextField());
|
||||
else if (type == _("choice")) return intrusive(new ChoiceField());
|
||||
else if (type == _("multiple choice")) return intrusive(new MultipleChoiceField());
|
||||
else if (type == _("boolean")) return intrusive(new BooleanField());
|
||||
else if (type == _("image")) return intrusive(new ImageField());
|
||||
else if (type == _("symbol")) return intrusive(new SymbolField());
|
||||
else if (type == _("color")) return intrusive(new ColorField());
|
||||
else if (type == _("info")) return intrusive(new InfoField());
|
||||
else if (type == _("package choice")) return intrusive(new PackageChoiceField());
|
||||
else if (type.empty()) {
|
||||
reader.warning(_ERROR_1_("expected key", _("type")));
|
||||
throw ParseError(_ERROR_("aborting parsing"));
|
||||
} else {
|
||||
reader.warning(_ERROR_1_("unsupported field type", type));
|
||||
throw ParseError(_ERROR_("aborting parsing"));
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Style
|
||||
|
||||
Style::Style(const FieldP& field)
|
||||
: fieldP(field)
|
||||
, z_index(0)
|
||||
, left (1000000), top (1000000)
|
||||
, width(0), height(0)
|
||||
, right(1000000), bottom(1000000)
|
||||
, angle(0)
|
||||
, visible(true)
|
||||
, automatic_side(AUTO_UNKNOWN)
|
||||
, content_dependent(false)
|
||||
: fieldP(field)
|
||||
, z_index(0)
|
||||
, left (1000000), top (1000000)
|
||||
, width(0), height(0)
|
||||
, right(1000000), bottom(1000000)
|
||||
, angle(0)
|
||||
, visible(true)
|
||||
, automatic_side(AUTO_UNKNOWN)
|
||||
, content_dependent(false)
|
||||
{}
|
||||
|
||||
Style::~Style() {}
|
||||
|
||||
IMPLEMENT_REFLECTION(Style) {
|
||||
REFLECT(z_index);
|
||||
REFLECT(left);
|
||||
REFLECT(width);
|
||||
REFLECT(right);
|
||||
REFLECT(top);
|
||||
REFLECT(height);
|
||||
REFLECT(bottom);
|
||||
REFLECT(angle);
|
||||
REFLECT(visible);
|
||||
REFLECT(mask);
|
||||
REFLECT(z_index);
|
||||
REFLECT(left);
|
||||
REFLECT(width);
|
||||
REFLECT(right);
|
||||
REFLECT(top);
|
||||
REFLECT(height);
|
||||
REFLECT(bottom);
|
||||
REFLECT(angle);
|
||||
REFLECT(visible);
|
||||
REFLECT(mask);
|
||||
}
|
||||
|
||||
void init_object(const FieldP& field, StyleP& style) {
|
||||
if (!style) style = field->newStyle(field);
|
||||
if (!style) style = field->newStyle(field);
|
||||
}
|
||||
template <> StyleP read_new<Style>(Reader&) {
|
||||
throw InternalError(_("IndexMap contains nullptr StyleP the application should have crashed already"));
|
||||
throw InternalError(_("IndexMap contains nullptr StyleP the application should have crashed already"));
|
||||
}
|
||||
|
||||
inline bool is_set(const Scriptable<double>& x) {
|
||||
return x.isScripted() || x < 100000;
|
||||
return x.isScripted() || x < 100000;
|
||||
}
|
||||
inline bool is_setw(const Scriptable<double>& x) {
|
||||
return x.isScripted() || fabs(x()) > 0.001;
|
||||
return x.isScripted() || fabs(x()) > 0.001;
|
||||
}
|
||||
|
||||
int Style::update(Context& ctx) {
|
||||
int changed =
|
||||
( left .update(ctx)
|
||||
| width .update(ctx)
|
||||
| right .update(ctx)
|
||||
| top .update(ctx)
|
||||
| height .update(ctx)
|
||||
| bottom .update(ctx)
|
||||
| angle .update(ctx) ) * CHANGE_SIZE
|
||||
| visible.update(ctx) * CHANGE_OTHER
|
||||
| mask .update(ctx) * CHANGE_MASK;
|
||||
// determine automatic_side and attachment of rotation point
|
||||
if (automatic_side == AUTO_UNKNOWN) {
|
||||
if (!is_set (right)) automatic_side = (AutomaticSide)(automatic_side | AUTO_RIGHT);
|
||||
else if (!is_setw(width)) automatic_side = (AutomaticSide)(automatic_side | AUTO_WIDTH);
|
||||
else if (!is_set (left)) automatic_side = (AutomaticSide)(automatic_side | AUTO_LEFT);
|
||||
else automatic_side = (AutomaticSide)(automatic_side | AUTO_LR);
|
||||
if (!is_set (bottom)) automatic_side = (AutomaticSide)(automatic_side | AUTO_BOTTOM);
|
||||
else if (!is_setw(height)) automatic_side = (AutomaticSide)(automatic_side | AUTO_HEIGHT);
|
||||
else if (!is_set (top)) automatic_side = (AutomaticSide)(automatic_side | AUTO_TOP);
|
||||
else automatic_side = (AutomaticSide)(automatic_side | AUTO_TB);
|
||||
changed |= CHANGE_SIZE;
|
||||
}
|
||||
if (!changed) return CHANGE_NONE;
|
||||
// update the automatic_side
|
||||
if (automatic_side & AUTO_LEFT) left = right - width;
|
||||
else if (automatic_side & AUTO_WIDTH) width = right - left;
|
||||
else if (automatic_side & AUTO_RIGHT) right = left + width;
|
||||
else {int lr = int(left + right); left = (lr - width) / 2; right = (lr + width) / 2; }
|
||||
if (automatic_side & AUTO_TOP) top = bottom - height;
|
||||
else if (automatic_side & AUTO_HEIGHT) height = bottom - top;
|
||||
else if (automatic_side & AUTO_BOTTOM) bottom = top + height;
|
||||
else {int tb = int(top + bottom); top = (tb - height) / 2; bottom = (tb + height) / 2; }
|
||||
// adjust rotation point
|
||||
if (angle != 0 && (automatic_side & (AUTO_LEFT | AUTO_TOP))) {
|
||||
double s = sin(deg_to_rad(angle)), c = cos(deg_to_rad(angle));
|
||||
if (automatic_side & AUTO_LEFT) { // attach right corner instead of left
|
||||
left = left + width * (1 - c);
|
||||
top = top + width * s;
|
||||
}
|
||||
if (automatic_side & AUTO_TOP) { // attach botom corner instead of top
|
||||
left = left - height * s;
|
||||
top = top + height * (1 - c);
|
||||
}
|
||||
}
|
||||
if (width < 0) width = -width;
|
||||
if (height < 0) height = -height;
|
||||
// done
|
||||
return changed;
|
||||
int changed =
|
||||
( left .update(ctx)
|
||||
| width .update(ctx)
|
||||
| right .update(ctx)
|
||||
| top .update(ctx)
|
||||
| height .update(ctx)
|
||||
| bottom .update(ctx)
|
||||
| angle .update(ctx) ) * CHANGE_SIZE
|
||||
| visible.update(ctx) * CHANGE_OTHER
|
||||
| mask .update(ctx) * CHANGE_MASK;
|
||||
// determine automatic_side and attachment of rotation point
|
||||
if (automatic_side == AUTO_UNKNOWN) {
|
||||
if (!is_set (right)) automatic_side = (AutomaticSide)(automatic_side | AUTO_RIGHT);
|
||||
else if (!is_setw(width)) automatic_side = (AutomaticSide)(automatic_side | AUTO_WIDTH);
|
||||
else if (!is_set (left)) automatic_side = (AutomaticSide)(automatic_side | AUTO_LEFT);
|
||||
else automatic_side = (AutomaticSide)(automatic_side | AUTO_LR);
|
||||
if (!is_set (bottom)) automatic_side = (AutomaticSide)(automatic_side | AUTO_BOTTOM);
|
||||
else if (!is_setw(height)) automatic_side = (AutomaticSide)(automatic_side | AUTO_HEIGHT);
|
||||
else if (!is_set (top)) automatic_side = (AutomaticSide)(automatic_side | AUTO_TOP);
|
||||
else automatic_side = (AutomaticSide)(automatic_side | AUTO_TB);
|
||||
changed |= CHANGE_SIZE;
|
||||
}
|
||||
if (!changed) return CHANGE_NONE;
|
||||
// update the automatic_side
|
||||
if (automatic_side & AUTO_LEFT) left = right - width;
|
||||
else if (automatic_side & AUTO_WIDTH) width = right - left;
|
||||
else if (automatic_side & AUTO_RIGHT) right = left + width;
|
||||
else {int lr = int(left + right); left = (lr - width) / 2; right = (lr + width) / 2; }
|
||||
if (automatic_side & AUTO_TOP) top = bottom - height;
|
||||
else if (automatic_side & AUTO_HEIGHT) height = bottom - top;
|
||||
else if (automatic_side & AUTO_BOTTOM) bottom = top + height;
|
||||
else {int tb = int(top + bottom); top = (tb - height) / 2; bottom = (tb + height) / 2; }
|
||||
// adjust rotation point
|
||||
if (angle != 0 && (automatic_side & (AUTO_LEFT | AUTO_TOP))) {
|
||||
double s = sin(deg_to_rad(angle)), c = cos(deg_to_rad(angle));
|
||||
if (automatic_side & AUTO_LEFT) { // attach right corner instead of left
|
||||
left = left + width * (1 - c);
|
||||
top = top + width * s;
|
||||
}
|
||||
if (automatic_side & AUTO_TOP) { // attach botom corner instead of top
|
||||
left = left - height * s;
|
||||
top = top + height * (1 - c);
|
||||
}
|
||||
}
|
||||
if (width < 0) width = -width;
|
||||
if (height < 0) height = -height;
|
||||
// done
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool Style::isVisible() const {
|
||||
return visible
|
||||
&& (width()) > 0
|
||||
&& fabs(left()) < 100000
|
||||
&& fabs(right()) < 100000
|
||||
&& (height()) > 0
|
||||
&& fabs(top()) < 100000
|
||||
&& fabs(bottom()) < 100000;
|
||||
return visible
|
||||
&& (width()) > 0
|
||||
&& fabs(left()) < 100000
|
||||
&& fabs(right()) < 100000
|
||||
&& (height()) > 0
|
||||
&& fabs(top()) < 100000
|
||||
&& fabs(bottom()) < 100000;
|
||||
}
|
||||
bool Style::hasSize() const {
|
||||
int h = is_setw(width) + is_set(left) + is_set(right);
|
||||
int v = is_setw(height) + is_set(top) + is_set(bottom);
|
||||
return h >= 2 && v >= 2;
|
||||
int h = is_setw(width) + is_set(left) + is_set(right);
|
||||
int v = is_setw(height) + is_set(top) + is_set(bottom);
|
||||
return h >= 2 && v >= 2;
|
||||
}
|
||||
|
||||
void Style::initDependencies(Context& ctx, const Dependency& dep) const {
|
||||
// left .initDependencies(ctx,dep);
|
||||
// top .initDependencies(ctx,dep);
|
||||
// width .initDependencies(ctx,dep);
|
||||
// height .initDependencies(ctx,dep);
|
||||
// visible.initDependencies(ctx,dep);
|
||||
// left .initDependencies(ctx,dep);
|
||||
// top .initDependencies(ctx,dep);
|
||||
// width .initDependencies(ctx,dep);
|
||||
// height .initDependencies(ctx,dep);
|
||||
// visible.initDependencies(ctx,dep);
|
||||
}
|
||||
void Style::checkContentDependencies(Context& ctx, const Dependency& dep) const {
|
||||
left .initDependencies(ctx,dep);
|
||||
top .initDependencies(ctx,dep);
|
||||
width .initDependencies(ctx,dep);
|
||||
height .initDependencies(ctx,dep);
|
||||
right .initDependencies(ctx,dep);
|
||||
bottom .initDependencies(ctx,dep);
|
||||
visible.initDependencies(ctx,dep);
|
||||
mask .initDependencies(ctx,dep);
|
||||
left .initDependencies(ctx,dep);
|
||||
top .initDependencies(ctx,dep);
|
||||
width .initDependencies(ctx,dep);
|
||||
height .initDependencies(ctx,dep);
|
||||
right .initDependencies(ctx,dep);
|
||||
bottom .initDependencies(ctx,dep);
|
||||
visible.initDependencies(ctx,dep);
|
||||
mask .initDependencies(ctx,dep);
|
||||
}
|
||||
|
||||
void Style::markDependencyMember(const String& name, const Dependency& dep) const {
|
||||
// mark dependencies on content
|
||||
if (dep.type == DEP_DUMMY && dep.index == false && starts_with(name, _("content "))) {
|
||||
// anything that starts with "content_" is a content property
|
||||
const_cast<Dependency&>(dep).index = true;
|
||||
}
|
||||
// mark dependencies on content
|
||||
if (dep.type == DEP_DUMMY && dep.index == false && starts_with(name, _("content "))) {
|
||||
// anything that starts with "content_" is a content property
|
||||
const_cast<Dependency&>(dep).index = true;
|
||||
}
|
||||
}
|
||||
|
||||
void mark_dependency_member(const Style& style, const String& name, const Dependency& dep) {
|
||||
style.markDependencyMember(name,dep);
|
||||
style.markDependencyMember(name,dep);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : StyleListener
|
||||
|
||||
void Style::addListener(StyleListener* listener) {
|
||||
listeners.push_back(listener);
|
||||
listeners.push_back(listener);
|
||||
}
|
||||
void Style::removeListener(StyleListener* listener) {
|
||||
listeners.erase(
|
||||
std::remove(
|
||||
listeners.begin(),
|
||||
listeners.end(),
|
||||
listener
|
||||
),
|
||||
listeners.end()
|
||||
);
|
||||
listeners.erase(
|
||||
std::remove(
|
||||
listeners.begin(),
|
||||
listeners.end(),
|
||||
listener
|
||||
),
|
||||
listeners.end()
|
||||
);
|
||||
}
|
||||
void Style::tellListeners(int changes) {
|
||||
FOR_EACH(l, listeners) l->onStyleChange(changes);
|
||||
FOR_EACH(l, listeners) l->onStyleChange(changes);
|
||||
}
|
||||
|
||||
StyleListener::StyleListener(const StyleP& style)
|
||||
: styleP(style)
|
||||
: styleP(style)
|
||||
{
|
||||
style->addListener(this);
|
||||
style->addListener(this);
|
||||
}
|
||||
StyleListener::~StyleListener() {
|
||||
styleP->removeListener(this);
|
||||
styleP->removeListener(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -270,34 +270,34 @@ IMPLEMENT_REFLECTION_NAMELESS(Value) {
|
||||
}
|
||||
|
||||
bool Value::equals(const Value* that) {
|
||||
return this == that;
|
||||
return this == that;
|
||||
}
|
||||
|
||||
bool Value::update(Context& ctx) {
|
||||
updateAge();
|
||||
updateSortValue(ctx);
|
||||
return false;
|
||||
updateAge();
|
||||
updateSortValue(ctx);
|
||||
return false;
|
||||
}
|
||||
void Value::updateAge() {
|
||||
last_script_update.update();
|
||||
last_script_update.update();
|
||||
}
|
||||
void Value::updateSortValue(Context& ctx) {
|
||||
sort_value = fieldP->sort_script.invoke(ctx)->toString();
|
||||
sort_value = fieldP->sort_script.invoke(ctx)->toString();
|
||||
}
|
||||
|
||||
void init_object(const FieldP& field, ValueP& value) {
|
||||
if (!value)
|
||||
value = field->newValue(field);
|
||||
if (!value)
|
||||
value = field->newValue(field);
|
||||
}
|
||||
template <> ValueP read_new<Value>(Reader&) {
|
||||
throw InternalError(_("IndexMap contains nullptr ValueP the application should have crashed already"));
|
||||
throw InternalError(_("IndexMap contains nullptr ValueP the application should have crashed already"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
void mark_dependency_member(const IndexMap<FieldP,ValueP>& value, const String& name, const Dependency& dep) {
|
||||
IndexMap<FieldP,ValueP>::const_iterator it = value.find(name);
|
||||
if (it != value.end()) {
|
||||
(*it)->fieldP->dependent_scripts.add(dep);
|
||||
}
|
||||
IndexMap<FieldP,ValueP>::const_iterator it = value.find(name);
|
||||
if (it != value.end()) {
|
||||
(*it)->fieldP->dependent_scripts.add(dep);
|
||||
}
|
||||
}
|
||||
|
||||
+196
-196
@@ -39,52 +39,52 @@ DECLARE_DYNAMIC_ARG(Value*, value_being_updated);
|
||||
/// Information on how to store a value
|
||||
class Field : public IntrusivePtrVirtualBase {
|
||||
public:
|
||||
Field();
|
||||
virtual ~Field();
|
||||
|
||||
size_t index; ///< Used by IndexMap
|
||||
String name; ///< Name of the field, for refering to it from scripts and files
|
||||
String description; ///< Description, used in status bar
|
||||
String icon_filename; ///< Filename for an icon (for list of fields)
|
||||
bool editable; ///< Can values of this field be edited?
|
||||
bool save_value; ///< Should values of this field be written to files? Can be false for script generated fields.
|
||||
bool show_statistics; ///< Should this field appear as a group by choice in the statistics panel?
|
||||
int position_hint; ///< Position in the statistics list
|
||||
bool identifying; ///< Does this field give Card::identification()?
|
||||
int card_list_column; ///< What column to use in the card list?
|
||||
UInt card_list_width; ///< Width of the card list column (pixels).
|
||||
bool card_list_visible;///< Is this field shown in the card list?
|
||||
bool card_list_allow; ///< Is this field allowed to appear in the card list?
|
||||
String card_list_name; ///< Alternate name to use in card list.
|
||||
Alignment card_list_align; ///< Alignment of the card list colummn.
|
||||
OptionalScript sort_script; ///< The script to use when sorting this, if not the value.
|
||||
int tab_index; ///< Tab index in editor
|
||||
Dependencies dependent_scripts; ///< Scripts that depend on values of this field
|
||||
|
||||
/// Creates a new Value corresponding to this Field
|
||||
/** thisP is a smart pointer to this */
|
||||
virtual ValueP newValue(const FieldP& thisP) const = 0;
|
||||
/// Creates a new Style corresponding to this Field
|
||||
/** thisP is a smart pointer to this */
|
||||
virtual StyleP newStyle(const FieldP& thisP) const = 0;
|
||||
/// Type of this field
|
||||
virtual String typeName() const = 0;
|
||||
|
||||
/// Add the given dependency to the dependet_scripts list for the variables this field depends on
|
||||
virtual void initDependencies(Context& ctx, const Dependency& dep) const;
|
||||
|
||||
Field();
|
||||
virtual ~Field();
|
||||
|
||||
size_t index; ///< Used by IndexMap
|
||||
String name; ///< Name of the field, for refering to it from scripts and files
|
||||
String description; ///< Description, used in status bar
|
||||
String icon_filename; ///< Filename for an icon (for list of fields)
|
||||
bool editable; ///< Can values of this field be edited?
|
||||
bool save_value; ///< Should values of this field be written to files? Can be false for script generated fields.
|
||||
bool show_statistics; ///< Should this field appear as a group by choice in the statistics panel?
|
||||
int position_hint; ///< Position in the statistics list
|
||||
bool identifying; ///< Does this field give Card::identification()?
|
||||
int card_list_column; ///< What column to use in the card list?
|
||||
UInt card_list_width; ///< Width of the card list column (pixels).
|
||||
bool card_list_visible;///< Is this field shown in the card list?
|
||||
bool card_list_allow; ///< Is this field allowed to appear in the card list?
|
||||
String card_list_name; ///< Alternate name to use in card list.
|
||||
Alignment card_list_align; ///< Alignment of the card list colummn.
|
||||
OptionalScript sort_script; ///< The script to use when sorting this, if not the value.
|
||||
int tab_index; ///< Tab index in editor
|
||||
Dependencies dependent_scripts; ///< Scripts that depend on values of this field
|
||||
|
||||
/// Creates a new Value corresponding to this Field
|
||||
/** thisP is a smart pointer to this */
|
||||
virtual ValueP newValue(const FieldP& thisP) const = 0;
|
||||
/// Creates a new Style corresponding to this Field
|
||||
/** thisP is a smart pointer to this */
|
||||
virtual StyleP newStyle(const FieldP& thisP) const = 0;
|
||||
/// Type of this field
|
||||
virtual String typeName() const = 0;
|
||||
|
||||
/// Add the given dependency to the dependet_scripts list for the variables this field depends on
|
||||
virtual void initDependencies(Context& ctx, const Dependency& dep) const;
|
||||
|
||||
private:
|
||||
DECLARE_REFLECTION_VIRTUAL();
|
||||
DECLARE_REFLECTION_VIRTUAL();
|
||||
};
|
||||
|
||||
template <>
|
||||
intrusive_ptr<Field> read_new<Field>(Reader& reader);
|
||||
inline void update_index(FieldP& f, size_t index) {
|
||||
f->index = index;
|
||||
f->index = index;
|
||||
}
|
||||
|
||||
inline String type_name(const Field&) {
|
||||
return _TYPE_("field");
|
||||
return _TYPE_("field");
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Style
|
||||
@@ -92,88 +92,88 @@ inline String type_name(const Field&) {
|
||||
/// Style information needed to display a Value in a Field.
|
||||
class Style : public IntrusivePtrVirtualBase {
|
||||
public:
|
||||
Style(const FieldP&);
|
||||
virtual ~Style();
|
||||
|
||||
const FieldP fieldP; ///< Field this style is for, should have the right type!
|
||||
|
||||
int z_index; ///< Stacking of values of this field, higher = on top
|
||||
Scriptable<double> left, top; ///< Position of this field
|
||||
Scriptable<double> width, height; ///< Position of this field
|
||||
Scriptable<double> right, bottom; ///< Position of this field
|
||||
Scriptable<Degrees> angle; ///< Rotation of the box
|
||||
Scriptable<bool> visible; ///< Is this field visible?
|
||||
CachedScriptableMask mask; ///< Mask image
|
||||
|
||||
enum AutomaticSide {
|
||||
AUTO_UNKNOWN = 0x00,
|
||||
AUTO_LEFT = 0x01, AUTO_WIDTH = 0x02, AUTO_RIGHT = 0x04, AUTO_LR = 0x08,
|
||||
AUTO_TOP = 0x10, AUTO_HEIGHT = 0x20, AUTO_BOTTOM = 0x40, AUTO_TB = 0x80,
|
||||
ATTACH_LEFT = 0x04, ATTACH_CENTER = 0x02, ATTACH_RIGHT = 0x01,
|
||||
ATTACH_TOP = 0x40, ATTACH_MIDDLE = 0x20, ATTACH_BOTTOM = 0x10,
|
||||
} automatic_side : 8; ///< Which of (left, width, right) and (top, height, bottom) is determined automatically?
|
||||
bool content_dependent; ///< Does this style depend on content properties?
|
||||
|
||||
inline RealPoint getPos() const { return RealPoint(left, top ); }
|
||||
inline RealSize getSize() const { return RealSize ( width, height); }
|
||||
inline RealRect getExternalRect() const { return RealRect (left, top, width, height); }
|
||||
inline RealRect getInternalRect() const { return RealRect(0, 0, width, height); }
|
||||
|
||||
/// Does this style have a non-zero size (or is it scripted)?
|
||||
bool hasSize() const;
|
||||
/// Is this style visible, and does it have a sane size
|
||||
bool isVisible() const;
|
||||
|
||||
/// Get a copy of this style
|
||||
virtual StyleP clone() const = 0;
|
||||
|
||||
/// Make a viewer object for values using this style
|
||||
/** thisP is a smart pointer to this */
|
||||
virtual ValueViewerP makeViewer(DataViewer& parent, const StyleP& thisP) = 0;
|
||||
/// Make an editor object for values using this style
|
||||
/** thisP is a smart pointer to this */
|
||||
virtual ValueViewerP makeEditor(DataEditor& parent, const StyleP& thisP) = 0;
|
||||
|
||||
/// Update scripted values of this style, return nonzero if anything has changed.
|
||||
/** The caller should tellListeners()
|
||||
* The result is a combination of StyleChange flags
|
||||
*/
|
||||
virtual int update(Context&);
|
||||
/// Add the given dependency to the dependent_scripts list for the variables this style depends on
|
||||
/** Only use for things that need invalidate() */
|
||||
virtual void initDependencies(Context&, const Dependency&) const;
|
||||
/// Check if the style depends on content properties
|
||||
/** If there is such a dependency, set dep.index to true.
|
||||
* This is done by mark_dependency_member for those properies */
|
||||
virtual void checkContentDependencies(Context&, const Dependency&) const;
|
||||
/// Dependencies on properies?
|
||||
/** In particular, if dep == DEP_DUMMY and name is a content property, set dep.index=true */
|
||||
virtual void markDependencyMember(const String& name, const Dependency&) const;
|
||||
/// Invalidate scripted images for this style
|
||||
virtual void invalidate() {}
|
||||
|
||||
/// Add a StyleListener
|
||||
void addListener(StyleListener*);
|
||||
/// Remove a StyleListener
|
||||
void removeListener(StyleListener*);
|
||||
/// Tell the StyleListeners that this style has changed
|
||||
/** change_info is a subset of StyleChange flags */
|
||||
void tellListeners(int changes);
|
||||
|
||||
Style(const FieldP&);
|
||||
virtual ~Style();
|
||||
|
||||
const FieldP fieldP; ///< Field this style is for, should have the right type!
|
||||
|
||||
int z_index; ///< Stacking of values of this field, higher = on top
|
||||
Scriptable<double> left, top; ///< Position of this field
|
||||
Scriptable<double> width, height; ///< Position of this field
|
||||
Scriptable<double> right, bottom; ///< Position of this field
|
||||
Scriptable<Degrees> angle; ///< Rotation of the box
|
||||
Scriptable<bool> visible; ///< Is this field visible?
|
||||
CachedScriptableMask mask; ///< Mask image
|
||||
|
||||
enum AutomaticSide {
|
||||
AUTO_UNKNOWN = 0x00,
|
||||
AUTO_LEFT = 0x01, AUTO_WIDTH = 0x02, AUTO_RIGHT = 0x04, AUTO_LR = 0x08,
|
||||
AUTO_TOP = 0x10, AUTO_HEIGHT = 0x20, AUTO_BOTTOM = 0x40, AUTO_TB = 0x80,
|
||||
ATTACH_LEFT = 0x04, ATTACH_CENTER = 0x02, ATTACH_RIGHT = 0x01,
|
||||
ATTACH_TOP = 0x40, ATTACH_MIDDLE = 0x20, ATTACH_BOTTOM = 0x10,
|
||||
} automatic_side : 8; ///< Which of (left, width, right) and (top, height, bottom) is determined automatically?
|
||||
bool content_dependent; ///< Does this style depend on content properties?
|
||||
|
||||
inline RealPoint getPos() const { return RealPoint(left, top ); }
|
||||
inline RealSize getSize() const { return RealSize ( width, height); }
|
||||
inline RealRect getExternalRect() const { return RealRect (left, top, width, height); }
|
||||
inline RealRect getInternalRect() const { return RealRect(0, 0, width, height); }
|
||||
|
||||
/// Does this style have a non-zero size (or is it scripted)?
|
||||
bool hasSize() const;
|
||||
/// Is this style visible, and does it have a sane size
|
||||
bool isVisible() const;
|
||||
|
||||
/// Get a copy of this style
|
||||
virtual StyleP clone() const = 0;
|
||||
|
||||
/// Make a viewer object for values using this style
|
||||
/** thisP is a smart pointer to this */
|
||||
virtual ValueViewerP makeViewer(DataViewer& parent, const StyleP& thisP) = 0;
|
||||
/// Make an editor object for values using this style
|
||||
/** thisP is a smart pointer to this */
|
||||
virtual ValueViewerP makeEditor(DataEditor& parent, const StyleP& thisP) = 0;
|
||||
|
||||
/// Update scripted values of this style, return nonzero if anything has changed.
|
||||
/** The caller should tellListeners()
|
||||
* The result is a combination of StyleChange flags
|
||||
*/
|
||||
virtual int update(Context&);
|
||||
/// Add the given dependency to the dependent_scripts list for the variables this style depends on
|
||||
/** Only use for things that need invalidate() */
|
||||
virtual void initDependencies(Context&, const Dependency&) const;
|
||||
/// Check if the style depends on content properties
|
||||
/** If there is such a dependency, set dep.index to true.
|
||||
* This is done by mark_dependency_member for those properies */
|
||||
virtual void checkContentDependencies(Context&, const Dependency&) const;
|
||||
/// Dependencies on properies?
|
||||
/** In particular, if dep == DEP_DUMMY and name is a content property, set dep.index=true */
|
||||
virtual void markDependencyMember(const String& name, const Dependency&) const;
|
||||
/// Invalidate scripted images for this style
|
||||
virtual void invalidate() {}
|
||||
|
||||
/// Add a StyleListener
|
||||
void addListener(StyleListener*);
|
||||
/// Remove a StyleListener
|
||||
void removeListener(StyleListener*);
|
||||
/// Tell the StyleListeners that this style has changed
|
||||
/** change_info is a subset of StyleChange flags */
|
||||
void tellListeners(int changes);
|
||||
|
||||
private:
|
||||
DECLARE_REFLECTION_VIRTUAL();
|
||||
/// Things that are listening to changes in this style
|
||||
vector<StyleListener*> listeners;
|
||||
DECLARE_REFLECTION_VIRTUAL();
|
||||
/// Things that are listening to changes in this style
|
||||
vector<StyleListener*> listeners;
|
||||
};
|
||||
|
||||
/// What changed in a style update?
|
||||
enum StyleChange
|
||||
{ CHANGE_NONE = 0x00 // nothing changed
|
||||
, CHANGE_OTHER = 0x01 // some other change (note: result of casting from bool)
|
||||
, CHANGE_SIZE = 0x02 // size/angle changed
|
||||
, CHANGE_DEFAULT = 0x04 // only the 'default' state is affected
|
||||
, CHANGE_MASK = 0x08 // a mask image changed, must be reloaded
|
||||
, CHANGE_ALREADY_PREPARED = 0x80 // hint that the change was the result of a content property change, viewers are already prepared
|
||||
{ CHANGE_NONE = 0x00 // nothing changed
|
||||
, CHANGE_OTHER = 0x01 // some other change (note: result of casting from bool)
|
||||
, CHANGE_SIZE = 0x02 // size/angle changed
|
||||
, CHANGE_DEFAULT = 0x04 // only the 'default' state is affected
|
||||
, CHANGE_MASK = 0x08 // a mask image changed, must be reloaded
|
||||
, CHANGE_ALREADY_PREPARED = 0x80 // hint that the change was the result of a content property change, viewers are already prepared
|
||||
};
|
||||
|
||||
void init_object(const FieldP&, StyleP&);
|
||||
@@ -182,7 +182,7 @@ inline const String& get_key_name(const StyleP& s) { return s->fieldP->name; }
|
||||
template <> StyleP read_new<Style>(Reader&);
|
||||
|
||||
inline String type_name(const Style&) {
|
||||
return _TYPE_("style");
|
||||
return _TYPE_("style");
|
||||
}
|
||||
|
||||
void mark_dependency_member(const Style& style, const String& name, const Dependency& dep);
|
||||
@@ -192,14 +192,14 @@ void mark_dependency_member(const Style& style, const String& name, const Depend
|
||||
/// An object that can respond when a style changes;
|
||||
class StyleListener : public IntrusivePtrVirtualBase {
|
||||
public:
|
||||
StyleListener(const StyleP& style);
|
||||
virtual ~StyleListener();
|
||||
|
||||
/// Called when a (scripted) property of the viewed style has changed
|
||||
/** changes is a combination of StyleChange flags */
|
||||
virtual void onStyleChange(int changes) {}
|
||||
StyleListener(const StyleP& style);
|
||||
virtual ~StyleListener();
|
||||
|
||||
/// Called when a (scripted) property of the viewed style has changed
|
||||
/** changes is a combination of StyleChange flags */
|
||||
virtual void onStyleChange(int changes) {}
|
||||
protected:
|
||||
const StyleP styleP; ///< The style we are listening to
|
||||
const StyleP styleP; ///< The style we are listening to
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Value
|
||||
@@ -207,43 +207,43 @@ class StyleListener : public IntrusivePtrVirtualBase {
|
||||
/// A specific value 'in' a Field.
|
||||
class Value : public IntrusivePtrVirtualBase {
|
||||
public:
|
||||
inline Value(const FieldP& field) : fieldP(field) {}
|
||||
virtual ~Value();
|
||||
|
||||
const FieldP fieldP; ///< Field this value is for, should have the right type!
|
||||
Age last_script_update; ///< When where the scripts last updated? (by calling update)
|
||||
String sort_value; ///< How this should be sorted.
|
||||
|
||||
/// Get a copy of this value
|
||||
virtual ValueP clone() const = 0;
|
||||
|
||||
/// Convert this value to a string for use in tables
|
||||
virtual String toString() const = 0;
|
||||
/// Apply scripts to this value, return true if the value has changed
|
||||
virtual bool update(Context& ctx);
|
||||
/// This value has been updated by an action
|
||||
/** Does nothing for most Values, only FakeValues can update underlying data */
|
||||
virtual void onAction(Action& a, bool undone) {}
|
||||
/// Is this value the same as some other value (for the same field&card)
|
||||
/** Has behaviour other than == for FakeTextValue.
|
||||
* In that case returns true if this and that editing the same undelying value.
|
||||
* If so, this value is updated to reflect the (possibly changed) underlying value.
|
||||
*/
|
||||
virtual bool equals(const Value* that);
|
||||
inline Value(const FieldP& field) : fieldP(field) {}
|
||||
virtual ~Value();
|
||||
|
||||
const FieldP fieldP; ///< Field this value is for, should have the right type!
|
||||
Age last_script_update; ///< When where the scripts last updated? (by calling update)
|
||||
String sort_value; ///< How this should be sorted.
|
||||
|
||||
/// Get a copy of this value
|
||||
virtual ValueP clone() const = 0;
|
||||
|
||||
/// Convert this value to a string for use in tables
|
||||
virtual String toString() const = 0;
|
||||
/// Apply scripts to this value, return true if the value has changed
|
||||
virtual bool update(Context& ctx);
|
||||
/// This value has been updated by an action
|
||||
/** Does nothing for most Values, only FakeValues can update underlying data */
|
||||
virtual void onAction(Action& a, bool undone) {}
|
||||
/// Is this value the same as some other value (for the same field&card)
|
||||
/** Has behaviour other than == for FakeTextValue.
|
||||
* In that case returns true if this and that editing the same undelying value.
|
||||
* If so, this value is updated to reflect the (possibly changed) underlying value.
|
||||
*/
|
||||
virtual bool equals(const Value* that);
|
||||
|
||||
/// Get the key to use for sorting this value
|
||||
inline String getSortKey() const {
|
||||
return fieldP->sort_script ? sort_value : toString();
|
||||
}
|
||||
|
||||
/// Get the key to use for sorting this value
|
||||
inline String getSortKey() const {
|
||||
return fieldP->sort_script ? sort_value : toString();
|
||||
}
|
||||
|
||||
protected:
|
||||
/// update() split into two functions;.
|
||||
/** Derived classes should put their stuff in between if they need the age in scripts */
|
||||
void updateAge();
|
||||
void updateSortValue(Context& ctx);
|
||||
|
||||
/// update() split into two functions;.
|
||||
/** Derived classes should put their stuff in between if they need the age in scripts */
|
||||
void updateAge();
|
||||
void updateSortValue(Context& ctx);
|
||||
|
||||
private:
|
||||
DECLARE_REFLECTION_VIRTUAL();
|
||||
DECLARE_REFLECTION_VIRTUAL();
|
||||
};
|
||||
|
||||
void init_object(const FieldP&, ValueP&);
|
||||
@@ -252,56 +252,56 @@ inline const String& get_key_name(const ValueP& v) { return v->fieldP->name; }
|
||||
template <> ValueP read_new<Value>(Reader&);
|
||||
|
||||
inline String type_name(const Value&) {
|
||||
return _TYPE_("value");
|
||||
return _TYPE_("value");
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Utilities
|
||||
|
||||
#define DECLARE_FIELD_TYPE(Type) \
|
||||
DECLARE_REFLECTION(); public: \
|
||||
virtual ValueP newValue(const FieldP& thisP) const; \
|
||||
virtual StyleP newStyle(const FieldP& thisP) const; \
|
||||
virtual String typeName() const
|
||||
#define DECLARE_FIELD_TYPE(Type) \
|
||||
DECLARE_REFLECTION(); public: \
|
||||
virtual ValueP newValue(const FieldP& thisP) const; \
|
||||
virtual StyleP newStyle(const FieldP& thisP) const; \
|
||||
virtual String typeName() const
|
||||
|
||||
// implement newStyle and newValue
|
||||
#define IMPLEMENT_FIELD_TYPE(Type, NAME) \
|
||||
StyleP Type ## Field::newStyle(const FieldP& thisP) const { \
|
||||
assert(thisP.get() == this); \
|
||||
return intrusive(new Type ## Style(static_pointer_cast<Type ## Field>(thisP))); \
|
||||
} \
|
||||
ValueP Type ## Field::newValue(const FieldP& thisP) const { \
|
||||
assert(thisP.get() == this); \
|
||||
return intrusive(new Type ## Value(static_pointer_cast<Type ## Field>(thisP))); \
|
||||
} \
|
||||
StyleP Type ## Style::clone() const { \
|
||||
return intrusive(new Type ## Style(*this)); \
|
||||
} \
|
||||
ValueP Type ## Value::clone() const { \
|
||||
return intrusive(new Type ## Value(*this)); \
|
||||
} \
|
||||
String Type ## Field::typeName() const { \
|
||||
return _(NAME); \
|
||||
}
|
||||
#define IMPLEMENT_FIELD_TYPE(Type, NAME) \
|
||||
StyleP Type ## Field::newStyle(const FieldP& thisP) const { \
|
||||
assert(thisP.get() == this); \
|
||||
return intrusive(new Type ## Style(static_pointer_cast<Type ## Field>(thisP))); \
|
||||
} \
|
||||
ValueP Type ## Field::newValue(const FieldP& thisP) const { \
|
||||
assert(thisP.get() == this); \
|
||||
return intrusive(new Type ## Value(static_pointer_cast<Type ## Field>(thisP))); \
|
||||
} \
|
||||
StyleP Type ## Style::clone() const { \
|
||||
return intrusive(new Type ## Style(*this)); \
|
||||
} \
|
||||
ValueP Type ## Value::clone() const { \
|
||||
return intrusive(new Type ## Value(*this)); \
|
||||
} \
|
||||
String Type ## Field::typeName() const { \
|
||||
return _(NAME); \
|
||||
}
|
||||
|
||||
#define DECLARE_STYLE_TYPE(Type) \
|
||||
DECLARE_REFLECTION(); public: \
|
||||
DECLARE_HAS_FIELD(Type) \
|
||||
virtual StyleP clone() const; \
|
||||
virtual ValueViewerP makeViewer(DataViewer& parent, const StyleP& thisP); \
|
||||
virtual ValueViewerP makeEditor(DataEditor& parent, const StyleP& thisP)
|
||||
#define DECLARE_STYLE_TYPE(Type) \
|
||||
DECLARE_REFLECTION(); public: \
|
||||
DECLARE_HAS_FIELD(Type) \
|
||||
virtual StyleP clone() const; \
|
||||
virtual ValueViewerP makeViewer(DataViewer& parent, const StyleP& thisP); \
|
||||
virtual ValueViewerP makeEditor(DataEditor& parent, const StyleP& thisP)
|
||||
|
||||
#define DECLARE_VALUE_TYPE(Type,ValueType_) \
|
||||
DECLARE_REFLECTION(); public: \
|
||||
DECLARE_HAS_FIELD(Type) \
|
||||
virtual ValueP clone() const; \
|
||||
virtual String toString() const; \
|
||||
typedef ValueType_ ValueType
|
||||
#define DECLARE_VALUE_TYPE(Type,ValueType_) \
|
||||
DECLARE_REFLECTION(); public: \
|
||||
DECLARE_HAS_FIELD(Type) \
|
||||
virtual ValueP clone() const; \
|
||||
virtual String toString() const; \
|
||||
typedef ValueType_ ValueType
|
||||
|
||||
// implement field() which returns a field with the right (derived) type
|
||||
#define DECLARE_HAS_FIELD(Type) \
|
||||
inline Type ## Field& field() const { \
|
||||
return *static_cast<Type ## Field*>(fieldP.get()); \
|
||||
}
|
||||
#define DECLARE_HAS_FIELD(Type) \
|
||||
inline Type ## Field& field() const { \
|
||||
return *static_cast<Type ## Field*>(fieldP.get()); \
|
||||
}
|
||||
|
||||
void mark_dependency_member(const IndexMap<FieldP,ValueP>& value, const String& name, const Dependency& dep);
|
||||
|
||||
|
||||
+15
-15
@@ -12,38 +12,38 @@
|
||||
// ----------------------------------------------------------------------------- : BooleanField
|
||||
|
||||
BooleanField::BooleanField() {
|
||||
choices->choices.push_back(intrusive(new Choice(_("yes"))));
|
||||
choices->choices.push_back(intrusive(new Choice(_("no"))));
|
||||
choices->initIds();
|
||||
choices->choices.push_back(intrusive(new Choice(_("yes"))));
|
||||
choices->choices.push_back(intrusive(new Choice(_("no"))));
|
||||
choices->initIds();
|
||||
}
|
||||
|
||||
IMPLEMENT_FIELD_TYPE(Boolean, "boolean");
|
||||
|
||||
IMPLEMENT_REFLECTION(BooleanField) {
|
||||
REFLECT_BASE(Field); // NOTE: don't reflect as a ChoiceField
|
||||
REFLECT(script);
|
||||
REFLECT_N("default", default_script);
|
||||
REFLECT(initial);
|
||||
REFLECT_BASE(Field); // NOTE: don't reflect as a ChoiceField
|
||||
REFLECT(script);
|
||||
REFLECT_N("default", default_script);
|
||||
REFLECT(initial);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : BooleanStyle
|
||||
|
||||
BooleanStyle::BooleanStyle(const ChoiceFieldP& field)
|
||||
: ChoiceStyle(field)
|
||||
: ChoiceStyle(field)
|
||||
{
|
||||
render_style = RENDER_BOTH;
|
||||
//choice_images[_("yes")] = ScriptableImage(_("buildin_image(\"bool_yes\")"));
|
||||
//choice_images[_("no")] = ScriptableImage(_("buildin_image(\"bool_no\")"));
|
||||
choice_images[_("yes")] = ScriptableImage(intrusive(new BuiltInImage(_("bool_yes"))));
|
||||
choice_images[_("no")] = ScriptableImage(intrusive(new BuiltInImage(_("bool_no"))));
|
||||
render_style = RENDER_BOTH;
|
||||
//choice_images[_("yes")] = ScriptableImage(_("buildin_image(\"bool_yes\")"));
|
||||
//choice_images[_("no")] = ScriptableImage(_("buildin_image(\"bool_no\")"));
|
||||
choice_images[_("yes")] = ScriptableImage(intrusive(new BuiltInImage(_("bool_yes"))));
|
||||
choice_images[_("no")] = ScriptableImage(intrusive(new BuiltInImage(_("bool_no"))));
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION(BooleanStyle) {
|
||||
REFLECT_BASE(ChoiceStyle);
|
||||
REFLECT_BASE(ChoiceStyle);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : BooleanValue
|
||||
|
||||
IMPLEMENT_REFLECTION_NAMELESS(BooleanValue) {
|
||||
REFLECT_BASE(ChoiceValue);
|
||||
REFLECT_BASE(ChoiceValue);
|
||||
}
|
||||
|
||||
+18
-18
@@ -21,10 +21,10 @@ DECLARE_POINTER_TYPE(BooleanValue);
|
||||
/// A field whos value is either true or false
|
||||
class BooleanField : public ChoiceField {
|
||||
public:
|
||||
BooleanField();
|
||||
DECLARE_FIELD_TYPE(Boolean);
|
||||
|
||||
// no extra data
|
||||
BooleanField();
|
||||
DECLARE_FIELD_TYPE(Boolean);
|
||||
|
||||
// no extra data
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : BooleanStyle
|
||||
@@ -32,14 +32,14 @@ class BooleanField : public ChoiceField {
|
||||
/// The Style for a BooleanField
|
||||
class BooleanStyle : public ChoiceStyle {
|
||||
public:
|
||||
BooleanStyle(const ChoiceFieldP& field);
|
||||
DECLARE_HAS_FIELD(Boolean); // not DECLARE_STYLE_TYPE, because we use a normal ChoiceValueViewer/Editor
|
||||
virtual StyleP clone() const;
|
||||
|
||||
// no extra data
|
||||
|
||||
BooleanStyle(const ChoiceFieldP& field);
|
||||
DECLARE_HAS_FIELD(Boolean); // not DECLARE_STYLE_TYPE, because we use a normal ChoiceValueViewer/Editor
|
||||
virtual StyleP clone() const;
|
||||
|
||||
// no extra data
|
||||
|
||||
private:
|
||||
DECLARE_REFLECTION();
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : BooleanValue
|
||||
@@ -47,14 +47,14 @@ class BooleanStyle : public ChoiceStyle {
|
||||
/// The Value in a BooleanField
|
||||
class BooleanValue : public ChoiceValue {
|
||||
public:
|
||||
inline BooleanValue(const ChoiceFieldP& field) : ChoiceValue(field) {}
|
||||
DECLARE_HAS_FIELD(Boolean);
|
||||
virtual ValueP clone() const;
|
||||
|
||||
// no extra data
|
||||
|
||||
inline BooleanValue(const ChoiceFieldP& field) : ChoiceValue(field) {}
|
||||
DECLARE_HAS_FIELD(Boolean);
|
||||
virtual ValueP clone() const;
|
||||
|
||||
// no extra data
|
||||
|
||||
private:
|
||||
DECLARE_REFLECTION();
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
+194
-194
@@ -18,292 +18,292 @@ DECLARE_TYPEOF(map<String COMMA ScriptableImage>);
|
||||
// ----------------------------------------------------------------------------- : ChoiceField
|
||||
|
||||
ChoiceField::ChoiceField()
|
||||
: choices((Choice*)new Choice)
|
||||
, default_name(_("Default"))
|
||||
: choices((Choice*)new Choice)
|
||||
, default_name(_("Default"))
|
||||
{}
|
||||
|
||||
IMPLEMENT_FIELD_TYPE(Choice, "choice");
|
||||
|
||||
void ChoiceField::initDependencies(Context& ctx, const Dependency& dep) const {
|
||||
Field ::initDependencies(ctx, dep);
|
||||
script .initDependencies(ctx, dep);
|
||||
default_script.initDependencies(ctx, dep);
|
||||
Field ::initDependencies(ctx, dep);
|
||||
script .initDependencies(ctx, dep);
|
||||
default_script.initDependencies(ctx, dep);
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION(ChoiceField) {
|
||||
REFLECT_BASE(Field);
|
||||
REFLECT_N("choices", choices->choices);
|
||||
REFLECT(script);
|
||||
REFLECT_N("default", default_script);
|
||||
REFLECT(initial);
|
||||
REFLECT(default_name);
|
||||
REFLECT_IF_READING {
|
||||
choices->initIds();
|
||||
}
|
||||
REFLECT(choice_colors);
|
||||
REFLECT(choice_colors_cardlist);
|
||||
REFLECT_BASE(Field);
|
||||
REFLECT_N("choices", choices->choices);
|
||||
REFLECT(script);
|
||||
REFLECT_N("default", default_script);
|
||||
REFLECT(initial);
|
||||
REFLECT(default_name);
|
||||
REFLECT_IF_READING {
|
||||
choices->initIds();
|
||||
}
|
||||
REFLECT(choice_colors);
|
||||
REFLECT(choice_colors_cardlist);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : ChoiceField::Choice
|
||||
|
||||
ChoiceField::Choice::Choice()
|
||||
: line_below(false), enabled(true), type(CHOICE_TYPE_CHECK)
|
||||
, first_id(0)
|
||||
: line_below(false), enabled(true), type(CHOICE_TYPE_CHECK)
|
||||
, first_id(0)
|
||||
{}
|
||||
ChoiceField::Choice::Choice(const String& name)
|
||||
: name(name)
|
||||
, line_below(false), enabled(true), type(CHOICE_TYPE_CHECK)
|
||||
, first_id(0)
|
||||
: name(name)
|
||||
, line_below(false), enabled(true), type(CHOICE_TYPE_CHECK)
|
||||
, first_id(0)
|
||||
{}
|
||||
|
||||
|
||||
bool ChoiceField::Choice::isGroup() const {
|
||||
return !choices.empty();
|
||||
return !choices.empty();
|
||||
}
|
||||
bool ChoiceField::Choice::hasDefault() const {
|
||||
return !isGroup() || !default_name.empty();
|
||||
return !isGroup() || !default_name.empty();
|
||||
}
|
||||
|
||||
|
||||
int ChoiceField::Choice::initIds() {
|
||||
int id = first_id + (hasDefault() ? 1 : 0);
|
||||
FOR_EACH(c, choices) {
|
||||
c->first_id = id;
|
||||
id = c->initIds();
|
||||
}
|
||||
return id;
|
||||
int id = first_id + (hasDefault() ? 1 : 0);
|
||||
FOR_EACH(c, choices) {
|
||||
c->first_id = id;
|
||||
id = c->initIds();
|
||||
}
|
||||
return id;
|
||||
}
|
||||
int ChoiceField::Choice::choiceCount() const {
|
||||
return lastId() - first_id;
|
||||
return lastId() - first_id;
|
||||
}
|
||||
int ChoiceField::Choice::lastId() const {
|
||||
if (isGroup()) {
|
||||
// last id of last choice
|
||||
return choices.back()->lastId();
|
||||
} else {
|
||||
return first_id + 1;
|
||||
}
|
||||
if (isGroup()) {
|
||||
// last id of last choice
|
||||
return choices.back()->lastId();
|
||||
} else {
|
||||
return first_id + 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int ChoiceField::Choice::choiceId(const String& search_name) const {
|
||||
if (hasDefault() && search_name == name) {
|
||||
return first_id;
|
||||
} else if (name.empty()) { // no name for this group, forward to all children
|
||||
FOR_EACH_CONST(c, choices) {
|
||||
int sub_id = c->choiceId(search_name);
|
||||
if (sub_id != -1) return sub_id;
|
||||
}
|
||||
} else if (isGroup() && starts_with(search_name, name + _(" "))) {
|
||||
String sub_name = search_name.substr(name.size() + 1);
|
||||
FOR_EACH_CONST(c, choices) {
|
||||
int sub_id = c->choiceId(sub_name);
|
||||
if (sub_id != -1) return sub_id;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
if (hasDefault() && search_name == name) {
|
||||
return first_id;
|
||||
} else if (name.empty()) { // no name for this group, forward to all children
|
||||
FOR_EACH_CONST(c, choices) {
|
||||
int sub_id = c->choiceId(search_name);
|
||||
if (sub_id != -1) return sub_id;
|
||||
}
|
||||
} else if (isGroup() && starts_with(search_name, name + _(" "))) {
|
||||
String sub_name = search_name.substr(name.size() + 1);
|
||||
FOR_EACH_CONST(c, choices) {
|
||||
int sub_id = c->choiceId(sub_name);
|
||||
if (sub_id != -1) return sub_id;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
String ChoiceField::Choice::choiceName(int id) const {
|
||||
if (hasDefault() && id == first_id) {
|
||||
return name;
|
||||
} else {
|
||||
FOR_EACH_CONST_REVERSE(c, choices) { // take the last one that still contains id
|
||||
if (id >= c->first_id) {
|
||||
if (name.empty()) {
|
||||
return c->choiceName(id);
|
||||
} else {
|
||||
return name + _(" ") + c->choiceName(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return _("");
|
||||
if (hasDefault() && id == first_id) {
|
||||
return name;
|
||||
} else {
|
||||
FOR_EACH_CONST_REVERSE(c, choices) { // take the last one that still contains id
|
||||
if (id >= c->first_id) {
|
||||
if (name.empty()) {
|
||||
return c->choiceName(id);
|
||||
} else {
|
||||
return name + _(" ") + c->choiceName(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return _("");
|
||||
}
|
||||
|
||||
String ChoiceField::Choice::choiceNameNice(int id) const {
|
||||
if (!isGroup() && id == first_id) {
|
||||
return name;
|
||||
} else if (hasDefault() && id == first_id) {
|
||||
return default_name;
|
||||
} else {
|
||||
FOR_EACH_CONST_REVERSE(c, choices) {
|
||||
if (id == c->first_id) {
|
||||
return c->name; // we don't want "<group> default"
|
||||
} else if (id > c->first_id) {
|
||||
return c->choiceNameNice(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
return _("");
|
||||
if (!isGroup() && id == first_id) {
|
||||
return name;
|
||||
} else if (hasDefault() && id == first_id) {
|
||||
return default_name;
|
||||
} else {
|
||||
FOR_EACH_CONST_REVERSE(c, choices) {
|
||||
if (id == c->first_id) {
|
||||
return c->name; // we don't want "<group> default"
|
||||
} else if (id > c->first_id) {
|
||||
return c->choiceNameNice(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
return _("");
|
||||
}
|
||||
|
||||
|
||||
IMPLEMENT_REFLECTION_ENUM(ChoiceChoiceType) {
|
||||
VALUE_N("check", CHOICE_TYPE_CHECK);
|
||||
VALUE_N("radio", CHOICE_TYPE_RADIO);
|
||||
VALUE_N("check", CHOICE_TYPE_CHECK);
|
||||
VALUE_N("radio", CHOICE_TYPE_RADIO);
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION(ChoiceField::Choice) {
|
||||
if (isGroup() || line_below || enabled.isScripted() || tag.isComplex()) {
|
||||
// complex values are groups
|
||||
REFLECT(name);
|
||||
REFLECT_N("group_choice", default_name);
|
||||
REFLECT(choices);
|
||||
REFLECT(line_below);
|
||||
REFLECT(enabled);
|
||||
REFLECT(type);
|
||||
} else {
|
||||
REFLECT_NAMELESS(name);
|
||||
}
|
||||
if (isGroup() || line_below || enabled.isScripted() || tag.isComplex()) {
|
||||
// complex values are groups
|
||||
REFLECT(name);
|
||||
REFLECT_N("group_choice", default_name);
|
||||
REFLECT(choices);
|
||||
REFLECT(line_below);
|
||||
REFLECT(enabled);
|
||||
REFLECT(type);
|
||||
} else {
|
||||
REFLECT_NAMELESS(name);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : ChoiceStyle
|
||||
|
||||
ChoiceStyle::ChoiceStyle(const ChoiceFieldP& field)
|
||||
: Style(field)
|
||||
, popup_style(POPUP_DROPDOWN)
|
||||
, render_style(RENDER_TEXT)
|
||||
, choice_images_initialized(false)
|
||||
, combine(COMBINE_NORMAL)
|
||||
, alignment(ALIGN_STRETCH)
|
||||
, thumbnails(nullptr)
|
||||
, content_width(0.0), content_height(0.0)
|
||||
: Style(field)
|
||||
, popup_style(POPUP_DROPDOWN)
|
||||
, render_style(RENDER_TEXT)
|
||||
, choice_images_initialized(false)
|
||||
, combine(COMBINE_NORMAL)
|
||||
, alignment(ALIGN_STRETCH)
|
||||
, thumbnails(nullptr)
|
||||
, content_width(0.0), content_height(0.0)
|
||||
{}
|
||||
|
||||
ChoiceStyle::~ChoiceStyle() {
|
||||
delete thumbnails;
|
||||
delete thumbnails;
|
||||
}
|
||||
|
||||
void ChoiceStyle::initImage() {
|
||||
if (image.isSet() || choice_images.empty()) return;
|
||||
// for, for example:
|
||||
// choice images:
|
||||
// a: {uvw}
|
||||
// b: {xyz}
|
||||
// generate the script:
|
||||
// [a: {uvw}, b: {xyz}][input]() or else nil
|
||||
// or in bytecode
|
||||
// PUSH_CONST [a: {uvw}, b: {xyz}]
|
||||
// GET_VAR input
|
||||
// MEMBER
|
||||
// CALL 0
|
||||
// PUSH_CONST nil
|
||||
// OR_ELSE
|
||||
ScriptCustomCollectionP lookup(new ScriptCustomCollection());
|
||||
FOR_EACH(ci, choice_images) {
|
||||
lookup->key_value[ci.first] = ci.second.getValidScriptP();
|
||||
}
|
||||
Script& script = image.getMutableScript();
|
||||
script.addInstruction(I_PUSH_CONST, lookup);
|
||||
script.addInstruction(I_GET_VAR, SCRIPT_VAR_input);
|
||||
script.addInstruction(I_BINARY, I_MEMBER);
|
||||
script.addInstruction(I_CALL, 0);
|
||||
script.addInstruction(I_PUSH_CONST, script_nil);
|
||||
script.addInstruction(I_BINARY, I_OR_ELSE);
|
||||
if (image.isSet() || choice_images.empty()) return;
|
||||
// for, for example:
|
||||
// choice images:
|
||||
// a: {uvw}
|
||||
// b: {xyz}
|
||||
// generate the script:
|
||||
// [a: {uvw}, b: {xyz}][input]() or else nil
|
||||
// or in bytecode
|
||||
// PUSH_CONST [a: {uvw}, b: {xyz}]
|
||||
// GET_VAR input
|
||||
// MEMBER
|
||||
// CALL 0
|
||||
// PUSH_CONST nil
|
||||
// OR_ELSE
|
||||
ScriptCustomCollectionP lookup(new ScriptCustomCollection());
|
||||
FOR_EACH(ci, choice_images) {
|
||||
lookup->key_value[ci.first] = ci.second.getValidScriptP();
|
||||
}
|
||||
Script& script = image.getMutableScript();
|
||||
script.addInstruction(I_PUSH_CONST, lookup);
|
||||
script.addInstruction(I_GET_VAR, SCRIPT_VAR_input);
|
||||
script.addInstruction(I_BINARY, I_MEMBER);
|
||||
script.addInstruction(I_CALL, 0);
|
||||
script.addInstruction(I_PUSH_CONST, script_nil);
|
||||
script.addInstruction(I_BINARY, I_OR_ELSE);
|
||||
}
|
||||
|
||||
int ChoiceStyle::update(Context& ctx) {
|
||||
// Don't update the choice images, leave that to invalidate()
|
||||
int change = Style::update(ctx)
|
||||
| font .update(ctx) * CHANGE_OTHER;
|
||||
if (!choice_images_initialized) {
|
||||
// we only want to do this once because it is rather slow, other updates are handled by dependencies
|
||||
choice_images_initialized = true;
|
||||
FOR_EACH(ci, choice_images) {
|
||||
if (ci.second.update(ctx)) {
|
||||
change |= CHANGE_OTHER;
|
||||
// TODO : remove this thumbnail
|
||||
}
|
||||
}
|
||||
}
|
||||
return change;
|
||||
// Don't update the choice images, leave that to invalidate()
|
||||
int change = Style::update(ctx)
|
||||
| font .update(ctx) * CHANGE_OTHER;
|
||||
if (!choice_images_initialized) {
|
||||
// we only want to do this once because it is rather slow, other updates are handled by dependencies
|
||||
choice_images_initialized = true;
|
||||
FOR_EACH(ci, choice_images) {
|
||||
if (ci.second.update(ctx)) {
|
||||
change |= CHANGE_OTHER;
|
||||
// TODO : remove this thumbnail
|
||||
}
|
||||
}
|
||||
}
|
||||
return change;
|
||||
}
|
||||
void ChoiceStyle::initDependencies(Context& ctx, const Dependency& dep) const {
|
||||
Style::initDependencies(ctx, dep);
|
||||
FOR_EACH_CONST(ci, choice_images) {
|
||||
ci.second.initDependencies(ctx, dep);
|
||||
}
|
||||
Style::initDependencies(ctx, dep);
|
||||
FOR_EACH_CONST(ci, choice_images) {
|
||||
ci.second.initDependencies(ctx, dep);
|
||||
}
|
||||
}
|
||||
void ChoiceStyle::checkContentDependencies(Context& ctx, const Dependency& dep) const {
|
||||
Style::checkContentDependencies(ctx, dep);
|
||||
image.initDependencies(ctx, dep);
|
||||
FOR_EACH_CONST(ci, choice_images) {
|
||||
ci.second.initDependencies(ctx, dep);
|
||||
}
|
||||
Style::checkContentDependencies(ctx, dep);
|
||||
image.initDependencies(ctx, dep);
|
||||
FOR_EACH_CONST(ci, choice_images) {
|
||||
ci.second.initDependencies(ctx, dep);
|
||||
}
|
||||
}
|
||||
void ChoiceStyle::invalidate() {
|
||||
// TODO : this is also done in update(), once should be enough
|
||||
// Update choice images and thumbnails
|
||||
int end = field().choices->lastId();
|
||||
thumbnails_status.resize(end, THUMB_NOT_MADE);
|
||||
for (int i = 0 ; i < end ; ++i) {
|
||||
if (thumbnails_status[i] == THUMB_OK) thumbnails_status[i] = THUMB_CHANGED;
|
||||
}
|
||||
tellListeners(CHANGE_OTHER);
|
||||
// TODO : this is also done in update(), once should be enough
|
||||
// Update choice images and thumbnails
|
||||
int end = field().choices->lastId();
|
||||
thumbnails_status.resize(end, THUMB_NOT_MADE);
|
||||
for (int i = 0 ; i < end ; ++i) {
|
||||
if (thumbnails_status[i] == THUMB_OK) thumbnails_status[i] = THUMB_CHANGED;
|
||||
}
|
||||
tellListeners(CHANGE_OTHER);
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION_ENUM(ChoicePopupStyle) {
|
||||
VALUE_N("dropdown", POPUP_DROPDOWN);
|
||||
VALUE_N("menu", POPUP_MENU);
|
||||
VALUE_N("in place", POPUP_DROPDOWN_IN_PLACE);
|
||||
VALUE_N("dropdown", POPUP_DROPDOWN);
|
||||
VALUE_N("menu", POPUP_MENU);
|
||||
VALUE_N("in place", POPUP_DROPDOWN_IN_PLACE);
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION_ENUM(ChoiceRenderStyle) {
|
||||
VALUE_N("text", RENDER_TEXT);
|
||||
VALUE_N("image", RENDER_IMAGE);
|
||||
VALUE_N("both", RENDER_BOTH);
|
||||
VALUE_N("hidden", RENDER_HIDDEN);
|
||||
VALUE_N("image hidden", RENDER_HIDDEN_IMAGE);
|
||||
VALUE_N("checklist", RENDER_TEXT_CHECKLIST);
|
||||
VALUE_N("image checklist", RENDER_IMAGE_CHECKLIST);
|
||||
VALUE_N("both checklist", RENDER_BOTH_CHECKLIST);
|
||||
VALUE_N("text list", RENDER_TEXT_LIST);
|
||||
VALUE_N("image list", RENDER_IMAGE_LIST);
|
||||
VALUE_N("both list", RENDER_BOTH_LIST);
|
||||
VALUE_N("text", RENDER_TEXT);
|
||||
VALUE_N("image", RENDER_IMAGE);
|
||||
VALUE_N("both", RENDER_BOTH);
|
||||
VALUE_N("hidden", RENDER_HIDDEN);
|
||||
VALUE_N("image hidden", RENDER_HIDDEN_IMAGE);
|
||||
VALUE_N("checklist", RENDER_TEXT_CHECKLIST);
|
||||
VALUE_N("image checklist", RENDER_IMAGE_CHECKLIST);
|
||||
VALUE_N("both checklist", RENDER_BOTH_CHECKLIST);
|
||||
VALUE_N("text list", RENDER_TEXT_LIST);
|
||||
VALUE_N("image list", RENDER_IMAGE_LIST);
|
||||
VALUE_N("both list", RENDER_BOTH_LIST);
|
||||
}
|
||||
|
||||
template <typename T> void reflect_content(T& tag, const ChoiceStyle& cs) {}
|
||||
template <> void reflect_content(GetMember& tag, const ChoiceStyle& cs) {
|
||||
REFLECT_N("content_width", cs.content_width);
|
||||
REFLECT_N("content_height", cs.content_height);
|
||||
REFLECT_N("content_width", cs.content_width);
|
||||
REFLECT_N("content_height", cs.content_height);
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION(ChoiceStyle) {
|
||||
REFLECT_ALIAS(300, "card list colors", "colors card list");
|
||||
REFLECT_BASE(Style);
|
||||
REFLECT(popup_style);
|
||||
REFLECT(render_style);
|
||||
REFLECT(combine);
|
||||
REFLECT(alignment);
|
||||
REFLECT(font);
|
||||
REFLECT(image);
|
||||
REFLECT(choice_images);
|
||||
reflect_content(tag, *this);
|
||||
REFLECT_ALIAS(300, "card list colors", "colors card list");
|
||||
REFLECT_BASE(Style);
|
||||
REFLECT(popup_style);
|
||||
REFLECT(render_style);
|
||||
REFLECT(combine);
|
||||
REFLECT(alignment);
|
||||
REFLECT(font);
|
||||
REFLECT(image);
|
||||
REFLECT(choice_images);
|
||||
reflect_content(tag, *this);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : ChoiceValue
|
||||
|
||||
ChoiceValue::ChoiceValue(const ChoiceFieldP& field, bool initial_first_choice)
|
||||
: Value(field)
|
||||
, value( !field->initial.empty() ? field->initial
|
||||
: initial_first_choice ? field->choices->choiceName(0)
|
||||
: _("")
|
||||
, true)
|
||||
: Value(field)
|
||||
, value( !field->initial.empty() ? field->initial
|
||||
: initial_first_choice ? field->choices->choiceName(0)
|
||||
: _("")
|
||||
, true)
|
||||
{}
|
||||
|
||||
String ChoiceValue::toString() const {
|
||||
return value();
|
||||
return value();
|
||||
}
|
||||
bool ChoiceValue::update(Context& ctx) {
|
||||
bool change = field().default_script.invokeOnDefault(ctx, value)
|
||||
| field(). script.invokeOn(ctx, value);
|
||||
Value::update(ctx);
|
||||
return change;
|
||||
bool change = field().default_script.invokeOnDefault(ctx, value)
|
||||
| field(). script.invokeOn(ctx, value);
|
||||
Value::update(ctx);
|
||||
return change;
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION_NAMELESS(ChoiceValue) {
|
||||
if (fieldP->save_value || tag.scripting() || tag.reading()) REFLECT_NAMELESS(value);
|
||||
if (fieldP->save_value || tag.scripting() || tag.reading()) REFLECT_NAMELESS(value);
|
||||
}
|
||||
|
||||
INSTANTIATE_REFLECTION_NAMELESS(ChoiceValue)
|
||||
|
||||
+117
-117
@@ -27,139 +27,139 @@ DECLARE_POINTER_TYPE(ChoiceValue);
|
||||
/// A field that contains a list of choices
|
||||
class ChoiceField : public Field {
|
||||
public:
|
||||
ChoiceField();
|
||||
DECLARE_FIELD_TYPE(Choice);
|
||||
|
||||
class Choice;
|
||||
typedef intrusive_ptr<Choice> ChoiceP;
|
||||
|
||||
ChoiceP choices; ///< A choice group of possible choices
|
||||
OptionalScript script; ///< Script to apply to all values
|
||||
OptionalScript default_script; ///< Script that generates the default value
|
||||
String initial; ///< Initial choice of a new value, or ""
|
||||
String default_name; ///< Name of "default" value
|
||||
map<String,Color> choice_colors; ///< Colors for the various choices (when color_cardlist)
|
||||
map<String,Color> choice_colors_cardlist; ///< Colors for the various choices, for in the card list
|
||||
|
||||
virtual void initDependencies(Context&, const Dependency&) const;
|
||||
ChoiceField();
|
||||
DECLARE_FIELD_TYPE(Choice);
|
||||
|
||||
class Choice;
|
||||
typedef intrusive_ptr<Choice> ChoiceP;
|
||||
|
||||
ChoiceP choices; ///< A choice group of possible choices
|
||||
OptionalScript script; ///< Script to apply to all values
|
||||
OptionalScript default_script; ///< Script that generates the default value
|
||||
String initial; ///< Initial choice of a new value, or ""
|
||||
String default_name; ///< Name of "default" value
|
||||
map<String,Color> choice_colors; ///< Colors for the various choices (when color_cardlist)
|
||||
map<String,Color> choice_colors_cardlist; ///< Colors for the various choices, for in the card list
|
||||
|
||||
virtual void initDependencies(Context&, const Dependency&) const;
|
||||
};
|
||||
|
||||
|
||||
enum ChoiceChoiceType {
|
||||
CHOICE_TYPE_CHECK,
|
||||
CHOICE_TYPE_RADIO
|
||||
CHOICE_TYPE_CHECK,
|
||||
CHOICE_TYPE_RADIO
|
||||
};
|
||||
|
||||
/// An item that can be chosen for this field
|
||||
class ChoiceField::Choice : public IntrusivePtrBase<ChoiceField::Choice> {
|
||||
public:
|
||||
Choice();
|
||||
Choice(const String& name);
|
||||
|
||||
String name; ///< Name/value of the item
|
||||
String default_name; ///< A default item, if this is a group and default_name.empty() there is no default
|
||||
vector<ChoiceP> choices; ///< Choices and sub groups in this group
|
||||
bool line_below; ///< Show a line after this item?
|
||||
Scriptable<bool> enabled; ///< Is this item enabled?
|
||||
ChoiceChoiceType type; ///< How should this item be shown, only for multiple choice fields
|
||||
/// First item-id in this group (can be the default item)
|
||||
/** Item-ids are consecutive integers, a group uses all ids [first_id..lastId()).
|
||||
* The top level group has first_id 0.
|
||||
*/
|
||||
int first_id;
|
||||
|
||||
/// Is this a group?
|
||||
bool isGroup() const;
|
||||
/// Can this Choice itself be chosen?
|
||||
/** For a single choice this is always true, for a group only if it has a default choice */
|
||||
bool hasDefault() const;
|
||||
|
||||
/// Initialize the first_id of children
|
||||
/** @pre first_id is set
|
||||
* Returns lastId()
|
||||
*/
|
||||
int initIds();
|
||||
/// Number of choices in this group (and subgroups), 1 if it is not a group
|
||||
/** The default choice also counts */
|
||||
int choiceCount() const;
|
||||
/// item-id just beyond the end of this group
|
||||
int lastId() const;
|
||||
|
||||
/// item-id of a choice, given the internal name
|
||||
/** If the id is not in this group, returns -1 */
|
||||
int choiceId(const String& name) const;
|
||||
/// Internal name of a choice
|
||||
/** The internal name is formed by concatenating the names of all parents, separated by spaces.
|
||||
* Returns "" if id is not in this group
|
||||
*/
|
||||
String choiceName(int id) const;
|
||||
/// Formated name of a choice.
|
||||
/** Intended for use in menu structures, so it doesn't include the group name for children.
|
||||
* Returns "" if id is not in this group.
|
||||
*/
|
||||
String choiceNameNice(int id) const;
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
Choice();
|
||||
Choice(const String& name);
|
||||
|
||||
String name; ///< Name/value of the item
|
||||
String default_name; ///< A default item, if this is a group and default_name.empty() there is no default
|
||||
vector<ChoiceP> choices; ///< Choices and sub groups in this group
|
||||
bool line_below; ///< Show a line after this item?
|
||||
Scriptable<bool> enabled; ///< Is this item enabled?
|
||||
ChoiceChoiceType type; ///< How should this item be shown, only for multiple choice fields
|
||||
/// First item-id in this group (can be the default item)
|
||||
/** Item-ids are consecutive integers, a group uses all ids [first_id..lastId()).
|
||||
* The top level group has first_id 0.
|
||||
*/
|
||||
int first_id;
|
||||
|
||||
/// Is this a group?
|
||||
bool isGroup() const;
|
||||
/// Can this Choice itself be chosen?
|
||||
/** For a single choice this is always true, for a group only if it has a default choice */
|
||||
bool hasDefault() const;
|
||||
|
||||
/// Initialize the first_id of children
|
||||
/** @pre first_id is set
|
||||
* Returns lastId()
|
||||
*/
|
||||
int initIds();
|
||||
/// Number of choices in this group (and subgroups), 1 if it is not a group
|
||||
/** The default choice also counts */
|
||||
int choiceCount() const;
|
||||
/// item-id just beyond the end of this group
|
||||
int lastId() const;
|
||||
|
||||
/// item-id of a choice, given the internal name
|
||||
/** If the id is not in this group, returns -1 */
|
||||
int choiceId(const String& name) const;
|
||||
/// Internal name of a choice
|
||||
/** The internal name is formed by concatenating the names of all parents, separated by spaces.
|
||||
* Returns "" if id is not in this group
|
||||
*/
|
||||
String choiceName(int id) const;
|
||||
/// Formated name of a choice.
|
||||
/** Intended for use in menu structures, so it doesn't include the group name for children.
|
||||
* Returns "" if id is not in this group.
|
||||
*/
|
||||
String choiceNameNice(int id) const;
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : ChoiceStyle
|
||||
|
||||
// How should the menu for a choice look?
|
||||
enum ChoicePopupStyle
|
||||
{ POPUP_MENU
|
||||
, POPUP_DROPDOWN
|
||||
, POPUP_DROPDOWN_IN_PLACE
|
||||
{ POPUP_MENU
|
||||
, POPUP_DROPDOWN
|
||||
, POPUP_DROPDOWN_IN_PLACE
|
||||
};
|
||||
// How should a choice value be rendered?
|
||||
enum ChoiceRenderStyle
|
||||
{ RENDER_TEXT = 0x01 // render the name as text
|
||||
, RENDER_IMAGE = 0x10 // render an image
|
||||
, RENDER_HIDDEN = 0x20 // don't render anything, only have a menu
|
||||
, RENDER_CHECKLIST = 0x100 // render as a checklist, intended for multiple choice
|
||||
, RENDER_LIST = 0x200 // render as a list of images/text, intended for multiple choice
|
||||
, RENDER_BOTH = RENDER_TEXT | RENDER_IMAGE
|
||||
, RENDER_HIDDEN_IMAGE = RENDER_HIDDEN | RENDER_IMAGE
|
||||
, RENDER_TEXT_CHECKLIST = RENDER_CHECKLIST | RENDER_TEXT
|
||||
, RENDER_IMAGE_CHECKLIST = RENDER_CHECKLIST | RENDER_IMAGE
|
||||
, RENDER_BOTH_CHECKLIST = RENDER_CHECKLIST | RENDER_BOTH
|
||||
, RENDER_TEXT_LIST = RENDER_LIST | RENDER_TEXT
|
||||
, RENDER_IMAGE_LIST = RENDER_LIST | RENDER_IMAGE
|
||||
, RENDER_BOTH_LIST = RENDER_LIST | RENDER_BOTH
|
||||
{ RENDER_TEXT = 0x01 // render the name as text
|
||||
, RENDER_IMAGE = 0x10 // render an image
|
||||
, RENDER_HIDDEN = 0x20 // don't render anything, only have a menu
|
||||
, RENDER_CHECKLIST = 0x100 // render as a checklist, intended for multiple choice
|
||||
, RENDER_LIST = 0x200 // render as a list of images/text, intended for multiple choice
|
||||
, RENDER_BOTH = RENDER_TEXT | RENDER_IMAGE
|
||||
, RENDER_HIDDEN_IMAGE = RENDER_HIDDEN | RENDER_IMAGE
|
||||
, RENDER_TEXT_CHECKLIST = RENDER_CHECKLIST | RENDER_TEXT
|
||||
, RENDER_IMAGE_CHECKLIST = RENDER_CHECKLIST | RENDER_IMAGE
|
||||
, RENDER_BOTH_CHECKLIST = RENDER_CHECKLIST | RENDER_BOTH
|
||||
, RENDER_TEXT_LIST = RENDER_LIST | RENDER_TEXT
|
||||
, RENDER_IMAGE_LIST = RENDER_LIST | RENDER_IMAGE
|
||||
, RENDER_BOTH_LIST = RENDER_LIST | RENDER_BOTH
|
||||
};
|
||||
|
||||
enum ThumbnailStatus
|
||||
{ THUMB_NOT_MADE // there is no image
|
||||
, THUMB_OK // image is ok
|
||||
, THUMB_CHANGED // there is an image, but it may need to be updated
|
||||
{ THUMB_NOT_MADE // there is no image
|
||||
, THUMB_OK // image is ok
|
||||
, THUMB_CHANGED // there is an image, but it may need to be updated
|
||||
};
|
||||
|
||||
/// The Style for a ChoiceField
|
||||
class ChoiceStyle : public Style {
|
||||
public:
|
||||
ChoiceStyle(const ChoiceFieldP& field);
|
||||
DECLARE_STYLE_TYPE(Choice);
|
||||
~ChoiceStyle();
|
||||
|
||||
ChoicePopupStyle popup_style; ///< Style of popups/menus
|
||||
ChoiceRenderStyle render_style; ///< Style of rendering
|
||||
Font font; ///< Font for drawing text (when RENDER_TEXT)
|
||||
CachedScriptableImage image; ///< Image to draw (when RENDER_IMAGE)
|
||||
map<String,ScriptableImage> choice_images; ///< Images for the various choices (when RENDER_IMAGE)
|
||||
bool choice_images_initialized;
|
||||
ImageCombine combine; ///< Combining mode for drawing the images
|
||||
Alignment alignment; ///< Alignment of images
|
||||
wxImageList* thumbnails; ///< Thumbnails for the choices
|
||||
vector<ThumbnailStatus> thumbnails_status; ///< Which thumbnails are up to date?
|
||||
// information from image rendering
|
||||
double content_width, content_height; ///< Size of the rendered image/text
|
||||
|
||||
/// Initialize image from choice_images
|
||||
void initImage();
|
||||
|
||||
virtual int update(Context&);
|
||||
virtual void initDependencies(Context&, const Dependency&) const;
|
||||
virtual void checkContentDependencies(Context&, const Dependency&) const;
|
||||
virtual void invalidate();
|
||||
ChoiceStyle(const ChoiceFieldP& field);
|
||||
DECLARE_STYLE_TYPE(Choice);
|
||||
~ChoiceStyle();
|
||||
|
||||
ChoicePopupStyle popup_style; ///< Style of popups/menus
|
||||
ChoiceRenderStyle render_style; ///< Style of rendering
|
||||
Font font; ///< Font for drawing text (when RENDER_TEXT)
|
||||
CachedScriptableImage image; ///< Image to draw (when RENDER_IMAGE)
|
||||
map<String,ScriptableImage> choice_images; ///< Images for the various choices (when RENDER_IMAGE)
|
||||
bool choice_images_initialized;
|
||||
ImageCombine combine; ///< Combining mode for drawing the images
|
||||
Alignment alignment; ///< Alignment of images
|
||||
wxImageList* thumbnails; ///< Thumbnails for the choices
|
||||
vector<ThumbnailStatus> thumbnails_status; ///< Which thumbnails are up to date?
|
||||
// information from image rendering
|
||||
double content_width, content_height; ///< Size of the rendered image/text
|
||||
|
||||
/// Initialize image from choice_images
|
||||
void initImage();
|
||||
|
||||
virtual int update(Context&);
|
||||
virtual void initDependencies(Context&, const Dependency&) const;
|
||||
virtual void checkContentDependencies(Context&, const Dependency&) const;
|
||||
virtual void invalidate();
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : ChoiceValue
|
||||
@@ -167,16 +167,16 @@ class ChoiceStyle : public Style {
|
||||
/// The Value in a ChoiceField
|
||||
class ChoiceValue : public Value {
|
||||
public:
|
||||
/// Create a value for the given field
|
||||
/** If initial_first_choice then the first choice should be used in the absence of
|
||||
an explicit initial value
|
||||
*/
|
||||
ChoiceValue(const ChoiceFieldP& field, bool initial_first_choice = true);
|
||||
DECLARE_VALUE_TYPE(Choice, Defaultable<String>);
|
||||
|
||||
ValueType value; /// The name of the selected choice
|
||||
|
||||
virtual bool update(Context&);
|
||||
/// Create a value for the given field
|
||||
/** If initial_first_choice then the first choice should be used in the absence of
|
||||
an explicit initial value
|
||||
*/
|
||||
ChoiceValue(const ChoiceFieldP& field, bool initial_first_choice = true);
|
||||
DECLARE_VALUE_TYPE(Choice, Defaultable<String>);
|
||||
|
||||
ValueType value; /// The name of the selected choice
|
||||
|
||||
virtual bool update(Context&);
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
+49
-49
@@ -15,90 +15,90 @@ DECLARE_TYPEOF_COLLECTION(ColorField::ChoiceP);
|
||||
// ----------------------------------------------------------------------------- : ColorField
|
||||
|
||||
ColorField::ColorField()
|
||||
: allow_custom(true)
|
||||
, default_name(_("Default"))
|
||||
: allow_custom(true)
|
||||
, default_name(_("Default"))
|
||||
{}
|
||||
|
||||
IMPLEMENT_FIELD_TYPE(Color, "color");
|
||||
|
||||
void ColorField::initDependencies(Context& ctx, const Dependency& dep) const {
|
||||
Field ::initDependencies(ctx, dep);
|
||||
script .initDependencies(ctx, dep);
|
||||
default_script.initDependencies(ctx, dep);
|
||||
Field ::initDependencies(ctx, dep);
|
||||
script .initDependencies(ctx, dep);
|
||||
default_script.initDependencies(ctx, dep);
|
||||
}
|
||||
|
||||
|
||||
IMPLEMENT_REFLECTION(ColorField) {
|
||||
REFLECT_BASE(Field);
|
||||
REFLECT(script);
|
||||
REFLECT_N("default", default_script);
|
||||
REFLECT(initial);
|
||||
REFLECT(default_name);
|
||||
REFLECT(allow_custom);
|
||||
REFLECT(choices);
|
||||
REFLECT_BASE(Field);
|
||||
REFLECT(script);
|
||||
REFLECT_N("default", default_script);
|
||||
REFLECT(initial);
|
||||
REFLECT(default_name);
|
||||
REFLECT(allow_custom);
|
||||
REFLECT(choices);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : ColorField::Choice
|
||||
|
||||
IMPLEMENT_REFLECTION(ColorField::Choice) {
|
||||
if (tag.reading() && !tag.isComplex()) {
|
||||
REFLECT_NAMELESS(name);
|
||||
color = parse_color(name);
|
||||
} else {
|
||||
REFLECT(name);
|
||||
REFLECT(color);
|
||||
}
|
||||
if (tag.reading() && !tag.isComplex()) {
|
||||
REFLECT_NAMELESS(name);
|
||||
color = parse_color(name);
|
||||
} else {
|
||||
REFLECT(name);
|
||||
REFLECT(color);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : ColorStyle
|
||||
|
||||
ColorStyle::ColorStyle(const ColorFieldP& field)
|
||||
: Style(field)
|
||||
, radius(0)
|
||||
, left_width(100000), right_width (100000)
|
||||
, top_width (100000), bottom_width(100000)
|
||||
, combine(COMBINE_NORMAL)
|
||||
: Style(field)
|
||||
, radius(0)
|
||||
, left_width(100000), right_width (100000)
|
||||
, top_width (100000), bottom_width(100000)
|
||||
, combine(COMBINE_NORMAL)
|
||||
{}
|
||||
|
||||
IMPLEMENT_REFLECTION(ColorStyle) {
|
||||
REFLECT_BASE(Style);
|
||||
REFLECT(radius);
|
||||
REFLECT(left_width);
|
||||
REFLECT(right_width);
|
||||
REFLECT(top_width);
|
||||
REFLECT(bottom_width);
|
||||
REFLECT(combine);
|
||||
REFLECT_BASE(Style);
|
||||
REFLECT(radius);
|
||||
REFLECT(left_width);
|
||||
REFLECT(right_width);
|
||||
REFLECT(top_width);
|
||||
REFLECT(bottom_width);
|
||||
REFLECT(combine);
|
||||
}
|
||||
|
||||
int ColorStyle::update(Context& ctx) {
|
||||
return Style::update(ctx);
|
||||
return Style::update(ctx);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : ColorValue
|
||||
|
||||
ColorValue::ColorValue(const ColorFieldP& field)
|
||||
: Value(field)
|
||||
, value( !field->initial.isDefault() ? field->initial()
|
||||
: !field->choices.empty() ? field->choices[0]->color
|
||||
: *wxBLACK
|
||||
, true)
|
||||
: Value(field)
|
||||
, value( !field->initial.isDefault() ? field->initial()
|
||||
: !field->choices.empty() ? field->choices[0]->color
|
||||
: *wxBLACK
|
||||
, true)
|
||||
{}
|
||||
|
||||
|
||||
String ColorValue::toString() const {
|
||||
if (value.isDefault()) return field().default_name;
|
||||
// is this a named color?
|
||||
FOR_EACH(c, field().choices) {
|
||||
if (value() == c->color) return c->name;
|
||||
}
|
||||
return _("<color>");
|
||||
if (value.isDefault()) return field().default_name;
|
||||
// is this a named color?
|
||||
FOR_EACH(c, field().choices) {
|
||||
if (value() == c->color) return c->name;
|
||||
}
|
||||
return _("<color>");
|
||||
}
|
||||
bool ColorValue::update(Context& ctx) {
|
||||
bool change = field().default_script.invokeOnDefault(ctx, value)
|
||||
| field(). script.invokeOn(ctx, value);
|
||||
Value::update(ctx);
|
||||
return change;
|
||||
bool change = field().default_script.invokeOnDefault(ctx, value)
|
||||
| field(). script.invokeOn(ctx, value);
|
||||
Value::update(ctx);
|
||||
return change;
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION_NAMELESS(ColorValue) {
|
||||
if (fieldP->save_value || tag.scripting() || tag.reading()) REFLECT_NAMELESS(value);
|
||||
if (fieldP->save_value || tag.scripting() || tag.reading()) REFLECT_NAMELESS(value);
|
||||
}
|
||||
|
||||
+35
-35
@@ -24,29 +24,29 @@ DECLARE_POINTER_TYPE(ColorValue);
|
||||
/// A field for color values, it contains a list of choices for colors
|
||||
class ColorField : public Field {
|
||||
public:
|
||||
ColorField();
|
||||
DECLARE_FIELD_TYPE(Color);
|
||||
|
||||
class Choice;
|
||||
typedef intrusive_ptr<Choice> ChoiceP;
|
||||
|
||||
OptionalScript script; ///< Script to apply to all values
|
||||
OptionalScript default_script; ///< Script that generates the default value
|
||||
vector<ChoiceP> choices; ///< Color choices available
|
||||
bool allow_custom; ///< Are colors not in the list of choices allowed?
|
||||
Defaultable<Color> initial; ///< Initial choice of a new value, if not set the first choice is used
|
||||
String default_name; ///< Name of "default" value
|
||||
|
||||
virtual void initDependencies(Context&, const Dependency&) const;
|
||||
ColorField();
|
||||
DECLARE_FIELD_TYPE(Color);
|
||||
|
||||
class Choice;
|
||||
typedef intrusive_ptr<Choice> ChoiceP;
|
||||
|
||||
OptionalScript script; ///< Script to apply to all values
|
||||
OptionalScript default_script; ///< Script that generates the default value
|
||||
vector<ChoiceP> choices; ///< Color choices available
|
||||
bool allow_custom; ///< Are colors not in the list of choices allowed?
|
||||
Defaultable<Color> initial; ///< Initial choice of a new value, if not set the first choice is used
|
||||
String default_name; ///< Name of "default" value
|
||||
|
||||
virtual void initDependencies(Context&, const Dependency&) const;
|
||||
};
|
||||
|
||||
/// A color that can be chosen for this field
|
||||
class ColorField::Choice : public IntrusivePtrBase<ColorField::Choice> {
|
||||
public:
|
||||
String name; ///< Name of the color
|
||||
Color color; ///< The actual color
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
String name; ///< Name of the color
|
||||
Color color; ///< The actual color
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : ColorStyle
|
||||
@@ -54,17 +54,17 @@ class ColorField::Choice : public IntrusivePtrBase<ColorField::Choice> {
|
||||
/// The Style for a ColorField
|
||||
class ColorStyle : public Style {
|
||||
public:
|
||||
ColorStyle(const ColorFieldP& field);
|
||||
DECLARE_STYLE_TYPE(Color);
|
||||
|
||||
double radius; ///< Radius of round corners
|
||||
double left_width; ///< Width of the colored region on the left side
|
||||
double right_width; ///< Width of the colored region on the right side
|
||||
double top_width; ///< Width of the colored region on the top side
|
||||
double bottom_width; ///< Width of the colored region on the bottom side
|
||||
ImageCombine combine; ///< How to combine image with the background
|
||||
|
||||
virtual int update(Context&);
|
||||
ColorStyle(const ColorFieldP& field);
|
||||
DECLARE_STYLE_TYPE(Color);
|
||||
|
||||
double radius; ///< Radius of round corners
|
||||
double left_width; ///< Width of the colored region on the left side
|
||||
double right_width; ///< Width of the colored region on the right side
|
||||
double top_width; ///< Width of the colored region on the top side
|
||||
double bottom_width; ///< Width of the colored region on the bottom side
|
||||
ImageCombine combine; ///< How to combine image with the background
|
||||
|
||||
virtual int update(Context&);
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : ColorValue
|
||||
@@ -72,12 +72,12 @@ class ColorStyle : public Style {
|
||||
/// The Value in a ColorField
|
||||
class ColorValue : public Value {
|
||||
public:
|
||||
ColorValue(const ColorFieldP& field);
|
||||
DECLARE_VALUE_TYPE(Color, Defaultable<Color>);
|
||||
|
||||
ValueType value; ///< The value
|
||||
|
||||
virtual bool update(Context&);
|
||||
ColorValue(const ColorFieldP& field);
|
||||
DECLARE_VALUE_TYPE(Color, Defaultable<Color>);
|
||||
|
||||
ValueType value; ///< The value
|
||||
|
||||
virtual bool update(Context&);
|
||||
};
|
||||
|
||||
|
||||
|
||||
+10
-10
@@ -15,38 +15,38 @@
|
||||
IMPLEMENT_FIELD_TYPE(Image, "image");
|
||||
|
||||
IMPLEMENT_REFLECTION(ImageField) {
|
||||
REFLECT_BASE(Field);
|
||||
REFLECT_BASE(Field);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------- : ImageStyle
|
||||
|
||||
IMPLEMENT_REFLECTION(ImageStyle) {
|
||||
REFLECT_BASE(Style);
|
||||
REFLECT_N("default", default_image);
|
||||
REFLECT_BASE(Style);
|
||||
REFLECT_N("default", default_image);
|
||||
}
|
||||
|
||||
int ImageStyle::update(Context& ctx) {
|
||||
return Style ::update(ctx)
|
||||
| default_image.update(ctx) * CHANGE_DEFAULT;
|
||||
return Style ::update(ctx)
|
||||
| default_image.update(ctx) * CHANGE_DEFAULT;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : ImageValue
|
||||
|
||||
String ImageValue::toString() const {
|
||||
return filename.empty() ? wxEmptyString : _("<image>");
|
||||
return filename.empty() ? wxEmptyString : _("<image>");
|
||||
}
|
||||
|
||||
// custom reflection: convert to ScriptImageP for scripting
|
||||
|
||||
void ImageValue::reflect(Reader& tag) {
|
||||
tag.handle(filename);
|
||||
tag.handle(filename);
|
||||
}
|
||||
void ImageValue::reflect(Writer& tag) {
|
||||
if (fieldP->save_value) tag.handle(filename);
|
||||
if (fieldP->save_value) tag.handle(filename);
|
||||
}
|
||||
void ImageValue::reflect(GetMember& tag) {}
|
||||
void ImageValue::reflect(GetDefaultMember& tag) {
|
||||
// convert to ScriptImageP for scripting
|
||||
tag.handle( (ScriptValueP)intrusive(new ImageValueToImage(filename, last_update)) );
|
||||
// convert to ScriptImageP for scripting
|
||||
tag.handle( (ScriptValueP)intrusive(new ImageValueToImage(filename, last_update)) );
|
||||
}
|
||||
|
||||
+13
-13
@@ -23,8 +23,8 @@ DECLARE_POINTER_TYPE(ImageValue);
|
||||
/// A field for image values
|
||||
class ImageField : public Field {
|
||||
public:
|
||||
// no extra data
|
||||
DECLARE_FIELD_TYPE(Image);
|
||||
// no extra data
|
||||
DECLARE_FIELD_TYPE(Image);
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : ImageStyle
|
||||
@@ -32,12 +32,12 @@ class ImageField : public Field {
|
||||
/// The Style for a ImageField
|
||||
class ImageStyle : public Style {
|
||||
public:
|
||||
inline ImageStyle(const ImageFieldP& field) : Style(field) {}
|
||||
DECLARE_STYLE_TYPE(Image);
|
||||
|
||||
ScriptableImage default_image; ///< Placeholder
|
||||
|
||||
virtual int update(Context&);
|
||||
inline ImageStyle(const ImageFieldP& field) : Style(field) {}
|
||||
DECLARE_STYLE_TYPE(Image);
|
||||
|
||||
ScriptableImage default_image; ///< Placeholder
|
||||
|
||||
virtual int update(Context&);
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : ImageValue
|
||||
@@ -45,11 +45,11 @@ class ImageStyle : public Style {
|
||||
/// The Value in a ImageField, i.e. an image
|
||||
class ImageValue : public Value {
|
||||
public:
|
||||
inline ImageValue(const ImageFieldP& field) : Value(field) {}
|
||||
DECLARE_VALUE_TYPE(Image, FileName);
|
||||
|
||||
ValueType filename; ///< Filename of the image (in the current package), or ""
|
||||
Age last_update; ///< When was the image last changed?
|
||||
inline ImageValue(const ImageFieldP& field) : Value(field) {}
|
||||
DECLARE_VALUE_TYPE(Image, FileName);
|
||||
|
||||
ValueType filename; ///< Filename of the image (in the current package), or ""
|
||||
Age last_update; ///< When was the image last changed?
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
@@ -15,60 +15,60 @@
|
||||
IMPLEMENT_FIELD_TYPE(Info, "info");
|
||||
|
||||
void InfoField::initDependencies(Context& ctx, const Dependency& dep) const {
|
||||
Field ::initDependencies(ctx, dep);
|
||||
script. initDependencies(ctx, dep);
|
||||
Field ::initDependencies(ctx, dep);
|
||||
script. initDependencies(ctx, dep);
|
||||
}
|
||||
|
||||
|
||||
IMPLEMENT_REFLECTION(InfoField) {
|
||||
REFLECT_BASE(Field);
|
||||
REFLECT(script);
|
||||
REFLECT_BASE(Field);
|
||||
REFLECT(script);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : InfoStyle
|
||||
|
||||
InfoStyle::InfoStyle(const InfoFieldP& field)
|
||||
: Style(field)
|
||||
, alignment(ALIGN_TOP_LEFT)
|
||||
, padding_left (2)
|
||||
, padding_right (2)
|
||||
, padding_top (2)
|
||||
, padding_bottom(2)
|
||||
, background_color(255,255,255)
|
||||
: Style(field)
|
||||
, alignment(ALIGN_TOP_LEFT)
|
||||
, padding_left (2)
|
||||
, padding_right (2)
|
||||
, padding_top (2)
|
||||
, padding_bottom(2)
|
||||
, background_color(255,255,255)
|
||||
{}
|
||||
|
||||
int InfoStyle::update(Context& ctx) {
|
||||
return Style ::update(ctx)
|
||||
| font .update(ctx) * CHANGE_OTHER;
|
||||
return Style ::update(ctx)
|
||||
| font .update(ctx) * CHANGE_OTHER;
|
||||
}
|
||||
void InfoStyle::initDependencies(Context& ctx, const Dependency& dep) const {
|
||||
Style ::initDependencies(ctx, dep);
|
||||
// font .initDependencies(ctx, dep);
|
||||
Style ::initDependencies(ctx, dep);
|
||||
// font .initDependencies(ctx, dep);
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION(InfoStyle) {
|
||||
REFLECT_BASE(Style);
|
||||
REFLECT(font);
|
||||
REFLECT(alignment);
|
||||
REFLECT(padding_left);
|
||||
REFLECT(padding_right);
|
||||
REFLECT(padding_top);
|
||||
REFLECT(padding_bottom);
|
||||
REFLECT(background_color);
|
||||
REFLECT_BASE(Style);
|
||||
REFLECT(font);
|
||||
REFLECT(alignment);
|
||||
REFLECT(padding_left);
|
||||
REFLECT(padding_right);
|
||||
REFLECT(padding_top);
|
||||
REFLECT(padding_bottom);
|
||||
REFLECT(background_color);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : InfoValue
|
||||
|
||||
String InfoValue::toString() const {
|
||||
return value;
|
||||
return value;
|
||||
}
|
||||
bool InfoValue::update(Context& ctx) {
|
||||
if (value.empty()) value = field().name;
|
||||
bool change = field().script.invokeOn(ctx, value);
|
||||
Value::update(ctx);
|
||||
return change;
|
||||
if (value.empty()) value = field().name;
|
||||
bool change = field().script.invokeOn(ctx, value);
|
||||
Value::update(ctx);
|
||||
return change;
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION_NAMELESS(InfoValue) {
|
||||
// never save
|
||||
// never save
|
||||
}
|
||||
|
||||
@@ -26,12 +26,12 @@ DECLARE_POINTER_TYPE(InfoValue);
|
||||
*/
|
||||
class InfoField : public Field {
|
||||
public:
|
||||
InfoField() { editable = false; }
|
||||
DECLARE_FIELD_TYPE(Text);
|
||||
|
||||
OptionalScript script; ///< Script to apply to all values
|
||||
|
||||
virtual void initDependencies(Context&, const Dependency&) const;
|
||||
InfoField() { editable = false; }
|
||||
DECLARE_FIELD_TYPE(Text);
|
||||
|
||||
OptionalScript script; ///< Script to apply to all values
|
||||
|
||||
virtual void initDependencies(Context&, const Dependency&) const;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : InfoStyle
|
||||
@@ -39,17 +39,17 @@ class InfoField : public Field {
|
||||
/// The Style for a InfoField
|
||||
class InfoStyle : public Style {
|
||||
public:
|
||||
InfoStyle(const InfoFieldP&);
|
||||
DECLARE_STYLE_TYPE(Info);
|
||||
|
||||
Font font; ///< Font to use for the text
|
||||
Alignment alignment; ///< Alignment inside the box
|
||||
double padding_left, padding_right; ///< Padding
|
||||
double padding_top, padding_bottom;
|
||||
Color background_color;
|
||||
|
||||
virtual int update(Context&);
|
||||
virtual void initDependencies(Context&, const Dependency&) const;
|
||||
InfoStyle(const InfoFieldP&);
|
||||
DECLARE_STYLE_TYPE(Info);
|
||||
|
||||
Font font; ///< Font to use for the text
|
||||
Alignment alignment; ///< Alignment inside the box
|
||||
double padding_left, padding_right; ///< Padding
|
||||
double padding_top, padding_bottom;
|
||||
Color background_color;
|
||||
|
||||
virtual int update(Context&);
|
||||
virtual void initDependencies(Context&, const Dependency&) const;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : InfoValue
|
||||
@@ -57,12 +57,12 @@ class InfoStyle : public Style {
|
||||
/// The Value in a InfoField
|
||||
class InfoValue : public Value {
|
||||
public:
|
||||
inline InfoValue(const InfoFieldP& field) : Value(field) {}
|
||||
DECLARE_VALUE_TYPE(Info, String);
|
||||
|
||||
ValueType value;
|
||||
|
||||
virtual bool update(Context&);
|
||||
inline InfoValue(const InfoFieldP& field) : Value(field) {}
|
||||
DECLARE_VALUE_TYPE(Info, String);
|
||||
|
||||
ValueType value;
|
||||
|
||||
virtual bool update(Context&);
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
@@ -12,103 +12,103 @@
|
||||
// ----------------------------------------------------------------------------- : MultipleChoiceField
|
||||
|
||||
MultipleChoiceField::MultipleChoiceField()
|
||||
: minimum_selection(0)
|
||||
, maximum_selection(1000000)
|
||||
: minimum_selection(0)
|
||||
, maximum_selection(1000000)
|
||||
{}
|
||||
|
||||
IMPLEMENT_FIELD_TYPE(MultipleChoice, "multiple choice");
|
||||
|
||||
IMPLEMENT_REFLECTION(MultipleChoiceField) {
|
||||
REFLECT_BASE(ChoiceField);
|
||||
REFLECT(minimum_selection);
|
||||
REFLECT(maximum_selection);
|
||||
REFLECT(empty_choice);
|
||||
REFLECT_BASE(ChoiceField);
|
||||
REFLECT(minimum_selection);
|
||||
REFLECT(maximum_selection);
|
||||
REFLECT(empty_choice);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : MultipleChoiceStyle
|
||||
|
||||
MultipleChoiceStyle::MultipleChoiceStyle(const MultipleChoiceFieldP& field)
|
||||
: ChoiceStyle(field)
|
||||
, direction(LEFT_TO_RIGHT)
|
||||
, spacing(0)
|
||||
: ChoiceStyle(field)
|
||||
, direction(LEFT_TO_RIGHT)
|
||||
, spacing(0)
|
||||
{}
|
||||
|
||||
IMPLEMENT_REFLECTION(MultipleChoiceStyle) {
|
||||
REFLECT_BASE(ChoiceStyle);
|
||||
REFLECT(direction);
|
||||
REFLECT(spacing);
|
||||
REFLECT_BASE(ChoiceStyle);
|
||||
REFLECT(direction);
|
||||
REFLECT(spacing);
|
||||
}
|
||||
|
||||
int MultipleChoiceStyle::update(Context& ctx) {
|
||||
return ChoiceStyle::update(ctx)
|
||||
| direction.update(ctx) * CHANGE_OTHER
|
||||
| spacing.update(ctx) * CHANGE_OTHER;
|
||||
return ChoiceStyle::update(ctx)
|
||||
| direction.update(ctx) * CHANGE_OTHER
|
||||
| spacing.update(ctx) * CHANGE_OTHER;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : MultipleChoiceValue
|
||||
|
||||
IMPLEMENT_REFLECTION_NAMELESS(MultipleChoiceValue) {
|
||||
REFLECT_BASE(ChoiceValue);
|
||||
REFLECT_BASE(ChoiceValue);
|
||||
}
|
||||
|
||||
bool MultipleChoiceValue::update(Context& ctx) {
|
||||
String old_value = value();
|
||||
ctx.setVariable(_("last change"), to_script(last_change));
|
||||
ChoiceValue::update(ctx);
|
||||
normalForm();
|
||||
return value() != old_value;
|
||||
String old_value = value();
|
||||
ctx.setVariable(_("last change"), to_script(last_change));
|
||||
ChoiceValue::update(ctx);
|
||||
normalForm();
|
||||
return value() != old_value;
|
||||
}
|
||||
|
||||
void MultipleChoiceValue::get(vector<String>& out) const {
|
||||
// split the value
|
||||
out.clear();
|
||||
bool is_new = true;
|
||||
FOR_EACH_CONST(c, value()) {
|
||||
if (c == _(',')) {
|
||||
is_new = true;
|
||||
} else if (is_new) {
|
||||
if (c != _(' ')) { // ignore whitespace after ,
|
||||
is_new = false;
|
||||
out.push_back(String(1, c));
|
||||
}
|
||||
} else {
|
||||
assert(!out.empty());
|
||||
out.back() += c;
|
||||
}
|
||||
}
|
||||
// split the value
|
||||
out.clear();
|
||||
bool is_new = true;
|
||||
FOR_EACH_CONST(c, value()) {
|
||||
if (c == _(',')) {
|
||||
is_new = true;
|
||||
} else if (is_new) {
|
||||
if (c != _(' ')) { // ignore whitespace after ,
|
||||
is_new = false;
|
||||
out.push_back(String(1, c));
|
||||
}
|
||||
} else {
|
||||
assert(!out.empty());
|
||||
out.back() += c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MultipleChoiceValue::normalForm() {
|
||||
String& val = value.mutateDontChangeDefault();
|
||||
// which choices are active?
|
||||
vector<bool> seen(field().choices->lastId());
|
||||
for (size_t pos = 0 ; pos < val.size() ; ) {
|
||||
if (val.GetChar(pos) == _(' ')) {
|
||||
++pos; // ingore whitespace
|
||||
} else {
|
||||
// does this choice match the one asked about?
|
||||
size_t end = val.find_first_of(_(','), pos);
|
||||
if (end == String::npos) end = val.size();
|
||||
// find this choice
|
||||
for (size_t i = 0 ; i < seen.size() ; ++i) {
|
||||
if (is_substr(val, pos, field().choices->choiceName((int)i))) {
|
||||
seen[i] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pos = end + 1;
|
||||
}
|
||||
}
|
||||
// now put them back in the right order
|
||||
val.clear();
|
||||
for (size_t i = 0 ; i < seen.size() ; ++i) {
|
||||
if (seen[i]) {
|
||||
if (!val.empty()) val += _(", ");
|
||||
val += field().choices->choiceName((int)i);
|
||||
}
|
||||
}
|
||||
// empty choice name
|
||||
if (val.empty()) {
|
||||
val = field().empty_choice;
|
||||
}
|
||||
String& val = value.mutateDontChangeDefault();
|
||||
// which choices are active?
|
||||
vector<bool> seen(field().choices->lastId());
|
||||
for (size_t pos = 0 ; pos < val.size() ; ) {
|
||||
if (val.GetChar(pos) == _(' ')) {
|
||||
++pos; // ingore whitespace
|
||||
} else {
|
||||
// does this choice match the one asked about?
|
||||
size_t end = val.find_first_of(_(','), pos);
|
||||
if (end == String::npos) end = val.size();
|
||||
// find this choice
|
||||
for (size_t i = 0 ; i < seen.size() ; ++i) {
|
||||
if (is_substr(val, pos, field().choices->choiceName((int)i))) {
|
||||
seen[i] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pos = end + 1;
|
||||
}
|
||||
}
|
||||
// now put them back in the right order
|
||||
val.clear();
|
||||
for (size_t i = 0 ; i < seen.size() ; ++i) {
|
||||
if (seen[i]) {
|
||||
if (!val.empty()) val += _(", ");
|
||||
val += field().choices->choiceName((int)i);
|
||||
}
|
||||
}
|
||||
// empty choice name
|
||||
if (val.empty()) {
|
||||
val = field().empty_choice;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,11 +21,11 @@ DECLARE_POINTER_TYPE(MultipleChoiceValue);
|
||||
/// A ChoiceField where multiple choices can be selected simultaniously
|
||||
class MultipleChoiceField : public ChoiceField {
|
||||
public:
|
||||
MultipleChoiceField();
|
||||
DECLARE_FIELD_TYPE(MultipleChoiceField);
|
||||
|
||||
UInt minimum_selection, maximum_selection; ///< How many choices can be selected simultaniously?
|
||||
String empty_choice; ///< Name to use when nothing is selected
|
||||
MultipleChoiceField();
|
||||
DECLARE_FIELD_TYPE(MultipleChoiceField);
|
||||
|
||||
UInt minimum_selection, maximum_selection; ///< How many choices can be selected simultaniously?
|
||||
String empty_choice; ///< Name to use when nothing is selected
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : MultipleChoiceStyle
|
||||
@@ -33,13 +33,13 @@ class MultipleChoiceField : public ChoiceField {
|
||||
/// The Style for a MultipleChoiceField
|
||||
class MultipleChoiceStyle : public ChoiceStyle {
|
||||
public:
|
||||
MultipleChoiceStyle(const MultipleChoiceFieldP& field);
|
||||
DECLARE_STYLE_TYPE(MultipleChoice);
|
||||
|
||||
Scriptable<Direction> direction; ///< In what direction are choices layed out?
|
||||
Scriptable<double> spacing; ///< Spacing between choices (images) in pixels
|
||||
|
||||
virtual int update(Context&);
|
||||
MultipleChoiceStyle(const MultipleChoiceFieldP& field);
|
||||
DECLARE_STYLE_TYPE(MultipleChoice);
|
||||
|
||||
Scriptable<Direction> direction; ///< In what direction are choices layed out?
|
||||
Scriptable<double> spacing; ///< Spacing between choices (images) in pixels
|
||||
|
||||
virtual int update(Context&);
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : MultipleChoiceValue
|
||||
@@ -50,28 +50,28 @@ class MultipleChoiceStyle : public ChoiceStyle {
|
||||
*/
|
||||
class MultipleChoiceValue : public ChoiceValue {
|
||||
public:
|
||||
inline MultipleChoiceValue(const MultipleChoiceFieldP& field) : ChoiceValue(field, false) {}
|
||||
DECLARE_HAS_FIELD(MultipleChoice);
|
||||
virtual ValueP clone() const;
|
||||
|
||||
String last_change; ///< Which of the choices was selected/deselected last?
|
||||
|
||||
// for SimpleValueAction
|
||||
struct ValueType {
|
||||
ChoiceValue::ValueType value;
|
||||
String last_change;
|
||||
};
|
||||
|
||||
/// Splits the value, stores the selected choices in the out parameter
|
||||
void get(vector<String>& out) const;
|
||||
|
||||
virtual bool update(Context&);
|
||||
|
||||
inline MultipleChoiceValue(const MultipleChoiceFieldP& field) : ChoiceValue(field, false) {}
|
||||
DECLARE_HAS_FIELD(MultipleChoice);
|
||||
virtual ValueP clone() const;
|
||||
|
||||
String last_change; ///< Which of the choices was selected/deselected last?
|
||||
|
||||
// for SimpleValueAction
|
||||
struct ValueType {
|
||||
ChoiceValue::ValueType value;
|
||||
String last_change;
|
||||
};
|
||||
|
||||
/// Splits the value, stores the selected choices in the out parameter
|
||||
void get(vector<String>& out) const;
|
||||
|
||||
virtual bool update(Context&);
|
||||
|
||||
private:
|
||||
DECLARE_REFLECTION();
|
||||
|
||||
/// Put the value in normal form (all choices ordered, empty_name
|
||||
void normalForm();
|
||||
DECLARE_REFLECTION();
|
||||
|
||||
/// Put the value in normal form (all choices ordered, empty_name
|
||||
void normalForm();
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Utilities
|
||||
|
||||
@@ -15,73 +15,73 @@
|
||||
IMPLEMENT_FIELD_TYPE(PackageChoice, "package choice");
|
||||
|
||||
void PackageChoiceField::initDependencies(Context& ctx, const Dependency& dep) const {
|
||||
Field ::initDependencies(ctx, dep);
|
||||
script. initDependencies(ctx, dep);
|
||||
Field ::initDependencies(ctx, dep);
|
||||
script. initDependencies(ctx, dep);
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION(PackageChoiceField) {
|
||||
REFLECT_BASE(Field);
|
||||
REFLECT(script);
|
||||
REFLECT(match);
|
||||
REFLECT(initial);
|
||||
REFLECT(required);
|
||||
REFLECT(empty_name);
|
||||
REFLECT_BASE(Field);
|
||||
REFLECT(script);
|
||||
REFLECT(match);
|
||||
REFLECT(initial);
|
||||
REFLECT(required);
|
||||
REFLECT(empty_name);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : PackageChoiceStyle
|
||||
|
||||
PackageChoiceStyle::PackageChoiceStyle(const PackageChoiceFieldP& field)
|
||||
: Style(field)
|
||||
: Style(field)
|
||||
{}
|
||||
|
||||
int PackageChoiceStyle::update(Context& ctx) {
|
||||
return Style ::update(ctx)
|
||||
| font .update(ctx) * CHANGE_OTHER;
|
||||
return Style ::update(ctx)
|
||||
| font .update(ctx) * CHANGE_OTHER;
|
||||
}
|
||||
/*void PackageChoiceStyle::initDependencies(Context& ctx, const Dependency& dep) const {
|
||||
Style ::initDependencies(ctx, dep);
|
||||
// font .initDependencies(ctx, dep);
|
||||
Style ::initDependencies(ctx, dep);
|
||||
// font .initDependencies(ctx, dep);
|
||||
}*/
|
||||
|
||||
IMPLEMENT_REFLECTION(PackageChoiceStyle) {
|
||||
REFLECT_BASE(Style);
|
||||
REFLECT(font);
|
||||
REFLECT_BASE(Style);
|
||||
REFLECT(font);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : PackageChoiceValue
|
||||
|
||||
String PackageChoiceValue::toString() const {
|
||||
PackagedP pack = getPackage();
|
||||
if (pack.get()) return pack->short_name;
|
||||
else return _("");
|
||||
PackagedP pack = getPackage();
|
||||
if (pack.get()) return pack->short_name;
|
||||
else return _("");
|
||||
}
|
||||
|
||||
PackagedP PackageChoiceValue::getPackage() const {
|
||||
if (package_name.empty()) return nullptr;
|
||||
else return package_manager.openAny(package_name, true);
|
||||
if (package_name.empty()) return nullptr;
|
||||
else return package_manager.openAny(package_name, true);
|
||||
}
|
||||
|
||||
bool PackageChoiceValue::update(Context& ctx) {
|
||||
bool change = field().script.invokeOn(ctx, package_name);
|
||||
Value::update(ctx);
|
||||
return change;
|
||||
bool change = field().script.invokeOn(ctx, package_name);
|
||||
Value::update(ctx);
|
||||
return change;
|
||||
}
|
||||
|
||||
void PackageChoiceValue::reflect(Reader& tag) {
|
||||
REFLECT_NAMELESS(package_name);
|
||||
REFLECT_NAMELESS(package_name);
|
||||
}
|
||||
void PackageChoiceValue::reflect(Writer& tag) {
|
||||
REFLECT_NAMELESS(package_name);
|
||||
REFLECT_NAMELESS(package_name);
|
||||
}
|
||||
void PackageChoiceValue::reflect(GetDefaultMember& tag) {
|
||||
if (package_name.empty()) {
|
||||
REFLECT_NAMELESS(package_name);
|
||||
} else if(package_name != field().initial) {
|
||||
// add a space to the name, to indicate the dependency doesn't have to be marked
|
||||
// see also PackageManager::openFileFromPackage and SymbolFontRef::loadFont
|
||||
REFLECT_NAMELESS(_("/:NO-WARN-DEP:") + package_name);
|
||||
} else {
|
||||
REFLECT_NAMELESS(_("/") + package_name);
|
||||
}
|
||||
if (package_name.empty()) {
|
||||
REFLECT_NAMELESS(package_name);
|
||||
} else if(package_name != field().initial) {
|
||||
// add a space to the name, to indicate the dependency doesn't have to be marked
|
||||
// see also PackageManager::openFileFromPackage and SymbolFontRef::loadFont
|
||||
REFLECT_NAMELESS(_("/:NO-WARN-DEP:") + package_name);
|
||||
} else {
|
||||
REFLECT_NAMELESS(_("/") + package_name);
|
||||
}
|
||||
}
|
||||
void PackageChoiceValue::reflect(GetMember& tag) {}
|
||||
|
||||
@@ -25,16 +25,16 @@ DECLARE_POINTER_TYPE(PackageChoiceValue);
|
||||
/// A field for PackageChoice values, it contains a list of choices for PackageChoices
|
||||
class PackageChoiceField : public Field {
|
||||
public:
|
||||
PackageChoiceField() : required(true), empty_name(_("none")) {}
|
||||
DECLARE_FIELD_TYPE(PackageChoice);
|
||||
|
||||
OptionalScript script; ///< Script to apply to all values
|
||||
String match; ///< Package filenames to match
|
||||
String initial; ///< Initial value
|
||||
bool required; ///< Is selecting a package required?
|
||||
String empty_name; ///< Displayed name for the empty value (if !required)
|
||||
|
||||
virtual void initDependencies(Context&, const Dependency&) const;
|
||||
PackageChoiceField() : required(true), empty_name(_("none")) {}
|
||||
DECLARE_FIELD_TYPE(PackageChoice);
|
||||
|
||||
OptionalScript script; ///< Script to apply to all values
|
||||
String match; ///< Package filenames to match
|
||||
String initial; ///< Initial value
|
||||
bool required; ///< Is selecting a package required?
|
||||
String empty_name; ///< Displayed name for the empty value (if !required)
|
||||
|
||||
virtual void initDependencies(Context&, const Dependency&) const;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : PackageChoiceStyle
|
||||
@@ -42,12 +42,12 @@ class PackageChoiceField : public Field {
|
||||
/// The Style for a PackageChoiceField
|
||||
class PackageChoiceStyle : public Style {
|
||||
public:
|
||||
PackageChoiceStyle(const PackageChoiceFieldP& field);
|
||||
DECLARE_STYLE_TYPE(PackageChoice);
|
||||
|
||||
Font font; ///< Font to use for the text
|
||||
|
||||
virtual int update(Context&);
|
||||
PackageChoiceStyle(const PackageChoiceFieldP& field);
|
||||
DECLARE_STYLE_TYPE(PackageChoice);
|
||||
|
||||
Font font; ///< Font to use for the text
|
||||
|
||||
virtual int update(Context&);
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : PackageChoiceValue
|
||||
@@ -55,15 +55,15 @@ class PackageChoiceStyle : public Style {
|
||||
/// The Value in a PackageChoiceField
|
||||
class PackageChoiceValue : public Value {
|
||||
public:
|
||||
PackageChoiceValue(const PackageChoiceFieldP& field) : Value(field), package_name(field->initial) {}
|
||||
DECLARE_VALUE_TYPE(PackageChoice, String);
|
||||
|
||||
ValueType package_name; ///< The selected package
|
||||
|
||||
/// Get the package (if it is set)
|
||||
PackagedP getPackage() const;
|
||||
|
||||
virtual bool update(Context&);
|
||||
PackageChoiceValue(const PackageChoiceFieldP& field) : Value(field), package_name(field->initial) {}
|
||||
DECLARE_VALUE_TYPE(PackageChoice, String);
|
||||
|
||||
ValueType package_name; ///< The selected package
|
||||
|
||||
/// Get the package (if it is set)
|
||||
PackagedP getPackage() const;
|
||||
|
||||
virtual bool update(Context&);
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
+14
-14
@@ -15,42 +15,42 @@
|
||||
IMPLEMENT_FIELD_TYPE(Symbol, "symbol");
|
||||
|
||||
IMPLEMENT_REFLECTION(SymbolField) {
|
||||
REFLECT_BASE(Field);
|
||||
REFLECT_BASE(Field);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------- : SymbolStyle
|
||||
|
||||
IMPLEMENT_REFLECTION(SymbolStyle) {
|
||||
REFLECT_BASE(Style);
|
||||
REFLECT(min_aspect_ratio);
|
||||
REFLECT(max_aspect_ratio);
|
||||
REFLECT(variations);
|
||||
REFLECT_BASE(Style);
|
||||
REFLECT(min_aspect_ratio);
|
||||
REFLECT(max_aspect_ratio);
|
||||
REFLECT(variations);
|
||||
}
|
||||
|
||||
SymbolVariation::SymbolVariation()
|
||||
: border_radius(0.05)
|
||||
: border_radius(0.05)
|
||||
{}
|
||||
SymbolVariation::~SymbolVariation() {}
|
||||
|
||||
bool SymbolVariation::operator == (const SymbolVariation& that) const {
|
||||
return name == that.name
|
||||
&& border_radius == that.border_radius
|
||||
&& *filter == *that.filter;
|
||||
return name == that.name
|
||||
&& border_radius == that.border_radius
|
||||
&& *filter == *that.filter;
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION_NO_SCRIPT(SymbolVariation) {
|
||||
REFLECT(name);
|
||||
REFLECT(border_radius);
|
||||
REFLECT_NAMELESS(filter);
|
||||
REFLECT(name);
|
||||
REFLECT(border_radius);
|
||||
REFLECT_NAMELESS(filter);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : SymbolValue
|
||||
|
||||
String SymbolValue::toString() const {
|
||||
return filename.empty() ? wxEmptyString : _("<symbol>");
|
||||
return filename.empty() ? wxEmptyString : _("<symbol>");
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION_NAMELESS(SymbolValue) {
|
||||
if (fieldP->save_value || tag.scripting() || tag.reading()) REFLECT_NAMELESS(filename);
|
||||
if (fieldP->save_value || tag.scripting() || tag.reading()) REFLECT_NAMELESS(filename);
|
||||
}
|
||||
|
||||
+26
-26
@@ -25,9 +25,9 @@ DECLARE_POINTER_TYPE(SymbolValue);
|
||||
/// A field for image values
|
||||
class SymbolField : public Field {
|
||||
public:
|
||||
DECLARE_FIELD_TYPE(Symbol);
|
||||
|
||||
// no extra data
|
||||
DECLARE_FIELD_TYPE(Symbol);
|
||||
|
||||
// no extra data
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : SymbolStyle
|
||||
@@ -35,29 +35,29 @@ class SymbolField : public Field {
|
||||
/// The Style for a SymbolField
|
||||
class SymbolStyle : public Style {
|
||||
public:
|
||||
inline SymbolStyle(const SymbolFieldP& field)
|
||||
: Style(field)
|
||||
, min_aspect_ratio(1), max_aspect_ratio(1)
|
||||
{}
|
||||
DECLARE_STYLE_TYPE(Symbol);
|
||||
double min_aspect_ratio;
|
||||
double max_aspect_ratio; ///< Bounds for the symbol's aspect ratio
|
||||
|
||||
vector<SymbolVariationP> variations; ///< Different variantions of the same symbol
|
||||
inline SymbolStyle(const SymbolFieldP& field)
|
||||
: Style(field)
|
||||
, min_aspect_ratio(1), max_aspect_ratio(1)
|
||||
{}
|
||||
DECLARE_STYLE_TYPE(Symbol);
|
||||
double min_aspect_ratio;
|
||||
double max_aspect_ratio; ///< Bounds for the symbol's aspect ratio
|
||||
|
||||
vector<SymbolVariationP> variations; ///< Different variantions of the same symbol
|
||||
};
|
||||
|
||||
/// Styling for a symbol variation, defines color, border, etc.
|
||||
class SymbolVariation : public IntrusivePtrBase<SymbolVariation> {
|
||||
public:
|
||||
SymbolVariation();
|
||||
~SymbolVariation();
|
||||
String name; ///< Name of this variation
|
||||
SymbolFilterP filter; ///< Filter to color the symbol
|
||||
double border_radius; ///< Border radius for the symbol
|
||||
|
||||
bool operator == (const SymbolVariation&) const;
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
SymbolVariation();
|
||||
~SymbolVariation();
|
||||
String name; ///< Name of this variation
|
||||
SymbolFilterP filter; ///< Filter to color the symbol
|
||||
double border_radius; ///< Border radius for the symbol
|
||||
|
||||
bool operator == (const SymbolVariation&) const;
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : SymbolValue
|
||||
@@ -65,11 +65,11 @@ class SymbolVariation : public IntrusivePtrBase<SymbolVariation> {
|
||||
/// The Value in a SymbolField, i.e. a symbol
|
||||
class SymbolValue : public Value {
|
||||
public:
|
||||
inline SymbolValue(const SymbolFieldP& field) : Value(field) {}
|
||||
DECLARE_VALUE_TYPE(Symbol, FileName);
|
||||
|
||||
ValueType filename; ///< Filename of the symbol (in the current package)
|
||||
Age last_update; ///< When was the symbol last changed?
|
||||
inline SymbolValue(const SymbolFieldP& field) : Value(field) {}
|
||||
DECLARE_VALUE_TYPE(Symbol, FileName);
|
||||
|
||||
ValueType filename; ///< Filename of the symbol (in the current package)
|
||||
Age last_update; ///< When was the symbol last changed?
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
+115
-115
@@ -14,176 +14,176 @@
|
||||
// ----------------------------------------------------------------------------- : TextField
|
||||
|
||||
TextField::TextField()
|
||||
: multi_line(false)
|
||||
, default_name(_("Default"))
|
||||
: multi_line(false)
|
||||
, default_name(_("Default"))
|
||||
{}
|
||||
|
||||
IMPLEMENT_FIELD_TYPE(Text, "text");
|
||||
|
||||
void TextField::initDependencies(Context& ctx, const Dependency& dep) const {
|
||||
Field ::initDependencies(ctx, dep);
|
||||
script .initDependencies(ctx, dep);
|
||||
default_script.initDependencies(ctx, dep);
|
||||
Field ::initDependencies(ctx, dep);
|
||||
script .initDependencies(ctx, dep);
|
||||
default_script.initDependencies(ctx, dep);
|
||||
}
|
||||
|
||||
|
||||
IMPLEMENT_REFLECTION(TextField) {
|
||||
REFLECT_BASE(Field);
|
||||
REFLECT(multi_line);
|
||||
REFLECT(script);
|
||||
REFLECT_N("default", default_script);
|
||||
REFLECT(default_name);
|
||||
REFLECT_BASE(Field);
|
||||
REFLECT(multi_line);
|
||||
REFLECT(script);
|
||||
REFLECT_N("default", default_script);
|
||||
REFLECT(default_name);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : TextStyle
|
||||
|
||||
TextStyle::TextStyle(const TextFieldP& field)
|
||||
: Style(field)
|
||||
, always_symbol(false), allow_formating(true)
|
||||
, alignment(ALIGN_TOP_LEFT)
|
||||
, padding_left (0), padding_left_min (10000)
|
||||
, padding_right (0), padding_right_min (10000)
|
||||
, padding_top (0), padding_top_min (10000)
|
||||
, padding_bottom(0), padding_bottom_min(10000)
|
||||
, line_height_soft(1.0)
|
||||
, line_height_hard(1.0)
|
||||
, line_height_line(1.0)
|
||||
, line_height_soft_max(0.0)
|
||||
, line_height_hard_max(0.0)
|
||||
, line_height_line_max(0.0)
|
||||
, paragraph_height(-1)
|
||||
, direction(LEFT_TO_RIGHT)
|
||||
, content_width(0), content_height(0), content_lines(0)
|
||||
: Style(field)
|
||||
, always_symbol(false), allow_formating(true)
|
||||
, alignment(ALIGN_TOP_LEFT)
|
||||
, padding_left (0), padding_left_min (10000)
|
||||
, padding_right (0), padding_right_min (10000)
|
||||
, padding_top (0), padding_top_min (10000)
|
||||
, padding_bottom(0), padding_bottom_min(10000)
|
||||
, line_height_soft(1.0)
|
||||
, line_height_hard(1.0)
|
||||
, line_height_line(1.0)
|
||||
, line_height_soft_max(0.0)
|
||||
, line_height_hard_max(0.0)
|
||||
, line_height_line_max(0.0)
|
||||
, paragraph_height(-1)
|
||||
, direction(LEFT_TO_RIGHT)
|
||||
, content_width(0), content_height(0), content_lines(0)
|
||||
{}
|
||||
|
||||
double TextStyle::getStretch() const {
|
||||
if (content_width > 0 && (alignment() & ALIGN_STRETCH)) {
|
||||
double factor = (width - padding_left - padding_right) / content_width;
|
||||
if (!(alignment() & ALIGN_IF_OVERFLOW) || factor < 1.0) {
|
||||
return factor;
|
||||
}
|
||||
}
|
||||
return 1.0;
|
||||
if (content_width > 0 && (alignment() & ALIGN_STRETCH)) {
|
||||
double factor = (width - padding_left - padding_right) / content_width;
|
||||
if (!(alignment() & ALIGN_IF_OVERFLOW) || factor < 1.0) {
|
||||
return factor;
|
||||
}
|
||||
}
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
int TextStyle::update(Context& ctx) {
|
||||
return Style ::update(ctx)
|
||||
| font .update(ctx) * CHANGE_OTHER
|
||||
| symbol_font.update(ctx) * CHANGE_OTHER
|
||||
| alignment .update(ctx) * CHANGE_OTHER
|
||||
| ( padding_left .update(ctx)
|
||||
| padding_left_min .update(ctx)
|
||||
| padding_right .update(ctx)
|
||||
| padding_right_min .update(ctx)
|
||||
| padding_top .update(ctx)
|
||||
| padding_top_min .update(ctx)
|
||||
| padding_bottom .update(ctx)
|
||||
| padding_bottom_min .update(ctx)
|
||||
| line_height_soft .update(ctx)
|
||||
| line_height_hard .update(ctx)
|
||||
| line_height_line .update(ctx)
|
||||
| line_height_soft_max.update(ctx)
|
||||
| line_height_hard_max.update(ctx)
|
||||
| line_height_line_max.update(ctx)
|
||||
) * CHANGE_OTHER;
|
||||
return Style ::update(ctx)
|
||||
| font .update(ctx) * CHANGE_OTHER
|
||||
| symbol_font.update(ctx) * CHANGE_OTHER
|
||||
| alignment .update(ctx) * CHANGE_OTHER
|
||||
| ( padding_left .update(ctx)
|
||||
| padding_left_min .update(ctx)
|
||||
| padding_right .update(ctx)
|
||||
| padding_right_min .update(ctx)
|
||||
| padding_top .update(ctx)
|
||||
| padding_top_min .update(ctx)
|
||||
| padding_bottom .update(ctx)
|
||||
| padding_bottom_min .update(ctx)
|
||||
| line_height_soft .update(ctx)
|
||||
| line_height_hard .update(ctx)
|
||||
| line_height_line .update(ctx)
|
||||
| line_height_soft_max.update(ctx)
|
||||
| line_height_hard_max.update(ctx)
|
||||
| line_height_line_max.update(ctx)
|
||||
) * CHANGE_OTHER;
|
||||
}
|
||||
void TextStyle::initDependencies(Context& ctx, const Dependency& dep) const {
|
||||
Style ::initDependencies(ctx, dep);
|
||||
// font .initDependencies(ctx, dep);
|
||||
// symbol_font.initDependencies(ctx, dep);
|
||||
Style ::initDependencies(ctx, dep);
|
||||
// font .initDependencies(ctx, dep);
|
||||
// symbol_font.initDependencies(ctx, dep);
|
||||
}
|
||||
void TextStyle::checkContentDependencies(Context& ctx, const Dependency& dep) const {
|
||||
Style ::checkContentDependencies(ctx, dep);
|
||||
alignment.initDependencies(ctx, dep);
|
||||
Style ::checkContentDependencies(ctx, dep);
|
||||
alignment.initDependencies(ctx, dep);
|
||||
}
|
||||
|
||||
template <typename T> void reflect_content(T& tag, const TextStyle& ts) {}
|
||||
template <> void reflect_content(GetMember& tag, const TextStyle& ts) {
|
||||
REFLECT_N("content_width", ts.content_width);
|
||||
REFLECT_N("content_height", ts.content_height);
|
||||
REFLECT_N("content_lines", ts.content_lines);
|
||||
REFLECT_N("content_width", ts.content_width);
|
||||
REFLECT_N("content_height", ts.content_height);
|
||||
REFLECT_N("content_lines", ts.content_lines);
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION(TextStyle) {
|
||||
REFLECT_BASE(Style);
|
||||
REFLECT(font);
|
||||
REFLECT(symbol_font);
|
||||
REFLECT(always_symbol);
|
||||
REFLECT(allow_formating);
|
||||
REFLECT(alignment);
|
||||
REFLECT(padding_left);
|
||||
REFLECT(padding_right);
|
||||
REFLECT(padding_top);
|
||||
REFLECT(padding_bottom);
|
||||
REFLECT(padding_left_min);
|
||||
REFLECT(padding_right_min);
|
||||
REFLECT(padding_top_min);
|
||||
REFLECT(padding_bottom_min);
|
||||
REFLECT(line_height_soft);
|
||||
REFLECT(line_height_hard);
|
||||
REFLECT(line_height_line);
|
||||
REFLECT(line_height_soft_max);
|
||||
REFLECT(line_height_hard_max);
|
||||
REFLECT(line_height_line_max);
|
||||
REFLECT(paragraph_height);
|
||||
REFLECT(direction);
|
||||
reflect_content(tag, *this);
|
||||
REFLECT_BASE(Style);
|
||||
REFLECT(font);
|
||||
REFLECT(symbol_font);
|
||||
REFLECT(always_symbol);
|
||||
REFLECT(allow_formating);
|
||||
REFLECT(alignment);
|
||||
REFLECT(padding_left);
|
||||
REFLECT(padding_right);
|
||||
REFLECT(padding_top);
|
||||
REFLECT(padding_bottom);
|
||||
REFLECT(padding_left_min);
|
||||
REFLECT(padding_right_min);
|
||||
REFLECT(padding_top_min);
|
||||
REFLECT(padding_bottom_min);
|
||||
REFLECT(line_height_soft);
|
||||
REFLECT(line_height_hard);
|
||||
REFLECT(line_height_line);
|
||||
REFLECT(line_height_soft_max);
|
||||
REFLECT(line_height_hard_max);
|
||||
REFLECT(line_height_line_max);
|
||||
REFLECT(paragraph_height);
|
||||
REFLECT(direction);
|
||||
reflect_content(tag, *this);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : TextValue
|
||||
|
||||
String TextValue::toString() const {
|
||||
return untag_hide_sep(value());
|
||||
return untag_hide_sep(value());
|
||||
}
|
||||
bool TextValue::update(Context& ctx) {
|
||||
updateAge();
|
||||
WITH_DYNAMIC_ARG(last_update_age, last_update.get());
|
||||
WITH_DYNAMIC_ARG(value_being_updated, this);
|
||||
bool change = field().default_script.invokeOnDefault(ctx, value)
|
||||
| field(). script.invokeOn(ctx, value);
|
||||
if (change) last_update.update();
|
||||
updateSortValue(ctx);
|
||||
return change;
|
||||
updateAge();
|
||||
WITH_DYNAMIC_ARG(last_update_age, last_update.get());
|
||||
WITH_DYNAMIC_ARG(value_being_updated, this);
|
||||
bool change = field().default_script.invokeOnDefault(ctx, value)
|
||||
| field(). script.invokeOn(ctx, value);
|
||||
if (change) last_update.update();
|
||||
updateSortValue(ctx);
|
||||
return change;
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION_NAMELESS(TextValue) {
|
||||
if (fieldP->save_value || tag.scripting() || tag.reading()) REFLECT_NAMELESS(value);
|
||||
if (fieldP->save_value || tag.scripting() || tag.reading()) REFLECT_NAMELESS(value);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : FakeTextValue
|
||||
|
||||
FakeTextValue::FakeTextValue(const TextFieldP& field, String* underlying, bool editable, bool untagged)
|
||||
: TextValue(field), underlying(underlying)
|
||||
, editable(editable), untagged(untagged)
|
||||
: TextValue(field), underlying(underlying)
|
||||
, editable(editable), untagged(untagged)
|
||||
{}
|
||||
|
||||
void FakeTextValue::store() {
|
||||
if (underlying) {
|
||||
if (editable) {
|
||||
*underlying = untagged ? untag(value) : value();
|
||||
} else {
|
||||
retrieve();
|
||||
}
|
||||
}
|
||||
if (underlying) {
|
||||
if (editable) {
|
||||
*underlying = untagged ? untag(value) : value();
|
||||
} else {
|
||||
retrieve();
|
||||
}
|
||||
}
|
||||
}
|
||||
void FakeTextValue::retrieve() {
|
||||
if (underlying) {
|
||||
value.assign(untagged ? escape(*underlying) : *underlying);
|
||||
} else {
|
||||
value.assign(wxEmptyString);
|
||||
}
|
||||
if (underlying) {
|
||||
value.assign(untagged ? escape(*underlying) : *underlying);
|
||||
} else {
|
||||
value.assign(wxEmptyString);
|
||||
}
|
||||
}
|
||||
|
||||
void FakeTextValue::onAction(Action& a, bool undone) {
|
||||
store();
|
||||
store();
|
||||
}
|
||||
|
||||
bool FakeTextValue::equals(const Value* that) {
|
||||
if (this == that) return true;
|
||||
if (!underlying) return false;
|
||||
const FakeTextValue* thatT = dynamic_cast<const FakeTextValue*>(that);
|
||||
if (!thatT || underlying != thatT->underlying) return false;
|
||||
// update the value
|
||||
retrieve();
|
||||
return true;
|
||||
if (this == that) return true;
|
||||
if (!underlying) return false;
|
||||
const FakeTextValue* thatT = dynamic_cast<const FakeTextValue*>(that);
|
||||
if (!thatT || underlying != thatT->underlying) return false;
|
||||
// update the value
|
||||
retrieve();
|
||||
return true;
|
||||
}
|
||||
|
||||
+68
-68
@@ -30,17 +30,17 @@ DECLARE_POINTER_TYPE(TextBackground);
|
||||
/// A field for values containing tagged text
|
||||
class TextField : public Field {
|
||||
public:
|
||||
TextField();
|
||||
DECLARE_FIELD_TYPE(Text);
|
||||
|
||||
OptionalScript script; ///< Script to apply to all values
|
||||
OptionalScript default_script; ///< Script that generates the default value
|
||||
//%OptionalScript view_script; ///< Script to apply before viewing
|
||||
//%OptionalScript unview_script; ///< Script to apply after changes to the view
|
||||
bool multi_line; ///< Are newlines allowed in the text?
|
||||
String default_name; ///< Name of "default" value
|
||||
|
||||
virtual void initDependencies(Context&, const Dependency&) const;
|
||||
TextField();
|
||||
DECLARE_FIELD_TYPE(Text);
|
||||
|
||||
OptionalScript script; ///< Script to apply to all values
|
||||
OptionalScript default_script; ///< Script that generates the default value
|
||||
//%OptionalScript view_script; ///< Script to apply before viewing
|
||||
//%OptionalScript unview_script; ///< Script to apply after changes to the view
|
||||
bool multi_line; ///< Are newlines allowed in the text?
|
||||
String default_name; ///< Name of "default" value
|
||||
|
||||
virtual void initDependencies(Context&, const Dependency&) const;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : TextStyle
|
||||
@@ -48,37 +48,37 @@ class TextField : public Field {
|
||||
/// The Style for a TextField
|
||||
class TextStyle : public Style {
|
||||
public:
|
||||
TextStyle(const TextFieldP&);
|
||||
DECLARE_STYLE_TYPE(Text);
|
||||
|
||||
Font font; ///< Font to use for the text
|
||||
SymbolFontRef symbol_font; ///< Symbol font for symbols in the text
|
||||
bool always_symbol; ///< Should everything be drawn as symbols?
|
||||
bool allow_formating; ///< Is formating (bold/italic/..) allowed?
|
||||
Scriptable<Alignment> alignment; ///< Alignment inside the box
|
||||
Scriptable<double>
|
||||
padding_left, padding_left_min, ///< Padding
|
||||
padding_right, padding_right_min, ///< Padding
|
||||
padding_top, padding_top_min, ///< Padding
|
||||
padding_bottom, padding_bottom_min, ///< Padding
|
||||
line_height_soft, ///< Line height for soft linebreaks
|
||||
line_height_hard, ///< Line height for hard linebreaks
|
||||
line_height_line, ///< Line height for <line> tags
|
||||
line_height_soft_max, ///< Maximum line height
|
||||
line_height_hard_max, ///< Maximum line height
|
||||
line_height_line_max, ///< Maximum line height
|
||||
paragraph_height; ///< Fixed height of paragraphs
|
||||
Direction direction; ///< In what direction is text layed out?
|
||||
// information from text rendering
|
||||
double content_width, content_height; ///< Size of the rendered text
|
||||
int content_lines; ///< Number of rendered lines
|
||||
|
||||
virtual int update(Context&);
|
||||
virtual void initDependencies(Context&, const Dependency&) const;
|
||||
virtual void checkContentDependencies(Context&, const Dependency&) const;
|
||||
|
||||
/// Stretch factor to use
|
||||
double getStretch() const;
|
||||
TextStyle(const TextFieldP&);
|
||||
DECLARE_STYLE_TYPE(Text);
|
||||
|
||||
Font font; ///< Font to use for the text
|
||||
SymbolFontRef symbol_font; ///< Symbol font for symbols in the text
|
||||
bool always_symbol; ///< Should everything be drawn as symbols?
|
||||
bool allow_formating; ///< Is formating (bold/italic/..) allowed?
|
||||
Scriptable<Alignment> alignment; ///< Alignment inside the box
|
||||
Scriptable<double>
|
||||
padding_left, padding_left_min, ///< Padding
|
||||
padding_right, padding_right_min, ///< Padding
|
||||
padding_top, padding_top_min, ///< Padding
|
||||
padding_bottom, padding_bottom_min, ///< Padding
|
||||
line_height_soft, ///< Line height for soft linebreaks
|
||||
line_height_hard, ///< Line height for hard linebreaks
|
||||
line_height_line, ///< Line height for <line> tags
|
||||
line_height_soft_max, ///< Maximum line height
|
||||
line_height_hard_max, ///< Maximum line height
|
||||
line_height_line_max, ///< Maximum line height
|
||||
paragraph_height; ///< Fixed height of paragraphs
|
||||
Direction direction; ///< In what direction is text layed out?
|
||||
// information from text rendering
|
||||
double content_width, content_height; ///< Size of the rendered text
|
||||
int content_lines; ///< Number of rendered lines
|
||||
|
||||
virtual int update(Context&);
|
||||
virtual void initDependencies(Context&, const Dependency&) const;
|
||||
virtual void checkContentDependencies(Context&, const Dependency&) const;
|
||||
|
||||
/// Stretch factor to use
|
||||
double getStretch() const;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : TextValue
|
||||
@@ -86,13 +86,13 @@ class TextStyle : public Style {
|
||||
/// The Value in a TextField
|
||||
class TextValue : public Value {
|
||||
public:
|
||||
inline TextValue(const TextFieldP& field) : Value(field), last_update(1) {}
|
||||
DECLARE_VALUE_TYPE(Text, Defaultable<String>);
|
||||
|
||||
ValueType value; ///< The text of this value
|
||||
Age last_update; ///< When was the text last changed?
|
||||
|
||||
virtual bool update(Context&);
|
||||
inline TextValue(const TextFieldP& field) : Value(field), last_update(1) {}
|
||||
DECLARE_VALUE_TYPE(Text, Defaultable<String>);
|
||||
|
||||
ValueType value; ///< The text of this value
|
||||
Age last_update; ///< When was the text last changed?
|
||||
|
||||
virtual bool update(Context&);
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : TextValue
|
||||
@@ -101,25 +101,25 @@ class TextValue : public Value {
|
||||
/** Used by TextCtrl */
|
||||
class FakeTextValue : public TextValue {
|
||||
public:
|
||||
/// Initialize the fake text value
|
||||
/** underlying can be nullptr, in that case there is no underlying value */
|
||||
FakeTextValue(const TextFieldP& field, String* underlying, bool editable, bool untagged);
|
||||
|
||||
String* const underlying; ///< The underlying actual value, can be null
|
||||
bool const editable; ///< The underlying value can be edited
|
||||
bool const untagged; ///< The underlying value is untagged
|
||||
|
||||
/// Store the value in the underlying value.
|
||||
/** May be overloaded to do some transformation */
|
||||
virtual void store();
|
||||
/// Retrieve the value from the underlying value.
|
||||
/** May be overloaded to do some transformation */
|
||||
virtual void retrieve();
|
||||
|
||||
/// Update underlying data
|
||||
virtual void onAction(Action& a, bool undone);
|
||||
/// Editing the same underlying value?
|
||||
virtual bool equals(const Value* that);
|
||||
/// Initialize the fake text value
|
||||
/** underlying can be nullptr, in that case there is no underlying value */
|
||||
FakeTextValue(const TextFieldP& field, String* underlying, bool editable, bool untagged);
|
||||
|
||||
String* const underlying; ///< The underlying actual value, can be null
|
||||
bool const editable; ///< The underlying value can be edited
|
||||
bool const untagged; ///< The underlying value is untagged
|
||||
|
||||
/// Store the value in the underlying value.
|
||||
/** May be overloaded to do some transformation */
|
||||
virtual void store();
|
||||
/// Retrieve the value from the underlying value.
|
||||
/** May be overloaded to do some transformation */
|
||||
virtual void retrieve();
|
||||
|
||||
/// Update underlying data
|
||||
virtual void onAction(Action& a, bool undone);
|
||||
/// Editing the same underlying value?
|
||||
virtual bool equals(const Value* that);
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
+51
-51
@@ -17,21 +17,21 @@
|
||||
template <typename T>
|
||||
class Filter : public IntrusivePtrVirtualBase {
|
||||
public:
|
||||
typedef intrusive_ptr<T> TP;
|
||||
|
||||
virtual ~Filter() {}
|
||||
/// Should an object be shown in the list?
|
||||
virtual bool keep(T const& x) const {
|
||||
return false;
|
||||
}
|
||||
/// Select objects from a list
|
||||
virtual void getItems(vector<TP> const& in, vector<VoidP>& out) const {
|
||||
for (typename vector<TP>::const_iterator it = in.begin() ; it != in.end() ; ++it) {
|
||||
if (keep(**it)) {
|
||||
out.push_back(*it);
|
||||
}
|
||||
}
|
||||
}
|
||||
typedef intrusive_ptr<T> TP;
|
||||
|
||||
virtual ~Filter() {}
|
||||
/// Should an object be shown in the list?
|
||||
virtual bool keep(T const& x) const {
|
||||
return false;
|
||||
}
|
||||
/// Select objects from a list
|
||||
virtual void getItems(vector<TP> const& in, vector<VoidP>& out) const {
|
||||
for (typename vector<TP>::const_iterator it = in.begin() ; it != in.end() ; ++it) {
|
||||
if (keep(**it)) {
|
||||
out.push_back(*it);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Quick search
|
||||
@@ -39,49 +39,49 @@ class Filter : public IntrusivePtrVirtualBase {
|
||||
/// Does the given object match the quick search query?
|
||||
template <typename T>
|
||||
bool match_quicksearch_query(String const& query, T const& object) {
|
||||
bool need_match = true;
|
||||
// iterate over the components of the query
|
||||
for (size_t i = 0 ; i < query.size() ; ) {
|
||||
if (query.GetChar(i) == _(' ')) {
|
||||
// skip spaces
|
||||
i++;
|
||||
} else if (query.GetChar(i) == _('-')) {
|
||||
// negate the next query, i.e. match only if it is not on the card
|
||||
need_match = !need_match;
|
||||
i++;
|
||||
} else {
|
||||
size_t end, next;
|
||||
if (query.GetChar(i) == _('"')) {
|
||||
// quoted string, match exactly
|
||||
i++;
|
||||
end =query.find_first_of(_('"'),i);
|
||||
next = min(end,query.size()) + 1;
|
||||
} else {
|
||||
// single word
|
||||
next = end = query.find_first_of(_(' '),i);
|
||||
}
|
||||
bool match = object.contains(query.substr(i,end-i));
|
||||
if (match != need_match) {
|
||||
return false;
|
||||
}
|
||||
need_match = true; // next word is no longer negated
|
||||
i = next;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
bool need_match = true;
|
||||
// iterate over the components of the query
|
||||
for (size_t i = 0 ; i < query.size() ; ) {
|
||||
if (query.GetChar(i) == _(' ')) {
|
||||
// skip spaces
|
||||
i++;
|
||||
} else if (query.GetChar(i) == _('-')) {
|
||||
// negate the next query, i.e. match only if it is not on the card
|
||||
need_match = !need_match;
|
||||
i++;
|
||||
} else {
|
||||
size_t end, next;
|
||||
if (query.GetChar(i) == _('"')) {
|
||||
// quoted string, match exactly
|
||||
i++;
|
||||
end =query.find_first_of(_('"'),i);
|
||||
next = min(end,query.size()) + 1;
|
||||
} else {
|
||||
// single word
|
||||
next = end = query.find_first_of(_(' '),i);
|
||||
}
|
||||
bool match = object.contains(query.substr(i,end-i));
|
||||
if (match != need_match) {
|
||||
return false;
|
||||
}
|
||||
need_match = true; // next word is no longer negated
|
||||
i = next;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// A filter function that searches for objects containing a string
|
||||
template <typename T>
|
||||
class QuickFilter : public Filter<T> {
|
||||
public:
|
||||
using typename Filter<T>::TP;
|
||||
QuickFilter(String const& query) : query(query) {}
|
||||
virtual bool keep(T const& x) const {
|
||||
return match_quicksearch_query(query, x);
|
||||
}
|
||||
using typename Filter<T>::TP;
|
||||
QuickFilter(String const& query) : query(query) {}
|
||||
virtual bool keep(T const& x) const {
|
||||
return match_quicksearch_query(query, x);
|
||||
}
|
||||
private:
|
||||
String query;
|
||||
String query;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
+95
-95
@@ -12,120 +12,120 @@
|
||||
// ----------------------------------------------------------------------------- : Font
|
||||
|
||||
Font::Font()
|
||||
: name()
|
||||
, size(1)
|
||||
, underline(false)
|
||||
, scale_down_to(100000)
|
||||
, max_stretch(1.0)
|
||||
, color(AColor(0,0,0))
|
||||
, shadow_displacement(0,0)
|
||||
, shadow_blur(0)
|
||||
, separator_color(AColor(0,0,0,128))
|
||||
, flags(FONT_NORMAL)
|
||||
: name()
|
||||
, size(1)
|
||||
, underline(false)
|
||||
, scale_down_to(100000)
|
||||
, max_stretch(1.0)
|
||||
, color(AColor(0,0,0))
|
||||
, shadow_displacement(0,0)
|
||||
, shadow_blur(0)
|
||||
, separator_color(AColor(0,0,0,128))
|
||||
, flags(FONT_NORMAL)
|
||||
{}
|
||||
|
||||
bool Font::update(Context& ctx) {
|
||||
bool changes
|
||||
= name .update(ctx)
|
||||
| italic_name .update(ctx)
|
||||
| size .update(ctx)
|
||||
| weight .update(ctx)
|
||||
| style .update(ctx)
|
||||
| underline .update(ctx)
|
||||
| color .update(ctx)
|
||||
| shadow_color.update(ctx);
|
||||
flags = (flags & ~FONT_BOLD & ~FONT_ITALIC)
|
||||
| (weight() == _("bold") ? FONT_BOLD : FONT_NORMAL)
|
||||
| (style() == _("italic") ? FONT_ITALIC : FONT_NORMAL);
|
||||
return changes;
|
||||
bool changes
|
||||
= name .update(ctx)
|
||||
| italic_name .update(ctx)
|
||||
| size .update(ctx)
|
||||
| weight .update(ctx)
|
||||
| style .update(ctx)
|
||||
| underline .update(ctx)
|
||||
| color .update(ctx)
|
||||
| shadow_color.update(ctx);
|
||||
flags = (flags & ~FONT_BOLD & ~FONT_ITALIC)
|
||||
| (weight() == _("bold") ? FONT_BOLD : FONT_NORMAL)
|
||||
| (style() == _("italic") ? FONT_ITALIC : FONT_NORMAL);
|
||||
return changes;
|
||||
}
|
||||
void Font::initDependencies(Context& ctx, const Dependency& dep) const {
|
||||
name .initDependencies(ctx, dep);
|
||||
italic_name .initDependencies(ctx, dep);
|
||||
size .initDependencies(ctx, dep);
|
||||
weight .initDependencies(ctx, dep);
|
||||
style .initDependencies(ctx, dep);
|
||||
underline .initDependencies(ctx, dep);
|
||||
color .initDependencies(ctx, dep);
|
||||
shadow_color.initDependencies(ctx, dep);
|
||||
name .initDependencies(ctx, dep);
|
||||
italic_name .initDependencies(ctx, dep);
|
||||
size .initDependencies(ctx, dep);
|
||||
weight .initDependencies(ctx, dep);
|
||||
style .initDependencies(ctx, dep);
|
||||
underline .initDependencies(ctx, dep);
|
||||
color .initDependencies(ctx, dep);
|
||||
shadow_color.initDependencies(ctx, dep);
|
||||
}
|
||||
|
||||
FontP Font::make(int add_flags, AColor* other_color, double* other_size) const {
|
||||
FontP f(new Font(*this));
|
||||
f->flags |= add_flags;
|
||||
if (add_flags & FONT_CODE_STRING) {
|
||||
f->color = Color(0,0,100);
|
||||
}
|
||||
if (add_flags & FONT_CODE) {
|
||||
f->color = Color(128,0,0);
|
||||
}
|
||||
if (add_flags & FONT_CODE_KW) {
|
||||
f->color = Color(158,100,0);
|
||||
f->flags |= FONT_BOLD;
|
||||
}
|
||||
if (add_flags & FONT_SOFT) {
|
||||
f->color = f->separator_color;
|
||||
f->shadow_displacement = RealSize(0,0); // no shadow
|
||||
}
|
||||
if (other_color) {
|
||||
f->color = *other_color;
|
||||
}
|
||||
if (other_size) {
|
||||
f->size = *other_size;
|
||||
}
|
||||
return f;
|
||||
FontP f(new Font(*this));
|
||||
f->flags |= add_flags;
|
||||
if (add_flags & FONT_CODE_STRING) {
|
||||
f->color = Color(0,0,100);
|
||||
}
|
||||
if (add_flags & FONT_CODE) {
|
||||
f->color = Color(128,0,0);
|
||||
}
|
||||
if (add_flags & FONT_CODE_KW) {
|
||||
f->color = Color(158,100,0);
|
||||
f->flags |= FONT_BOLD;
|
||||
}
|
||||
if (add_flags & FONT_SOFT) {
|
||||
f->color = f->separator_color;
|
||||
f->shadow_displacement = RealSize(0,0); // no shadow
|
||||
}
|
||||
if (other_color) {
|
||||
f->color = *other_color;
|
||||
}
|
||||
if (other_size) {
|
||||
f->size = *other_size;
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
static const String BOLD_STRING = _(" Bold");
|
||||
wxFont Font::toWxFont(double scale) const {
|
||||
int size_i = to_int(scale * size);
|
||||
int weight_i = flags & FONT_BOLD ? wxFONTWEIGHT_BOLD : wxFONTWEIGHT_NORMAL;
|
||||
int style_i = flags & FONT_ITALIC ? wxFONTSTYLE_ITALIC : wxFONTSTYLE_NORMAL;
|
||||
// make font
|
||||
wxFont font;
|
||||
int size_i = to_int(scale * size);
|
||||
int weight_i = flags & FONT_BOLD ? wxFONTWEIGHT_BOLD : wxFONTWEIGHT_NORMAL;
|
||||
int style_i = flags & FONT_ITALIC ? wxFONTSTYLE_ITALIC : wxFONTSTYLE_NORMAL;
|
||||
// make font
|
||||
wxFont font;
|
||||
|
||||
if (flags & FONT_CODE) {
|
||||
if (size_i < 2) {
|
||||
return wxFont(wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, weight_i, underline(), _("Courier New"));
|
||||
} else {
|
||||
font = wxFont(size_i, wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, weight_i, underline(), _("Courier New"));
|
||||
}
|
||||
} else if (name().empty()) {
|
||||
font = *wxNORMAL_FONT;
|
||||
font.SetPointSize(size > 1 ? size_i : int(scale * font.GetPointSize()));
|
||||
return font;
|
||||
} else if (flags & FONT_ITALIC && !italic_name().empty()) {
|
||||
font = wxFont(size_i, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, weight_i, underline(), italic_name());
|
||||
} else {
|
||||
if (flags & FONT_CODE) {
|
||||
if (size_i < 2) {
|
||||
return wxFont(wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, weight_i, underline(), _("Courier New"));
|
||||
} else {
|
||||
font = wxFont(size_i, wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, weight_i, underline(), _("Courier New"));
|
||||
}
|
||||
} else if (name().empty()) {
|
||||
font = *wxNORMAL_FONT;
|
||||
font.SetPointSize(size > 1 ? size_i : int(scale * font.GetPointSize()));
|
||||
return font;
|
||||
} else if (flags & FONT_ITALIC && !italic_name().empty()) {
|
||||
font = wxFont(size_i, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, weight_i, underline(), italic_name());
|
||||
} else {
|
||||
String familyName = name();
|
||||
if(familyName.EndsWith(BOLD_STRING)) {
|
||||
familyName = familyName.Left(familyName.length() - BOLD_STRING.length());
|
||||
weight_i = wxFONTWEIGHT_BOLD;
|
||||
}
|
||||
font = wxFont(size_i, wxFONTFAMILY_DEFAULT, style_i, weight_i, underline(), familyName);
|
||||
}
|
||||
// fix size
|
||||
#ifdef __WXMSW__
|
||||
// make it independent of screen dpi, always use 96 dpi
|
||||
// TODO: do something more sensible, and more portable
|
||||
font.SetPixelSize(wxSize(0, -(int)(scale*size*96.0/72.0 + 0.5) ));
|
||||
#endif
|
||||
return font;
|
||||
font = wxFont(size_i, wxFONTFAMILY_DEFAULT, style_i, weight_i, underline(), familyName);
|
||||
}
|
||||
// fix size
|
||||
#ifdef __WXMSW__
|
||||
// make it independent of screen dpi, always use 96 dpi
|
||||
// TODO: do something more sensible, and more portable
|
||||
font.SetPixelSize(wxSize(0, -(int)(scale*size*96.0/72.0 + 0.5) ));
|
||||
#endif
|
||||
return font;
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION_NO_SCRIPT(Font) {
|
||||
REFLECT(name);
|
||||
REFLECT(size);
|
||||
REFLECT(weight);
|
||||
REFLECT(style);
|
||||
REFLECT(underline);
|
||||
REFLECT(italic_name);
|
||||
REFLECT(color);
|
||||
REFLECT(scale_down_to);
|
||||
REFLECT(max_stretch);
|
||||
REFLECT_N("shadow_displacement_x", shadow_displacement.width);
|
||||
REFLECT_N("shadow_displacement_y", shadow_displacement.height);
|
||||
REFLECT(shadow_color);
|
||||
REFLECT(shadow_blur);
|
||||
REFLECT(separator_color);
|
||||
REFLECT(name);
|
||||
REFLECT(size);
|
||||
REFLECT(weight);
|
||||
REFLECT(style);
|
||||
REFLECT(underline);
|
||||
REFLECT(italic_name);
|
||||
REFLECT(color);
|
||||
REFLECT(scale_down_to);
|
||||
REFLECT(max_stretch);
|
||||
REFLECT_N("shadow_displacement_x", shadow_displacement.width);
|
||||
REFLECT_N("shadow_displacement_y", shadow_displacement.height);
|
||||
REFLECT(shadow_color);
|
||||
REFLECT(shadow_blur);
|
||||
REFLECT(separator_color);
|
||||
}
|
||||
|
||||
+42
-42
@@ -19,55 +19,55 @@ DECLARE_POINTER_TYPE(Font);
|
||||
// ----------------------------------------------------------------------------- : Font
|
||||
|
||||
enum FontFlags
|
||||
{ FONT_NORMAL = 0
|
||||
, FONT_BOLD = 0x01
|
||||
, FONT_ITALIC = 0x02
|
||||
, FONT_SOFT = 0x04
|
||||
, FONT_CODE = 0x08
|
||||
, FONT_CODE_KW = 0x10 // syntax highlighting
|
||||
, FONT_CODE_STRING = 0x20 // syntax highlighting
|
||||
, FONT_CODE_NUMBER = 0x40 // syntax highlighting
|
||||
, FONT_CODE_OPER = 0x80 // syntax highlighting
|
||||
{ FONT_NORMAL = 0
|
||||
, FONT_BOLD = 0x01
|
||||
, FONT_ITALIC = 0x02
|
||||
, FONT_SOFT = 0x04
|
||||
, FONT_CODE = 0x08
|
||||
, FONT_CODE_KW = 0x10 // syntax highlighting
|
||||
, FONT_CODE_STRING = 0x20 // syntax highlighting
|
||||
, FONT_CODE_NUMBER = 0x40 // syntax highlighting
|
||||
, FONT_CODE_OPER = 0x80 // syntax highlighting
|
||||
};
|
||||
|
||||
/// A font for rendering text
|
||||
/** Contains additional information about scaling, color and shadow */
|
||||
class Font : public IntrusivePtrBase<Font> {
|
||||
public:
|
||||
Scriptable<String> name; ///< Name of the font
|
||||
Scriptable<String> italic_name; ///< Font name for italic text (optional)
|
||||
Scriptable<double> size; ///< Size of the font
|
||||
Scriptable<String> weight, style; ///< Weight and style of the font (bold/italic)
|
||||
Scriptable<bool> underline; ///< Underlined?
|
||||
double scale_down_to; ///< Smallest size to scale down to
|
||||
double max_stretch; ///< How much should the font be stretched before scaling down?
|
||||
Scriptable<AColor> color; ///< Color to use
|
||||
Scriptable<AColor> shadow_color; ///< Color for shadow
|
||||
RealSize shadow_displacement; ///< Position of the shadow
|
||||
double shadow_blur; ///< Blur radius of the shadow
|
||||
AColor separator_color; ///< Color for <sep> text
|
||||
int flags; ///< FontFlags for this font
|
||||
|
||||
Font();
|
||||
|
||||
/// Update the scritables, returns true if there is a change
|
||||
bool update(Context& ctx);
|
||||
/// Add the given dependency to the dependent_scripts list for the variables this font depends on
|
||||
void initDependencies(Context&, const Dependency&) const;
|
||||
|
||||
/// Does this font have a shadow?
|
||||
inline bool hasShadow() const {
|
||||
return shadow_displacement.width != 0 || shadow_displacement.height != 0;
|
||||
}
|
||||
|
||||
/// Add style to a font, and optionally change the color and size
|
||||
FontP make(int add_flags, AColor* other_color, double* other_size) const;
|
||||
|
||||
/// Convert this font to a wxFont
|
||||
wxFont toWxFont(double scale) const;
|
||||
|
||||
Scriptable<String> name; ///< Name of the font
|
||||
Scriptable<String> italic_name; ///< Font name for italic text (optional)
|
||||
Scriptable<double> size; ///< Size of the font
|
||||
Scriptable<String> weight, style; ///< Weight and style of the font (bold/italic)
|
||||
Scriptable<bool> underline; ///< Underlined?
|
||||
double scale_down_to; ///< Smallest size to scale down to
|
||||
double max_stretch; ///< How much should the font be stretched before scaling down?
|
||||
Scriptable<AColor> color; ///< Color to use
|
||||
Scriptable<AColor> shadow_color; ///< Color for shadow
|
||||
RealSize shadow_displacement; ///< Position of the shadow
|
||||
double shadow_blur; ///< Blur radius of the shadow
|
||||
AColor separator_color; ///< Color for <sep> text
|
||||
int flags; ///< FontFlags for this font
|
||||
|
||||
Font();
|
||||
|
||||
/// Update the scritables, returns true if there is a change
|
||||
bool update(Context& ctx);
|
||||
/// Add the given dependency to the dependent_scripts list for the variables this font depends on
|
||||
void initDependencies(Context&, const Dependency&) const;
|
||||
|
||||
/// Does this font have a shadow?
|
||||
inline bool hasShadow() const {
|
||||
return shadow_displacement.width != 0 || shadow_displacement.height != 0;
|
||||
}
|
||||
|
||||
/// Add style to a font, and optionally change the color and size
|
||||
FontP make(int add_flags, AColor* other_color, double* other_size) const;
|
||||
|
||||
/// Convert this font to a wxFont
|
||||
wxFont toWxFont(double scale) const;
|
||||
|
||||
private:
|
||||
DECLARE_REFLECTION();
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
|
||||
|
||||
+544
-544
File diff suppressed because it is too large
Load Diff
@@ -23,131 +23,131 @@
|
||||
/// Serialize an object to a string, clipboard_package will be set to the given package.
|
||||
template <typename T>
|
||||
String serialize_for_clipboard(Package& package, T& object) {
|
||||
shared_ptr<wxStringOutputStream> stream( new wxStringOutputStream );
|
||||
Writer writer(stream, file_version_clipboard);
|
||||
WITH_DYNAMIC_ARG(clipboard_package, &package);
|
||||
writer.handle(object);
|
||||
return stream->GetString();
|
||||
shared_ptr<wxStringOutputStream> stream( new wxStringOutputStream );
|
||||
Writer writer(stream, file_version_clipboard);
|
||||
WITH_DYNAMIC_ARG(clipboard_package, &package);
|
||||
writer.handle(object);
|
||||
return stream->GetString();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void deserialize_from_clipboard(T& object, Package& package, const String& data) {
|
||||
shared_ptr<wxStringInputStream> stream( new wxStringInputStream(data) );
|
||||
Reader reader(stream, nullptr, _("clipboard"));
|
||||
WITH_DYNAMIC_ARG(clipboard_package, &package);
|
||||
reader.handle_greedy(object);
|
||||
shared_ptr<wxStringInputStream> stream( new wxStringInputStream(data) );
|
||||
Reader reader(stream, nullptr, _("clipboard"));
|
||||
WITH_DYNAMIC_ARG(clipboard_package, &package);
|
||||
reader.handle_greedy(object);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : CardDataObject
|
||||
|
||||
/// A wrapped cards for storing on the clipboard
|
||||
struct WrappedCards {
|
||||
Game* expected_game;
|
||||
String game_name;
|
||||
vector<CardP> cards;
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
Game* expected_game;
|
||||
String game_name;
|
||||
vector<CardP> cards;
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
IMPLEMENT_REFLECTION(WrappedCards) {
|
||||
REFLECT(game_name);
|
||||
if (game_name == expected_game->name()) {
|
||||
WITH_DYNAMIC_ARG(game_for_reading, expected_game);
|
||||
REFLECT(cards);
|
||||
}
|
||||
REFLECT(game_name);
|
||||
if (game_name == expected_game->name()) {
|
||||
WITH_DYNAMIC_ARG(game_for_reading, expected_game);
|
||||
REFLECT(cards);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
wxDataFormat CardsDataObject::format = _("application/x-mse-cards");
|
||||
|
||||
CardsDataObject::CardsDataObject(const SetP& set, const vector<CardP>& cards) {
|
||||
// set the stylesheet, so when deserializing we know whos style options we are reading
|
||||
bool* has_styling = new bool[cards.size()];
|
||||
for (size_t i = 0 ; i < cards.size() ; ++i) {
|
||||
has_styling[i] = cards[i]->has_styling && !cards[i]->stylesheet;
|
||||
if (has_styling[i]) {
|
||||
cards[i]->stylesheet = set->stylesheet;
|
||||
}
|
||||
}
|
||||
WrappedCards data = { set->game.get(), set->game->name(), cards };
|
||||
SetText(serialize_for_clipboard(*set, data));
|
||||
// restore cards
|
||||
for (size_t i = 0 ; i < cards.size() ; ++i) {
|
||||
if (has_styling[i]) {
|
||||
cards[i]->stylesheet = StyleSheetP();
|
||||
}
|
||||
}
|
||||
SetFormat(format);
|
||||
delete [] has_styling;
|
||||
// set the stylesheet, so when deserializing we know whos style options we are reading
|
||||
bool* has_styling = new bool[cards.size()];
|
||||
for (size_t i = 0 ; i < cards.size() ; ++i) {
|
||||
has_styling[i] = cards[i]->has_styling && !cards[i]->stylesheet;
|
||||
if (has_styling[i]) {
|
||||
cards[i]->stylesheet = set->stylesheet;
|
||||
}
|
||||
}
|
||||
WrappedCards data = { set->game.get(), set->game->name(), cards };
|
||||
SetText(serialize_for_clipboard(*set, data));
|
||||
// restore cards
|
||||
for (size_t i = 0 ; i < cards.size() ; ++i) {
|
||||
if (has_styling[i]) {
|
||||
cards[i]->stylesheet = StyleSheetP();
|
||||
}
|
||||
}
|
||||
SetFormat(format);
|
||||
delete [] has_styling;
|
||||
}
|
||||
|
||||
CardsDataObject::CardsDataObject() {
|
||||
SetFormat(format);
|
||||
SetFormat(format);
|
||||
}
|
||||
|
||||
bool CardsDataObject::getCards(const SetP& set, vector<CardP>& out) {
|
||||
WrappedCards data = { set->game.get(), set->game->name() };
|
||||
deserialize_from_clipboard(data, *set, GetText());
|
||||
if (data.cards.empty()) return false;
|
||||
if (data.game_name == set->game->name()) {
|
||||
// Cards are from the same game
|
||||
out = data.cards;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
WrappedCards data = { set->game.get(), set->game->name() };
|
||||
deserialize_from_clipboard(data, *set, GetText());
|
||||
if (data.cards.empty()) return false;
|
||||
if (data.game_name == set->game->name()) {
|
||||
// Cards are from the same game
|
||||
out = data.cards;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : KeywordDataObject
|
||||
|
||||
/// A wrapped keyword for storing on the clipboard
|
||||
struct WrappedKeyword {
|
||||
Game* expected_game;
|
||||
String game_name;
|
||||
KeywordP keyword;
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
Game* expected_game;
|
||||
String game_name;
|
||||
KeywordP keyword;
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
IMPLEMENT_REFLECTION(WrappedKeyword) {
|
||||
REFLECT(game_name);
|
||||
if (game_name == expected_game->name()) {
|
||||
WITH_DYNAMIC_ARG(game_for_reading, expected_game);
|
||||
REFLECT(keyword);
|
||||
}
|
||||
REFLECT(game_name);
|
||||
if (game_name == expected_game->name()) {
|
||||
WITH_DYNAMIC_ARG(game_for_reading, expected_game);
|
||||
REFLECT(keyword);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
wxDataFormat KeywordDataObject::format = _("application/x-mse-keyword");
|
||||
|
||||
KeywordDataObject::KeywordDataObject(const SetP& set, const KeywordP& keyword) {
|
||||
WrappedKeyword data = { set->game.get(), set->game->name(), keyword };
|
||||
SetText(serialize_for_clipboard(*set, data));
|
||||
SetFormat(format);
|
||||
WrappedKeyword data = { set->game.get(), set->game->name(), keyword };
|
||||
SetText(serialize_for_clipboard(*set, data));
|
||||
SetFormat(format);
|
||||
}
|
||||
|
||||
KeywordDataObject::KeywordDataObject() {
|
||||
SetFormat(format);
|
||||
SetFormat(format);
|
||||
}
|
||||
|
||||
KeywordP KeywordDataObject::getKeyword(const SetP& set) {
|
||||
KeywordP keyword(new Keyword());
|
||||
WrappedKeyword data = { set->game.get(), set->game->name(), keyword};
|
||||
deserialize_from_clipboard(data, *set, GetText());
|
||||
if (data.game_name != set->game->name()) return KeywordP(); // Keyword is from a different game
|
||||
else return keyword;
|
||||
KeywordP keyword(new Keyword());
|
||||
WrappedKeyword data = { set->game.get(), set->game->name(), keyword};
|
||||
deserialize_from_clipboard(data, *set, GetText());
|
||||
if (data.game_name != set->game->name()) return KeywordP(); // Keyword is from a different game
|
||||
else return keyword;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Card on clipboard
|
||||
|
||||
CardsOnClipboard::CardsOnClipboard(const SetP& set, const vector<CardP>& cards) {
|
||||
// Conversion to text format
|
||||
// TODO
|
||||
//Add( new TextDataObject(_("card")))
|
||||
// Conversion to bitmap format
|
||||
if (cards.size() == 1) {
|
||||
Add(new wxBitmapDataObject(export_bitmap(set, cards[0])));
|
||||
}
|
||||
// Conversion to serialized card format
|
||||
Add(new CardsDataObject(set, cards), true);
|
||||
// Conversion to text format
|
||||
// TODO
|
||||
//Add( new TextDataObject(_("card")))
|
||||
// Conversion to bitmap format
|
||||
if (cards.size() == 1) {
|
||||
Add(new wxBitmapDataObject(export_bitmap(set, cards[0])));
|
||||
}
|
||||
// Conversion to serialized card format
|
||||
Add(new CardsDataObject(set, cards), true);
|
||||
}
|
||||
|
||||
@@ -21,16 +21,16 @@ DECLARE_POINTER_TYPE(Keyword);
|
||||
/// The data format for cards on the clipboard
|
||||
class CardsDataObject : public wxTextDataObject {
|
||||
public:
|
||||
/// Name of the format of MSE cards
|
||||
static wxDataFormat format;
|
||||
|
||||
CardsDataObject();
|
||||
/// Store a card
|
||||
CardsDataObject(const SetP& set, const vector<CardP>& cards);
|
||||
|
||||
/// Retrieve the cards, only if it is made with the same game as set
|
||||
/** Return true if the cards are correctly retrieved, and there is at least one card */
|
||||
bool getCards(const SetP& set, vector<CardP>& out);
|
||||
/// Name of the format of MSE cards
|
||||
static wxDataFormat format;
|
||||
|
||||
CardsDataObject();
|
||||
/// Store a card
|
||||
CardsDataObject(const SetP& set, const vector<CardP>& cards);
|
||||
|
||||
/// Retrieve the cards, only if it is made with the same game as set
|
||||
/** Return true if the cards are correctly retrieved, and there is at least one card */
|
||||
bool getCards(const SetP& set, vector<CardP>& out);
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : KeywordDataObject
|
||||
@@ -38,15 +38,15 @@ class CardsDataObject : public wxTextDataObject {
|
||||
/// The data format for keywords on the clipboard
|
||||
class KeywordDataObject : public wxTextDataObject {
|
||||
public:
|
||||
/// Name of the format of MSE keywords
|
||||
static wxDataFormat format;
|
||||
|
||||
KeywordDataObject();
|
||||
/// Store a keyword
|
||||
KeywordDataObject(const SetP& set, const KeywordP& card);
|
||||
|
||||
/// Retrieve a keyword, only if it is made with the same game as set
|
||||
KeywordP getKeyword(const SetP& set);
|
||||
/// Name of the format of MSE keywords
|
||||
static wxDataFormat format;
|
||||
|
||||
KeywordDataObject();
|
||||
/// Store a keyword
|
||||
KeywordDataObject(const SetP& set, const KeywordP& card);
|
||||
|
||||
/// Retrieve a keyword, only if it is made with the same game as set
|
||||
KeywordP getKeyword(const SetP& set);
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Card on clipboard
|
||||
@@ -54,7 +54,7 @@ class KeywordDataObject : public wxTextDataObject {
|
||||
/// A DataObject for putting one or more cards on the clipboard, in multiple formats
|
||||
class CardsOnClipboard : public wxDataObjectComposite {
|
||||
public:
|
||||
CardsOnClipboard(const SetP& set, const vector<CardP>& cards);
|
||||
CardsOnClipboard(const SetP& set, const vector<CardP>& cards);
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
+37
-37
@@ -19,53 +19,53 @@ DECLARE_TYPEOF_COLLECTION(FileFormatP);
|
||||
vector<FileFormatP> file_formats;
|
||||
|
||||
void init_file_formats() {
|
||||
file_formats.push_back(mse2_file_format());
|
||||
file_formats.push_back(mse1_file_format());
|
||||
file_formats.push_back(mtg_editor_file_format());
|
||||
file_formats.push_back(mse2_file_format());
|
||||
file_formats.push_back(mse1_file_format());
|
||||
file_formats.push_back(mtg_editor_file_format());
|
||||
}
|
||||
|
||||
String import_formats() {
|
||||
String all_extensions; // type1;type2
|
||||
String type_strings; // |name1|type1|name2|type2
|
||||
FOR_EACH(f, file_formats) {
|
||||
if (f->canImport()) {
|
||||
if (!all_extensions.empty()) all_extensions += _(";");
|
||||
all_extensions += f->matches();
|
||||
type_strings += _("|") + f->name() + _("|") + f->matches();
|
||||
}
|
||||
}
|
||||
return _("Set files|") + all_extensions + type_strings + _("|All files (*.*)|*");
|
||||
String all_extensions; // type1;type2
|
||||
String type_strings; // |name1|type1|name2|type2
|
||||
FOR_EACH(f, file_formats) {
|
||||
if (f->canImport()) {
|
||||
if (!all_extensions.empty()) all_extensions += _(";");
|
||||
all_extensions += f->matches();
|
||||
type_strings += _("|") + f->name() + _("|") + f->matches();
|
||||
}
|
||||
}
|
||||
return _("Set files|") + all_extensions + type_strings + _("|All files (*.*)|*");
|
||||
}
|
||||
|
||||
String export_formats(const Game& game) {
|
||||
String type_strings; // name1|type1|name2|type2
|
||||
FOR_EACH(f, file_formats) {
|
||||
if (f->canExport(game)) {
|
||||
if (!type_strings.empty()) type_strings += _("|");
|
||||
type_strings += f->name() + _("|") + f->matches();
|
||||
}
|
||||
}
|
||||
return type_strings;
|
||||
String type_strings; // name1|type1|name2|type2
|
||||
FOR_EACH(f, file_formats) {
|
||||
if (f->canExport(game)) {
|
||||
if (!type_strings.empty()) type_strings += _("|");
|
||||
type_strings += f->name() + _("|") + f->matches();
|
||||
}
|
||||
}
|
||||
return type_strings;
|
||||
}
|
||||
|
||||
void export_set(Set& set, const String& filename, size_t format_index, bool is_copy) {
|
||||
FileFormatP format = file_formats.at(format_index);
|
||||
if (!format->canExport(*set.game)) {
|
||||
throw InternalError(_("File format doesn't apply to set"));
|
||||
}
|
||||
format->exportSet(set, filename, is_copy);
|
||||
FileFormatP format = file_formats.at(format_index);
|
||||
if (!format->canExport(*set.game)) {
|
||||
throw InternalError(_("File format doesn't apply to set"));
|
||||
}
|
||||
format->exportSet(set, filename, is_copy);
|
||||
}
|
||||
|
||||
SetP import_set(String name) {
|
||||
size_t pos = name.find_last_of(_('.'));
|
||||
String extension = pos==String::npos ? _("") : name.substr(pos + 1);
|
||||
// determine format
|
||||
FOR_EACH(f, file_formats) {
|
||||
if (f->extension() == extension) {
|
||||
return f->importSet(name);
|
||||
}
|
||||
}
|
||||
// default: use first format = MSE2 format
|
||||
assert(!file_formats.empty() && file_formats[0]->canImport());
|
||||
return file_formats[0]->importSet(name);
|
||||
size_t pos = name.find_last_of(_('.'));
|
||||
String extension = pos==String::npos ? _("") : name.substr(pos + 1);
|
||||
// determine format
|
||||
FOR_EACH(f, file_formats) {
|
||||
if (f->extension() == extension) {
|
||||
return f->importSet(name);
|
||||
}
|
||||
}
|
||||
// default: use first format = MSE2 format
|
||||
assert(!file_formats.empty() && file_formats[0]->canImport());
|
||||
return file_formats[0]->importSet(name);
|
||||
}
|
||||
|
||||
+22
-22
@@ -23,28 +23,28 @@ DECLARE_POINTER_TYPE(FileFormat);
|
||||
/// A filter for a specific file format
|
||||
class FileFormat : public IntrusivePtrVirtualBase {
|
||||
public:
|
||||
virtual ~FileFormat() {}
|
||||
/// File extension used by this file format
|
||||
virtual String extension() = 0;
|
||||
/// What to match against
|
||||
virtual String matches() {
|
||||
return _("*.") + extension();
|
||||
}
|
||||
/// Name of the filter
|
||||
virtual String name() = 0;
|
||||
/// Can it be used for importing sets?
|
||||
virtual bool canImport() = 0;
|
||||
/// Can it be used for exporting sets for a particular game?
|
||||
virtual bool canExport(const Game&) = 0;
|
||||
/// Import using this filter
|
||||
virtual SetP importSet(const String& filename) {
|
||||
throw InternalError(_("Import not supported by this file format"));
|
||||
}
|
||||
/// Export using this filter
|
||||
/** If is_copy, then the set should not be modified */
|
||||
virtual void exportSet(Set& set, const String& filename, bool is_copy = false) {
|
||||
throw InternalError(_("Export not supported by this file format"));
|
||||
}
|
||||
virtual ~FileFormat() {}
|
||||
/// File extension used by this file format
|
||||
virtual String extension() = 0;
|
||||
/// What to match against
|
||||
virtual String matches() {
|
||||
return _("*.") + extension();
|
||||
}
|
||||
/// Name of the filter
|
||||
virtual String name() = 0;
|
||||
/// Can it be used for importing sets?
|
||||
virtual bool canImport() = 0;
|
||||
/// Can it be used for exporting sets for a particular game?
|
||||
virtual bool canExport(const Game&) = 0;
|
||||
/// Import using this filter
|
||||
virtual SetP importSet(const String& filename) {
|
||||
throw InternalError(_("Import not supported by this file format"));
|
||||
}
|
||||
/// Export using this filter
|
||||
/** If is_copy, then the set should not be modified */
|
||||
virtual void exportSet(Set& set, const String& filename, bool is_copy = false) {
|
||||
throw InternalError(_("Export not supported by this file format"));
|
||||
}
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Formats
|
||||
|
||||
+51
-51
@@ -21,46 +21,46 @@ DECLARE_TYPEOF_COLLECTION(CardP);
|
||||
// ----------------------------------------------------------------------------- : Single card export
|
||||
|
||||
void export_image(const SetP& set, const CardP& card, const String& filename) {
|
||||
Image img = export_bitmap(set, card).ConvertToImage();
|
||||
img.SaveFile(filename); // can't use Bitmap::saveFile, it wants to know the file type
|
||||
// but image.saveFile determines it automagicly
|
||||
Image img = export_bitmap(set, card).ConvertToImage();
|
||||
img.SaveFile(filename); // can't use Bitmap::saveFile, it wants to know the file type
|
||||
// but image.saveFile determines it automagicly
|
||||
}
|
||||
|
||||
class UnzoomedDataViewer : public DataViewer {
|
||||
public:
|
||||
UnzoomedDataViewer(bool use_zoom_settings)
|
||||
: use_zoom_settings(use_zoom_settings)
|
||||
{}
|
||||
virtual Rotation getRotation() const;
|
||||
UnzoomedDataViewer(bool use_zoom_settings)
|
||||
: use_zoom_settings(use_zoom_settings)
|
||||
{}
|
||||
virtual Rotation getRotation() const;
|
||||
private:
|
||||
bool use_zoom_settings;
|
||||
bool use_zoom_settings;
|
||||
};
|
||||
Rotation UnzoomedDataViewer::getRotation() const {
|
||||
if (use_zoom_settings) {
|
||||
return DataViewer::getRotation();
|
||||
} else {
|
||||
if (!stylesheet) stylesheet = set->stylesheet;
|
||||
return Rotation(0, stylesheet->getCardRect(), 1.0, 1.0, ROTATION_ATTACH_TOP_LEFT);
|
||||
}
|
||||
if (use_zoom_settings) {
|
||||
return DataViewer::getRotation();
|
||||
} else {
|
||||
if (!stylesheet) stylesheet = set->stylesheet;
|
||||
return Rotation(0, stylesheet->getCardRect(), 1.0, 1.0, ROTATION_ATTACH_TOP_LEFT);
|
||||
}
|
||||
}
|
||||
|
||||
Bitmap export_bitmap(const SetP& set, const CardP& card) {
|
||||
if (!set) throw Error(_("no set"));
|
||||
// create viewer
|
||||
UnzoomedDataViewer viewer(!settings.stylesheetSettingsFor(set->stylesheetFor(card)).card_normal_export());
|
||||
viewer.setSet(set);
|
||||
viewer.setCard(card);
|
||||
// size of cards
|
||||
RealSize size = viewer.getRotation().getExternalSize();
|
||||
// create bitmap & dc
|
||||
Bitmap bitmap((int) size.width, (int) size.height);
|
||||
if (!bitmap.Ok()) throw InternalError(_("Unable to create bitmap"));
|
||||
wxMemoryDC dc;
|
||||
dc.SelectObject(bitmap);
|
||||
// draw
|
||||
viewer.draw(dc);
|
||||
dc.SelectObject(wxNullBitmap);
|
||||
return bitmap;
|
||||
if (!set) throw Error(_("no set"));
|
||||
// create viewer
|
||||
UnzoomedDataViewer viewer(!settings.stylesheetSettingsFor(set->stylesheetFor(card)).card_normal_export());
|
||||
viewer.setSet(set);
|
||||
viewer.setCard(card);
|
||||
// size of cards
|
||||
RealSize size = viewer.getRotation().getExternalSize();
|
||||
// create bitmap & dc
|
||||
Bitmap bitmap((int) size.width, (int) size.height);
|
||||
if (!bitmap.Ok()) throw InternalError(_("Unable to create bitmap"));
|
||||
wxMemoryDC dc;
|
||||
dc.SelectObject(bitmap);
|
||||
// draw
|
||||
viewer.draw(dc);
|
||||
dc.SelectObject(wxNullBitmap);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Multiple card export
|
||||
@@ -69,25 +69,25 @@ Bitmap export_bitmap(const SetP& set, const CardP& card) {
|
||||
void export_images(const SetP& set, const vector<CardP>& cards,
|
||||
const String& path, const String& filename_template, FilenameConflicts conflicts)
|
||||
{
|
||||
wxBusyCursor busy;
|
||||
// Script
|
||||
ScriptP filename_script = parse(filename_template, nullptr, true);
|
||||
// Path
|
||||
wxFileName fn(path);
|
||||
// Export
|
||||
std::set<String> used; // for CONFLICT_NUMBER_OVERWRITE
|
||||
FOR_EACH_CONST(card, cards) {
|
||||
// filename for this card
|
||||
Context& ctx = set->getContext(card);
|
||||
String filename = clean_filename(untag(ctx.eval(*filename_script)->toString()));
|
||||
if (!filename) continue; // no filename -> no saving
|
||||
// full path
|
||||
fn.SetFullName(filename);
|
||||
// does the file exist?
|
||||
if (!resolve_filename_conflicts(fn, conflicts, used)) continue;
|
||||
// write image
|
||||
filename = fn.GetFullPath();
|
||||
used.insert(filename);
|
||||
export_image(set, card, filename);
|
||||
}
|
||||
wxBusyCursor busy;
|
||||
// Script
|
||||
ScriptP filename_script = parse(filename_template, nullptr, true);
|
||||
// Path
|
||||
wxFileName fn(path);
|
||||
// Export
|
||||
std::set<String> used; // for CONFLICT_NUMBER_OVERWRITE
|
||||
FOR_EACH_CONST(card, cards) {
|
||||
// filename for this card
|
||||
Context& ctx = set->getContext(card);
|
||||
String filename = clean_filename(untag(ctx.eval(*filename_script)->toString()));
|
||||
if (!filename) continue; // no filename -> no saving
|
||||
// full path
|
||||
fn.SetFullName(filename);
|
||||
// does the file exist?
|
||||
if (!resolve_filename_conflicts(fn, conflicts, used)) continue;
|
||||
// write image
|
||||
filename = fn.GetFullPath();
|
||||
used.insert(filename);
|
||||
export_image(set, card, filename);
|
||||
}
|
||||
}
|
||||
|
||||
+350
-350
@@ -7,10 +7,10 @@
|
||||
// ----------------------------------------------------------------------------- : Includes
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER >= 1400
|
||||
// VS 8 has the audacity to give a warning about fill_n
|
||||
// That is both STUPID and WRONG, so disable that warning
|
||||
// This has to be done before includes, because the warning is reported in standard headers!
|
||||
#pragma warning(disable:4996)
|
||||
// VS 8 has the audacity to give a warning about fill_n
|
||||
// That is both STUPID and WRONG, so disable that warning
|
||||
// This has to be done before includes, because the warning is reported in standard headers!
|
||||
#pragma warning(disable:4996)
|
||||
#endif
|
||||
|
||||
#include <util/prec.hpp>
|
||||
@@ -25,9 +25,9 @@ DECLARE_TYPEOF_COLLECTION(SymbolPartP);
|
||||
// ----------------------------------------------------------------------------- : Image preprocessing
|
||||
|
||||
enum ImageMarker
|
||||
{ EMPTY = 0 // This cell is empty
|
||||
, FULL = 1 // This cell is full
|
||||
, MARKED = 2 // This cell is full, but it has been used as a starting point for finding symbols
|
||||
{ EMPTY = 0 // This cell is empty
|
||||
, FULL = 1 // This cell is full
|
||||
, MARKED = 2 // This cell is full, but it has been used as a starting point for finding symbols
|
||||
};
|
||||
|
||||
|
||||
@@ -37,13 +37,13 @@ enum ImageMarker
|
||||
* is no longer an actual image.
|
||||
*/
|
||||
void greyscale(Image& img) {
|
||||
UInt size = img.GetWidth() * img.GetHeight();
|
||||
Byte* data = img.GetData();
|
||||
Byte* out = data;
|
||||
for (UInt i = 0 ; i < size ; ++i) {
|
||||
*out++ = (data[0] + data[1] + data[2]) / 3;
|
||||
data += 3;
|
||||
}
|
||||
UInt size = img.GetWidth() * img.GetHeight();
|
||||
Byte* data = img.GetData();
|
||||
Byte* out = data;
|
||||
for (UInt i = 0 ; i < size ; ++i) {
|
||||
*out++ = (data[0] + data[1] + data[2]) / 3;
|
||||
data += 3;
|
||||
}
|
||||
}
|
||||
|
||||
/// Thresholds an image, giving a black & white result
|
||||
@@ -52,192 +52,192 @@ void greyscale(Image& img) {
|
||||
* EMPTY for the 'border' color, FULL for the interior
|
||||
*/
|
||||
void threshold(Byte* data, int w, int h) {
|
||||
size_t size = w * h;
|
||||
// make histogram of data
|
||||
size_t hist[256];
|
||||
fill_n(hist,256,0);
|
||||
for (size_t i = 0 ; i < size ; ++i) {
|
||||
hist[data[i]]++;
|
||||
}
|
||||
// find threshold
|
||||
size_t threshold_pos = size / 2;
|
||||
int threshold = 255;
|
||||
size_t below = 0;
|
||||
for (int i = 0 ; i < 255 ; ++i) {
|
||||
if (below + hist[i]/2 > threshold_pos) {
|
||||
threshold = i;
|
||||
break;
|
||||
}
|
||||
below += hist[i];
|
||||
if (below >= threshold_pos) {
|
||||
threshold = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// threshold data
|
||||
for (size_t i = 0 ; i < size ; ++i) {
|
||||
data[i] = data[i] >= threshold ? FULL : EMPTY;
|
||||
}
|
||||
// should the colors be inverted?
|
||||
int border_count = 0;
|
||||
for (int x = 0 ; x < w ; ++x) {
|
||||
border_count += data[x] + data[x+(h-1)*w];
|
||||
}
|
||||
for (int y = 0 ; y < h ; ++y) {
|
||||
border_count += data[y*w] + data[w-1+y*w];
|
||||
}
|
||||
if (border_count > w + h) {
|
||||
// more then half the border if FULL, invert
|
||||
for (size_t i = 0 ; i < size ; ++i) {
|
||||
data[i] = data[i] == FULL ? EMPTY : FULL;
|
||||
}
|
||||
}
|
||||
size_t size = w * h;
|
||||
// make histogram of data
|
||||
size_t hist[256];
|
||||
fill_n(hist,256,0);
|
||||
for (size_t i = 0 ; i < size ; ++i) {
|
||||
hist[data[i]]++;
|
||||
}
|
||||
// find threshold
|
||||
size_t threshold_pos = size / 2;
|
||||
int threshold = 255;
|
||||
size_t below = 0;
|
||||
for (int i = 0 ; i < 255 ; ++i) {
|
||||
if (below + hist[i]/2 > threshold_pos) {
|
||||
threshold = i;
|
||||
break;
|
||||
}
|
||||
below += hist[i];
|
||||
if (below >= threshold_pos) {
|
||||
threshold = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// threshold data
|
||||
for (size_t i = 0 ; i < size ; ++i) {
|
||||
data[i] = data[i] >= threshold ? FULL : EMPTY;
|
||||
}
|
||||
// should the colors be inverted?
|
||||
int border_count = 0;
|
||||
for (int x = 0 ; x < w ; ++x) {
|
||||
border_count += data[x] + data[x+(h-1)*w];
|
||||
}
|
||||
for (int y = 0 ; y < h ; ++y) {
|
||||
border_count += data[y*w] + data[w-1+y*w];
|
||||
}
|
||||
if (border_count > w + h) {
|
||||
// more then half the border if FULL, invert
|
||||
for (size_t i = 0 ; i < size ; ++i) {
|
||||
data[i] = data[i] == FULL ? EMPTY : FULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------- : Image to symbol
|
||||
|
||||
bool is_mse1_symbol(const Image& img) {
|
||||
// mse1 symbols are 60x80
|
||||
if (img.GetWidth() != 60 || img.GetHeight() != 80) return false;
|
||||
// the right side is black & white
|
||||
int delta = 0;
|
||||
for (int y = 0 ; y < 80 ; ++y) {
|
||||
Byte* d = img.GetData() + 3 * (y * 60 + 20);
|
||||
for (int x = 20 ; x < 60 ; ++x) {
|
||||
int r = *d++;
|
||||
int g = *d++;
|
||||
int b = *d++;
|
||||
delta += abs(r - b) + abs(r - g) + abs(b - g);
|
||||
}
|
||||
}
|
||||
if (delta > 5000) return false; // not black & white enough
|
||||
// TODO : more checks?
|
||||
return true;
|
||||
// mse1 symbols are 60x80
|
||||
if (img.GetWidth() != 60 || img.GetHeight() != 80) return false;
|
||||
// the right side is black & white
|
||||
int delta = 0;
|
||||
for (int y = 0 ; y < 80 ; ++y) {
|
||||
Byte* d = img.GetData() + 3 * (y * 60 + 20);
|
||||
for (int x = 20 ; x < 60 ; ++x) {
|
||||
int r = *d++;
|
||||
int g = *d++;
|
||||
int b = *d++;
|
||||
delta += abs(r - b) + abs(r - g) + abs(b - g);
|
||||
}
|
||||
}
|
||||
if (delta > 5000) return false; // not black & white enough
|
||||
// TODO : more checks?
|
||||
return true;
|
||||
}
|
||||
|
||||
struct ImageData {
|
||||
int width, height;
|
||||
Byte* data;
|
||||
mutable Byte dummy;
|
||||
inline Byte& operator () (int x, int y) const {
|
||||
if (x < 0 || x >= width || y < 0 || y >= height) {
|
||||
return (dummy = EMPTY); // outside, return empty
|
||||
} else {
|
||||
return data[x + y*width];
|
||||
}
|
||||
}
|
||||
int width, height;
|
||||
Byte* data;
|
||||
mutable Byte dummy;
|
||||
inline Byte& operator () (int x, int y) const {
|
||||
if (x < 0 || x >= width || y < 0 || y >= height) {
|
||||
return (dummy = EMPTY); // outside, return empty
|
||||
} else {
|
||||
return data[x + y*width];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
bool find_symbol_shape_start(const ImageData& data, int& x_out, int& y_out) {
|
||||
for (int x = 0 ; x < data.width ; ++x) {
|
||||
for (int y = 0 ; y < data.height ; ++y) {
|
||||
if (data(x, y) == FULL && data(x, y-1) == EMPTY) {
|
||||
// the point above must be clear, we don't want to start in the 'ground'
|
||||
// also, we don't want to find things we found before
|
||||
x_out = x;
|
||||
y_out = y;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
for (int x = 0 ; x < data.width ; ++x) {
|
||||
for (int y = 0 ; y < data.height ; ++y) {
|
||||
if (data(x, y) == FULL && data(x, y-1) == EMPTY) {
|
||||
// the point above must be clear, we don't want to start in the 'ground'
|
||||
// also, we don't want to find things we found before
|
||||
x_out = x;
|
||||
y_out = y;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
SymbolShapeP read_symbol_shape(const ImageData& data) {
|
||||
// find start point
|
||||
int xs, ys;
|
||||
if (!find_symbol_shape_start(data, xs, ys)) return SymbolShapeP();
|
||||
data(xs, ys) |= MARKED;
|
||||
|
||||
SymbolShapeP shape(new SymbolShape);
|
||||
|
||||
// walk around, clockwise
|
||||
xs += 1; // start right of the found point, otherwise last_move might think we came from above
|
||||
int x = xs, y = ys;
|
||||
int old_x = x, old_y = y;
|
||||
int last_move = 1; // 1 = right or down, (as in x|y += 1)
|
||||
do {
|
||||
// the cursor (x,y) is between four pounts:
|
||||
// a b
|
||||
// .
|
||||
// c d
|
||||
bool a = data(x-1, y-1) & FULL;
|
||||
bool b = data(x, y-1) & FULL;
|
||||
bool c = data(x-1, y ) & FULL;
|
||||
bool d = data(x, y ) & FULL;
|
||||
UInt pack = (a << 12) + (b << 8) + (c << 4) + d; // 0xabcd
|
||||
switch (pack) {
|
||||
case 0x0001 : x += 1; break;
|
||||
case 0x0010 : y += 1; break;
|
||||
case 0x0011 : x += 1; break;
|
||||
case 0x0100 : y -= 1; break;
|
||||
case 0x0101 : y -= 1; break;
|
||||
case 0x0110 : y -= last_move; break; // diagonal, we can come here from two sides, from left and right
|
||||
case 0x0111 : y -= 1; break; // last_move indicates which of {b,c} we are 'attached' to
|
||||
case 0x1000 : x -= 1; break;
|
||||
case 0x1001 : x += last_move; break;
|
||||
case 0x1010 : y += 1; break;
|
||||
case 0x1011 : x += 1; break;
|
||||
case 0x1100 : x -= 1; break;
|
||||
case 0x1101 : x -= 1; break;
|
||||
case 0x1110 : y += 1; break;
|
||||
default:
|
||||
throw InternalError(_("in the ground/air"));
|
||||
}
|
||||
|
||||
// add to shape and place a mark
|
||||
shape->points.push_back(intrusive(new ControlPoint(
|
||||
double(x) / data.width,
|
||||
double(y) / data.height
|
||||
)));
|
||||
if (x > old_x) data(old_x, y) |= MARKED; // mark when moving right -> only mark the top of the shape
|
||||
last_move = (x + y) - (old_x + old_y);
|
||||
old_x = x;
|
||||
old_y = y;
|
||||
} while (x != xs || y != ys); // we will end up in the start point
|
||||
|
||||
// are we on the inside or the outside?
|
||||
if (data(x-2,y-1) & FULL) {
|
||||
shape->combine = SYMBOL_COMBINE_SUBTRACT;
|
||||
} else {
|
||||
shape->combine = SYMBOL_COMBINE_MERGE;
|
||||
}
|
||||
return shape;
|
||||
// find start point
|
||||
int xs, ys;
|
||||
if (!find_symbol_shape_start(data, xs, ys)) return SymbolShapeP();
|
||||
data(xs, ys) |= MARKED;
|
||||
|
||||
SymbolShapeP shape(new SymbolShape);
|
||||
|
||||
// walk around, clockwise
|
||||
xs += 1; // start right of the found point, otherwise last_move might think we came from above
|
||||
int x = xs, y = ys;
|
||||
int old_x = x, old_y = y;
|
||||
int last_move = 1; // 1 = right or down, (as in x|y += 1)
|
||||
do {
|
||||
// the cursor (x,y) is between four pounts:
|
||||
// a b
|
||||
// .
|
||||
// c d
|
||||
bool a = data(x-1, y-1) & FULL;
|
||||
bool b = data(x, y-1) & FULL;
|
||||
bool c = data(x-1, y ) & FULL;
|
||||
bool d = data(x, y ) & FULL;
|
||||
UInt pack = (a << 12) + (b << 8) + (c << 4) + d; // 0xabcd
|
||||
switch (pack) {
|
||||
case 0x0001 : x += 1; break;
|
||||
case 0x0010 : y += 1; break;
|
||||
case 0x0011 : x += 1; break;
|
||||
case 0x0100 : y -= 1; break;
|
||||
case 0x0101 : y -= 1; break;
|
||||
case 0x0110 : y -= last_move; break; // diagonal, we can come here from two sides, from left and right
|
||||
case 0x0111 : y -= 1; break; // last_move indicates which of {b,c} we are 'attached' to
|
||||
case 0x1000 : x -= 1; break;
|
||||
case 0x1001 : x += last_move; break;
|
||||
case 0x1010 : y += 1; break;
|
||||
case 0x1011 : x += 1; break;
|
||||
case 0x1100 : x -= 1; break;
|
||||
case 0x1101 : x -= 1; break;
|
||||
case 0x1110 : y += 1; break;
|
||||
default:
|
||||
throw InternalError(_("in the ground/air"));
|
||||
}
|
||||
|
||||
// add to shape and place a mark
|
||||
shape->points.push_back(intrusive(new ControlPoint(
|
||||
double(x) / data.width,
|
||||
double(y) / data.height
|
||||
)));
|
||||
if (x > old_x) data(old_x, y) |= MARKED; // mark when moving right -> only mark the top of the shape
|
||||
last_move = (x + y) - (old_x + old_y);
|
||||
old_x = x;
|
||||
old_y = y;
|
||||
} while (x != xs || y != ys); // we will end up in the start point
|
||||
|
||||
// are we on the inside or the outside?
|
||||
if (data(x-2,y-1) & FULL) {
|
||||
shape->combine = SYMBOL_COMBINE_SUBTRACT;
|
||||
} else {
|
||||
shape->combine = SYMBOL_COMBINE_MERGE;
|
||||
}
|
||||
return shape;
|
||||
}
|
||||
|
||||
|
||||
SymbolP image_to_symbol(Image& img) {
|
||||
int w = img.GetWidth(), h = img.GetHeight();
|
||||
// 1. threshold the image
|
||||
greyscale(img);
|
||||
threshold(img.GetData(), w, h);
|
||||
// 2. read as many symbol shapes as we can
|
||||
ImageData data = {w,h,img.GetData()};
|
||||
SymbolP symbol(new Symbol);
|
||||
while (true) {
|
||||
SymbolShapeP shape = read_symbol_shape(data);
|
||||
if (!shape) break;
|
||||
symbol->parts.push_back(shape);
|
||||
}
|
||||
reverse(symbol->parts.begin(), symbol->parts.end());
|
||||
return symbol;
|
||||
int w = img.GetWidth(), h = img.GetHeight();
|
||||
// 1. threshold the image
|
||||
greyscale(img);
|
||||
threshold(img.GetData(), w, h);
|
||||
// 2. read as many symbol shapes as we can
|
||||
ImageData data = {w,h,img.GetData()};
|
||||
SymbolP symbol(new Symbol);
|
||||
while (true) {
|
||||
SymbolShapeP shape = read_symbol_shape(data);
|
||||
if (!shape) break;
|
||||
symbol->parts.push_back(shape);
|
||||
}
|
||||
reverse(symbol->parts.begin(), symbol->parts.end());
|
||||
return symbol;
|
||||
}
|
||||
|
||||
SymbolP import_symbol(Image& img) {
|
||||
SymbolP symbol;
|
||||
if (is_mse1_symbol(img)) {
|
||||
Image img2 = img.GetSubImage(wxRect(20,0,40,40));
|
||||
symbol = image_to_symbol(img2);
|
||||
} else if (img.GetWidth() > 100 || img.GetHeight() > 100) {
|
||||
// 100x100 ought to be enough, we trow out most afterwards data anyway
|
||||
Image resampled = img.Rescale(100,100);
|
||||
symbol = image_to_symbol(resampled);
|
||||
} else {
|
||||
symbol = image_to_symbol(img);
|
||||
}
|
||||
simplify_symbol(*symbol);
|
||||
return symbol;
|
||||
SymbolP symbol;
|
||||
if (is_mse1_symbol(img)) {
|
||||
Image img2 = img.GetSubImage(wxRect(20,0,40,40));
|
||||
symbol = image_to_symbol(img2);
|
||||
} else if (img.GetWidth() > 100 || img.GetHeight() > 100) {
|
||||
// 100x100 ought to be enough, we trow out most afterwards data anyway
|
||||
Image resampled = img.Rescale(100,100);
|
||||
symbol = image_to_symbol(resampled);
|
||||
} else {
|
||||
symbol = image_to_symbol(img);
|
||||
}
|
||||
simplify_symbol(*symbol);
|
||||
return symbol;
|
||||
}
|
||||
|
||||
|
||||
@@ -247,19 +247,19 @@ SymbolP import_symbol(Image& img) {
|
||||
/** A corner is a point that has an angle between tangent greater then a treshold
|
||||
*/
|
||||
void mark_corners(SymbolShape& shape) {
|
||||
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
|
||||
ControlPoint& current = *shape.getPoint(i);
|
||||
Vector2D before = .6 * shape.getPoint(i-1)->pos + .2 * shape.getPoint(i-2)->pos + .1 * shape.getPoint(i-3)->pos + .1 * shape.getPoint(i-4)->pos;
|
||||
Vector2D after = .6 * shape.getPoint(i+1)->pos + .2 * shape.getPoint(i+2)->pos + .1 * shape.getPoint(i+3)->pos + .1 * shape.getPoint(i+4)->pos;
|
||||
before = (before - current.pos).normalized();
|
||||
after = (after - current.pos).normalized();
|
||||
if (dot(before,after) >= -0.25f) {
|
||||
// corner
|
||||
current.lock = LOCK_FREE;
|
||||
} else {
|
||||
current.lock = LOCK_DIR;
|
||||
}
|
||||
}
|
||||
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
|
||||
ControlPoint& current = *shape.getPoint(i);
|
||||
Vector2D before = .6 * shape.getPoint(i-1)->pos + .2 * shape.getPoint(i-2)->pos + .1 * shape.getPoint(i-3)->pos + .1 * shape.getPoint(i-4)->pos;
|
||||
Vector2D after = .6 * shape.getPoint(i+1)->pos + .2 * shape.getPoint(i+2)->pos + .1 * shape.getPoint(i+3)->pos + .1 * shape.getPoint(i+4)->pos;
|
||||
before = (before - current.pos).normalized();
|
||||
after = (after - current.pos).normalized();
|
||||
if (dot(before,after) >= -0.25f) {
|
||||
// corner
|
||||
current.lock = LOCK_FREE;
|
||||
} else {
|
||||
current.lock = LOCK_DIR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Merge adjacent corners
|
||||
@@ -280,116 +280,116 @@ void mark_corners(SymbolShape& shape) {
|
||||
* is the merged corner. If it is too far away, don't merge
|
||||
*/
|
||||
void merge_corners(SymbolShape& shape) {
|
||||
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
|
||||
ControlPoint& cur = *shape.getPoint(i);
|
||||
ControlPoint& prev = *shape.getPoint(i - 1);
|
||||
if (prev.lock != LOCK_FREE || cur.lock != LOCK_FREE) continue;
|
||||
// step 1. find tangent lines: try tangent lines to the first point, the second, etc.
|
||||
// and take the one that has the largest angle with ab, i.e. the smallest dot,
|
||||
// where ab is the line between the two corners
|
||||
Vector2D ab = cur.pos - prev.pos;
|
||||
double min_a_dot = 1e100, min_b_dot = 1e100;
|
||||
Vector2D a, b;
|
||||
for (int j = 0 ; j < 4 ; ++j) {
|
||||
Vector2D a_ = (shape.getPoint(i-j-1)->pos - prev.pos).normalized();
|
||||
Vector2D b_ = (shape.getPoint(i+j)->pos - cur.pos).normalized();
|
||||
double a_dot = dot(a_, ab);
|
||||
double b_dot = -dot(b_, ab);
|
||||
if (a_dot < min_a_dot) {
|
||||
min_a_dot = a_dot;
|
||||
a = a_;
|
||||
}
|
||||
if (b_dot < min_b_dot) {
|
||||
min_b_dot = b_dot;
|
||||
b = b_;
|
||||
}
|
||||
}
|
||||
// step 2. find intersection point, to solve:
|
||||
// t a + ab = u b, solve for t,u
|
||||
// Gives us:
|
||||
// t = ab cross b / b cross a
|
||||
double tden = max(0.00000001, cross(b,a));
|
||||
double t = cross(ab,b) / tden;
|
||||
// do these tangent lines intersect, and not too far away?
|
||||
// if so, then the intersection point is the merged point
|
||||
if (t >= 0 && t < 20.0) {
|
||||
prev.pos += a * -t;
|
||||
shape.points.erase(shape.points.begin() + i);
|
||||
i -= 1;
|
||||
}
|
||||
}
|
||||
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
|
||||
ControlPoint& cur = *shape.getPoint(i);
|
||||
ControlPoint& prev = *shape.getPoint(i - 1);
|
||||
if (prev.lock != LOCK_FREE || cur.lock != LOCK_FREE) continue;
|
||||
// step 1. find tangent lines: try tangent lines to the first point, the second, etc.
|
||||
// and take the one that has the largest angle with ab, i.e. the smallest dot,
|
||||
// where ab is the line between the two corners
|
||||
Vector2D ab = cur.pos - prev.pos;
|
||||
double min_a_dot = 1e100, min_b_dot = 1e100;
|
||||
Vector2D a, b;
|
||||
for (int j = 0 ; j < 4 ; ++j) {
|
||||
Vector2D a_ = (shape.getPoint(i-j-1)->pos - prev.pos).normalized();
|
||||
Vector2D b_ = (shape.getPoint(i+j)->pos - cur.pos).normalized();
|
||||
double a_dot = dot(a_, ab);
|
||||
double b_dot = -dot(b_, ab);
|
||||
if (a_dot < min_a_dot) {
|
||||
min_a_dot = a_dot;
|
||||
a = a_;
|
||||
}
|
||||
if (b_dot < min_b_dot) {
|
||||
min_b_dot = b_dot;
|
||||
b = b_;
|
||||
}
|
||||
}
|
||||
// step 2. find intersection point, to solve:
|
||||
// t a + ab = u b, solve for t,u
|
||||
// Gives us:
|
||||
// t = ab cross b / b cross a
|
||||
double tden = max(0.00000001, cross(b,a));
|
||||
double t = cross(ab,b) / tden;
|
||||
// do these tangent lines intersect, and not too far away?
|
||||
// if so, then the intersection point is the merged point
|
||||
if (t >= 0 && t < 20.0) {
|
||||
prev.pos += a * -t;
|
||||
shape.points.erase(shape.points.begin() + i);
|
||||
i -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Avarage/'blur' a symbol shape
|
||||
void avarage(SymbolShape& shape) {
|
||||
// create a copy of the points
|
||||
vector<Vector2D> old_points;
|
||||
FOR_EACH(p, shape.points) {
|
||||
old_points.push_back(p->pos);
|
||||
}
|
||||
// avarage points
|
||||
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
|
||||
ControlPoint& p = *shape.getPoint(i);
|
||||
if (p.lock == LOCK_DIR) {
|
||||
p.pos = .25 * old_points[mod(i-1, old_points.size())]
|
||||
+ .50 * p.pos
|
||||
+ .25 * old_points[mod(i+1, old_points.size())];
|
||||
}
|
||||
}
|
||||
// create a copy of the points
|
||||
vector<Vector2D> old_points;
|
||||
FOR_EACH(p, shape.points) {
|
||||
old_points.push_back(p->pos);
|
||||
}
|
||||
// avarage points
|
||||
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
|
||||
ControlPoint& p = *shape.getPoint(i);
|
||||
if (p.lock == LOCK_DIR) {
|
||||
p.pos = .25 * old_points[mod(i-1, old_points.size())]
|
||||
+ .50 * p.pos
|
||||
+ .25 * old_points[mod(i+1, old_points.size())];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a symbol shape to curves
|
||||
void convert_to_curves(SymbolShape& shape) {
|
||||
// mark all segments as curves
|
||||
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
|
||||
ControlPoint& cur = *shape.getPoint(i);
|
||||
ControlPoint& next = *shape.getPoint(i + 1);
|
||||
cur.segment_after = SEGMENT_CURVE;
|
||||
cur.segment_before = SEGMENT_CURVE;
|
||||
cur.delta_after = (next.pos - cur.pos) / 3.0;
|
||||
next.delta_before = (cur.pos - next.pos) / 3.0;
|
||||
}
|
||||
// make the curves smooth by enforcing direction constraints
|
||||
FOR_EACH(p, shape.points) {
|
||||
p->onUpdateLock();
|
||||
}
|
||||
// mark all segments as curves
|
||||
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
|
||||
ControlPoint& cur = *shape.getPoint(i);
|
||||
ControlPoint& next = *shape.getPoint(i + 1);
|
||||
cur.segment_after = SEGMENT_CURVE;
|
||||
cur.segment_before = SEGMENT_CURVE;
|
||||
cur.delta_after = (next.pos - cur.pos) / 3.0;
|
||||
next.delta_before = (cur.pos - next.pos) / 3.0;
|
||||
}
|
||||
// make the curves smooth by enforcing direction constraints
|
||||
FOR_EACH(p, shape.points) {
|
||||
p->onUpdateLock();
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert almost straight curves in a symbol shape to lines
|
||||
void straighten(SymbolShape& shape) {
|
||||
const double treshold = 0.2;
|
||||
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
|
||||
ControlPoint& cur = *shape.getPoint(i);
|
||||
ControlPoint& next = *shape.getPoint(i + 1);
|
||||
Vector2D ab = (next.pos - cur.pos).normalized();
|
||||
Vector2D aa = cur.delta_after.normalized();
|
||||
Vector2D bb = next.delta_before.normalized();
|
||||
// if the area beneath the polygon formed by the handles is small
|
||||
// then it is a straight line
|
||||
double cpDot = abs(cross(aa,ab)) + abs(cross(bb,ab));
|
||||
if (cpDot < treshold) {
|
||||
cur.segment_after = next.segment_before = SEGMENT_LINE;
|
||||
cur.delta_after = next.delta_before = Vector2D();
|
||||
cur.lock = next.lock = LOCK_FREE;
|
||||
}
|
||||
}
|
||||
const double treshold = 0.2;
|
||||
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
|
||||
ControlPoint& cur = *shape.getPoint(i);
|
||||
ControlPoint& next = *shape.getPoint(i + 1);
|
||||
Vector2D ab = (next.pos - cur.pos).normalized();
|
||||
Vector2D aa = cur.delta_after.normalized();
|
||||
Vector2D bb = next.delta_before.normalized();
|
||||
// if the area beneath the polygon formed by the handles is small
|
||||
// then it is a straight line
|
||||
double cpDot = abs(cross(aa,ab)) + abs(cross(bb,ab));
|
||||
if (cpDot < treshold) {
|
||||
cur.segment_after = next.segment_before = SEGMENT_LINE;
|
||||
cur.delta_after = next.delta_before = Vector2D();
|
||||
cur.lock = next.lock = LOCK_FREE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove unneeded points between straight lines
|
||||
void merge_lines(SymbolShape& shape) {
|
||||
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
|
||||
ControlPoint& cur = *shape.getPoint(i);
|
||||
if (cur.segment_before != cur.segment_after) continue;
|
||||
Vector2D a = shape.getPoint(i-1)->pos, b = cur.pos, c = shape.getPoint(i+1)->pos;
|
||||
Vector2D ab = (a-b).normalized();
|
||||
Vector2D bc = (b-c).normalized();
|
||||
double angle_len = abs( atan2(ab.x,ab.y) - atan2(bc.x,bc.y)) * (a-c).lengthSqr();
|
||||
bool keep = angle_len >= .0001;
|
||||
if (!keep) {
|
||||
shape.points.erase(shape.points.begin() + i);
|
||||
i -= 1;
|
||||
}
|
||||
}
|
||||
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
|
||||
ControlPoint& cur = *shape.getPoint(i);
|
||||
if (cur.segment_before != cur.segment_after) continue;
|
||||
Vector2D a = shape.getPoint(i-1)->pos, b = cur.pos, c = shape.getPoint(i+1)->pos;
|
||||
Vector2D ab = (a-b).normalized();
|
||||
Vector2D bc = (b-c).normalized();
|
||||
double angle_len = abs( atan2(ab.x,ab.y) - atan2(bc.x,bc.y)) * (a-c).lengthSqr();
|
||||
bool keep = angle_len >= .0001;
|
||||
if (!keep) {
|
||||
shape.points.erase(shape.points.begin() + i);
|
||||
i -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double cost_of_point_removal(SymbolShape& shape, int i);
|
||||
@@ -400,83 +400,83 @@ void remove_point(SymbolShape& shape, int i);
|
||||
* stop when the cost becomes too high
|
||||
*/
|
||||
void remove_points(SymbolShape& shape) {
|
||||
const double treshold = 0.0002; // maximum cost
|
||||
while (true) {
|
||||
// Find the point with the lowest cost of removal
|
||||
int best = -1;
|
||||
double best_cost = 1e100;
|
||||
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
|
||||
double cost = cost_of_point_removal(shape, i);
|
||||
if (cost < best_cost) {
|
||||
best_cost = cost;
|
||||
best = i;
|
||||
}
|
||||
}
|
||||
if (best_cost > treshold) break;
|
||||
// ... and remove it
|
||||
remove_point(shape, best);
|
||||
}
|
||||
const double treshold = 0.0002; // maximum cost
|
||||
while (true) {
|
||||
// Find the point with the lowest cost of removal
|
||||
int best = -1;
|
||||
double best_cost = 1e100;
|
||||
for (int i = 0 ; (size_t)i < shape.points.size() ; ++i) {
|
||||
double cost = cost_of_point_removal(shape, i);
|
||||
if (cost < best_cost) {
|
||||
best_cost = cost;
|
||||
best = i;
|
||||
}
|
||||
}
|
||||
if (best_cost > treshold) break;
|
||||
// ... and remove it
|
||||
remove_point(shape, best);
|
||||
}
|
||||
}
|
||||
/// Cost of removing point i from a symbol shape
|
||||
double cost_of_point_removal(SymbolShape& shape, int i) {
|
||||
ControlPoint& cur = *shape.getPoint(i);
|
||||
ControlPoint& prev = *shape.getPoint(i-1);
|
||||
ControlPoint& next = *shape.getPoint(i+1);
|
||||
if (cur.lock != LOCK_DIR) return 1e100; // don't remove corners
|
||||
|
||||
Vector2D before = cur.delta_before;
|
||||
Vector2D after = cur.delta_after;
|
||||
Vector2D ac = prev.pos - next.pos;
|
||||
// Based on SinglePointRemoveAction
|
||||
double bl = before.length() + 0.00001; // prevent division by 0
|
||||
double al = after.length() + 0.00001;
|
||||
double totl = bl + al;
|
||||
// set new handle sizes
|
||||
Vector2D after0 = prev.delta_after * totl / bl;
|
||||
Vector2D before2 = next.delta_before * totl / al;
|
||||
// determine closest point on the merged curve
|
||||
BezierCurve c(prev.pos, prev.pos + after0, next.pos + before2, next.pos);
|
||||
double t = bl/totl;
|
||||
Vector2D np = cur.pos - c.pointAt(t);
|
||||
// cost is distance to new point * length of line ~= area added/removed from shape
|
||||
return np.length() * ac.length();
|
||||
ControlPoint& cur = *shape.getPoint(i);
|
||||
ControlPoint& prev = *shape.getPoint(i-1);
|
||||
ControlPoint& next = *shape.getPoint(i+1);
|
||||
if (cur.lock != LOCK_DIR) return 1e100; // don't remove corners
|
||||
|
||||
Vector2D before = cur.delta_before;
|
||||
Vector2D after = cur.delta_after;
|
||||
Vector2D ac = prev.pos - next.pos;
|
||||
// Based on SinglePointRemoveAction
|
||||
double bl = before.length() + 0.00001; // prevent division by 0
|
||||
double al = after.length() + 0.00001;
|
||||
double totl = bl + al;
|
||||
// set new handle sizes
|
||||
Vector2D after0 = prev.delta_after * totl / bl;
|
||||
Vector2D before2 = next.delta_before * totl / al;
|
||||
// determine closest point on the merged curve
|
||||
BezierCurve c(prev.pos, prev.pos + after0, next.pos + before2, next.pos);
|
||||
double t = bl/totl;
|
||||
Vector2D np = cur.pos - c.pointAt(t);
|
||||
// cost is distance to new point * length of line ~= area added/removed from shape
|
||||
return np.length() * ac.length();
|
||||
}
|
||||
/// Remove a point from a bezier curve
|
||||
/** See SinglePointRemoveAction for algorithm */
|
||||
void remove_point(SymbolShape& shape, int i) {
|
||||
ControlPoint& cur = *shape.getPoint(i);
|
||||
ControlPoint& prev = *shape.getPoint(i-1);
|
||||
ControlPoint& next = *shape.getPoint(i+1);
|
||||
Vector2D before = cur.delta_before;
|
||||
Vector2D after = cur.delta_after;
|
||||
// Based on SinglePointRemoveAction
|
||||
double bl = before.length() + 0.00001; // prevent division by 0
|
||||
double al = after.length() + 0.00001;
|
||||
double totl = bl + al;
|
||||
// set new handle sizes
|
||||
prev.delta_after *= totl / bl;
|
||||
next.delta_before *= totl / al;
|
||||
// remove
|
||||
shape.points.erase(shape.points.begin() + i);
|
||||
ControlPoint& cur = *shape.getPoint(i);
|
||||
ControlPoint& prev = *shape.getPoint(i-1);
|
||||
ControlPoint& next = *shape.getPoint(i+1);
|
||||
Vector2D before = cur.delta_before;
|
||||
Vector2D after = cur.delta_after;
|
||||
// Based on SinglePointRemoveAction
|
||||
double bl = before.length() + 0.00001; // prevent division by 0
|
||||
double al = after.length() + 0.00001;
|
||||
double totl = bl + al;
|
||||
// set new handle sizes
|
||||
prev.delta_after *= totl / bl;
|
||||
next.delta_before *= totl / al;
|
||||
// remove
|
||||
shape.points.erase(shape.points.begin() + i);
|
||||
}
|
||||
|
||||
|
||||
void simplify_symbol_shape(SymbolShape& shape) {
|
||||
mark_corners(shape);
|
||||
merge_corners(shape);
|
||||
for (int i = 0 ; i < 3 ; ++i) {
|
||||
avarage(shape);
|
||||
}
|
||||
convert_to_curves(shape);
|
||||
remove_points(shape);
|
||||
straighten(shape);
|
||||
merge_lines(shape);
|
||||
mark_corners(shape);
|
||||
merge_corners(shape);
|
||||
for (int i = 0 ; i < 3 ; ++i) {
|
||||
avarage(shape);
|
||||
}
|
||||
convert_to_curves(shape);
|
||||
remove_points(shape);
|
||||
straighten(shape);
|
||||
merge_lines(shape);
|
||||
}
|
||||
|
||||
void simplify_symbol(Symbol& symbol) {
|
||||
FOR_EACH(pb, symbol.parts) {
|
||||
if (SymbolShape* p = pb->isSymbolShape()) {
|
||||
simplify_symbol_shape(*p);
|
||||
}
|
||||
}
|
||||
FOR_EACH(pb, symbol.parts) {
|
||||
if (SymbolShape* p = pb->isSymbolShape()) {
|
||||
simplify_symbol_shape(*p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+133
-133
@@ -22,15 +22,15 @@
|
||||
/// The file format of MSE1 files
|
||||
class MSE1FileFormat : public FileFormat {
|
||||
public:
|
||||
virtual String extension() { return _("mse"); }
|
||||
virtual String name() { return _("Magic Set Editor version 1 files (*.mse)"); }
|
||||
virtual bool canImport() { return true; }
|
||||
virtual bool canExport(const Game&) { return false; }
|
||||
virtual SetP importSet(const String& filename);
|
||||
virtual String extension() { return _("mse"); }
|
||||
virtual String name() { return _("Magic Set Editor version 1 files (*.mse)"); }
|
||||
virtual bool canImport() { return true; }
|
||||
virtual bool canExport(const Game&) { return false; }
|
||||
virtual SetP importSet(const String& filename);
|
||||
};
|
||||
|
||||
FileFormatP mse1_file_format() {
|
||||
return intrusive(new MSE1FileFormat());
|
||||
return intrusive(new MSE1FileFormat());
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Importing
|
||||
@@ -39,134 +39,134 @@ FileFormatP mse1_file_format() {
|
||||
void read_mse1_card(Set& set, wxFileInputStream& f, wxTextInputStream& file);
|
||||
|
||||
SetP MSE1FileFormat::importSet(const String& filename) {
|
||||
wxFileInputStream f(filename);
|
||||
#ifdef UNICODE
|
||||
wxTextInputStream file(f, _('\n'), wxConvLibc);
|
||||
#else
|
||||
wxTextInputStream file(f);
|
||||
#endif
|
||||
// create set
|
||||
SetP set(new Set(Game::byName(_("magic"))));
|
||||
|
||||
// file version check
|
||||
String format = file.ReadLine();
|
||||
if (format.substr(0,8) != _("MTG Set8")) {
|
||||
throw ParseError(_("Expected MSE format version 8\nTo convert files made with older versions of Magic Set Editor:\n 1. Download the latest version 1 from http:;//magicsetedtitor.sourceforge.net\n 2. Open the set, then save the set\n 3. Try to open them again in this program."));
|
||||
}
|
||||
// read general info
|
||||
set->value<TextValue>(_("title")) .value = file.ReadLine();
|
||||
set->value<TextValue>(_("artist")) .value = file.ReadLine();
|
||||
set->value<TextValue>(_("copyright")).value = file.ReadLine();
|
||||
file.ReadLine(); // border color, ignored
|
||||
String stylesheet = file.ReadLine();
|
||||
set->apprentice_code = file.ReadLine(); // apprentice prefix
|
||||
file.ReadLine(); // 'formatN'?, not even used by MSE1 :S, ignored
|
||||
file.ReadLine(); // 'formatS'?, same, ignored
|
||||
file.ReadLine(); // symbol filename, ignored
|
||||
file.ReadLine(); // use black symbol for all rarities, ignored
|
||||
String desc, line;
|
||||
while (!f.Eof()) {
|
||||
line = file.ReadLine();
|
||||
if (line == _("\xFF")) break;
|
||||
desc += line;
|
||||
}
|
||||
set->value<TextValue>(_("description")).value = desc;
|
||||
|
||||
// load stylesheet
|
||||
if (stylesheet.substr(0,3) == _("old")) {
|
||||
try {
|
||||
set->stylesheet = StyleSheet::byGameAndName(*set->game, _("old"));
|
||||
} catch (const Error&) {
|
||||
// If old style doesn't work try the new one
|
||||
set->stylesheet = StyleSheet::byGameAndName(*set->game, _("new"));
|
||||
}
|
||||
} else {
|
||||
set->stylesheet = StyleSheet::byGameAndName(*set->game, _("new"));
|
||||
}
|
||||
|
||||
// read cards
|
||||
CardP current_card;
|
||||
while (!f.Eof()) {
|
||||
read_mse1_card(*set, f, file);
|
||||
}
|
||||
|
||||
// done
|
||||
set->validate();
|
||||
return set;
|
||||
wxFileInputStream f(filename);
|
||||
#ifdef UNICODE
|
||||
wxTextInputStream file(f, _('\n'), wxConvLibc);
|
||||
#else
|
||||
wxTextInputStream file(f);
|
||||
#endif
|
||||
// create set
|
||||
SetP set(new Set(Game::byName(_("magic"))));
|
||||
|
||||
// file version check
|
||||
String format = file.ReadLine();
|
||||
if (format.substr(0,8) != _("MTG Set8")) {
|
||||
throw ParseError(_("Expected MSE format version 8\nTo convert files made with older versions of Magic Set Editor:\n 1. Download the latest version 1 from http:;//magicsetedtitor.sourceforge.net\n 2. Open the set, then save the set\n 3. Try to open them again in this program."));
|
||||
}
|
||||
// read general info
|
||||
set->value<TextValue>(_("title")) .value = file.ReadLine();
|
||||
set->value<TextValue>(_("artist")) .value = file.ReadLine();
|
||||
set->value<TextValue>(_("copyright")).value = file.ReadLine();
|
||||
file.ReadLine(); // border color, ignored
|
||||
String stylesheet = file.ReadLine();
|
||||
set->apprentice_code = file.ReadLine(); // apprentice prefix
|
||||
file.ReadLine(); // 'formatN'?, not even used by MSE1 :S, ignored
|
||||
file.ReadLine(); // 'formatS'?, same, ignored
|
||||
file.ReadLine(); // symbol filename, ignored
|
||||
file.ReadLine(); // use black symbol for all rarities, ignored
|
||||
String desc, line;
|
||||
while (!f.Eof()) {
|
||||
line = file.ReadLine();
|
||||
if (line == _("\xFF")) break;
|
||||
desc += line;
|
||||
}
|
||||
set->value<TextValue>(_("description")).value = desc;
|
||||
|
||||
// load stylesheet
|
||||
if (stylesheet.substr(0,3) == _("old")) {
|
||||
try {
|
||||
set->stylesheet = StyleSheet::byGameAndName(*set->game, _("old"));
|
||||
} catch (const Error&) {
|
||||
// If old style doesn't work try the new one
|
||||
set->stylesheet = StyleSheet::byGameAndName(*set->game, _("new"));
|
||||
}
|
||||
} else {
|
||||
set->stylesheet = StyleSheet::byGameAndName(*set->game, _("new"));
|
||||
}
|
||||
|
||||
// read cards
|
||||
CardP current_card;
|
||||
while (!f.Eof()) {
|
||||
read_mse1_card(*set, f, file);
|
||||
}
|
||||
|
||||
// done
|
||||
set->validate();
|
||||
return set;
|
||||
}
|
||||
|
||||
void read_mse1_card(Set& set, wxFileInputStream& f, wxTextInputStream& file) {
|
||||
CardP card(new Card(*set.game));
|
||||
while (!f.Eof()) {
|
||||
// read a line
|
||||
String line = file.ReadLine();
|
||||
if (line.empty()) continue;
|
||||
Char type = line.GetChar(0);
|
||||
line = line.substr(1);
|
||||
// interpret this line
|
||||
switch (type) {
|
||||
case 'A': { // done
|
||||
set.cards.push_back(card);
|
||||
return;
|
||||
} case 'B': { // name
|
||||
card->value<TextValue>(_("name")) .value.assign(line);
|
||||
break;
|
||||
} case 'C': case 'D': { // image filename
|
||||
String image_file = set.newFileName(_("image"),_("")); // a new unique name in the package
|
||||
if (wxCopyFile(line, set.nameOut(image_file), true)) {
|
||||
card->value<ImageValue>(_("image")) .filename = image_file;
|
||||
}
|
||||
break;
|
||||
} case 'E': { // super type
|
||||
card->value<TextValue>(_("super type")) .value.assign(line);
|
||||
break;
|
||||
} case 'F': { // sub type
|
||||
card->value<TextValue>(_("sub type")) .value.assign(line);
|
||||
break;
|
||||
} case 'G': { // casting cost
|
||||
card->value<TextValue>(_("casting cost")).value.assign(line);
|
||||
break;
|
||||
} case 'H': { // rarity
|
||||
String rarity;
|
||||
if (line == _("(U)")) rarity = _("uncommon");
|
||||
else if (line == _("(R)")) rarity = _("rare");
|
||||
else rarity = _("common");
|
||||
card->value<ChoiceValue>(_("rarity")) .value.assign(rarity);
|
||||
break;
|
||||
} case 'I': { // power/thoughness
|
||||
size_t pos = line.find_first_of(_('/'));
|
||||
if (pos != String::npos) {
|
||||
card->value<TextValue>(_("power")) .value.assign(line.substr(0, pos));
|
||||
card->value<TextValue>(_("toughness")) .value.assign(line.substr(pos+1));
|
||||
}
|
||||
break;
|
||||
} case 'J': { // rule text or part of text
|
||||
Defaultable<String>& text = card->value<TextValue>(_("rule text")).value;
|
||||
if (!text().empty()) text.mutate() += _('\n');
|
||||
text.mutate() += line;
|
||||
break;
|
||||
} case 'K': { // flavor text or part of text
|
||||
Defaultable<String>& text = card->value<TextValue>(_("flavor text")).value;
|
||||
if (!text().empty()) text.mutate() += _('\n');
|
||||
text.mutate() += line;
|
||||
break;
|
||||
} case 'L': { // card color (if not default)
|
||||
// decode color
|
||||
String color;
|
||||
if (line == _("1")) color = _("white");
|
||||
else if (line == _("2")) color = _("blue");
|
||||
else if (line == _("3")) color = _("black");
|
||||
else if (line == _("4")) color = _("red");
|
||||
else if (line == _("5")) color = _("green");
|
||||
else if (line == _("6")) color = _("colorless");
|
||||
else if (line == _("7")) color = _("land");
|
||||
else if (line == _("9")) color = _("multicolor");
|
||||
else color = _("colorless");
|
||||
card->value<ChoiceValue>(_("card color")).value.assign(color);
|
||||
break;
|
||||
} default: {
|
||||
throw ParseError(_("Not a valid MSE1 file"));
|
||||
}
|
||||
}
|
||||
}
|
||||
CardP card(new Card(*set.game));
|
||||
while (!f.Eof()) {
|
||||
// read a line
|
||||
String line = file.ReadLine();
|
||||
if (line.empty()) continue;
|
||||
Char type = line.GetChar(0);
|
||||
line = line.substr(1);
|
||||
// interpret this line
|
||||
switch (type) {
|
||||
case 'A': { // done
|
||||
set.cards.push_back(card);
|
||||
return;
|
||||
} case 'B': { // name
|
||||
card->value<TextValue>(_("name")) .value.assign(line);
|
||||
break;
|
||||
} case 'C': case 'D': { // image filename
|
||||
String image_file = set.newFileName(_("image"),_("")); // a new unique name in the package
|
||||
if (wxCopyFile(line, set.nameOut(image_file), true)) {
|
||||
card->value<ImageValue>(_("image")) .filename = image_file;
|
||||
}
|
||||
break;
|
||||
} case 'E': { // super type
|
||||
card->value<TextValue>(_("super type")) .value.assign(line);
|
||||
break;
|
||||
} case 'F': { // sub type
|
||||
card->value<TextValue>(_("sub type")) .value.assign(line);
|
||||
break;
|
||||
} case 'G': { // casting cost
|
||||
card->value<TextValue>(_("casting cost")).value.assign(line);
|
||||
break;
|
||||
} case 'H': { // rarity
|
||||
String rarity;
|
||||
if (line == _("(U)")) rarity = _("uncommon");
|
||||
else if (line == _("(R)")) rarity = _("rare");
|
||||
else rarity = _("common");
|
||||
card->value<ChoiceValue>(_("rarity")) .value.assign(rarity);
|
||||
break;
|
||||
} case 'I': { // power/thoughness
|
||||
size_t pos = line.find_first_of(_('/'));
|
||||
if (pos != String::npos) {
|
||||
card->value<TextValue>(_("power")) .value.assign(line.substr(0, pos));
|
||||
card->value<TextValue>(_("toughness")) .value.assign(line.substr(pos+1));
|
||||
}
|
||||
break;
|
||||
} case 'J': { // rule text or part of text
|
||||
Defaultable<String>& text = card->value<TextValue>(_("rule text")).value;
|
||||
if (!text().empty()) text.mutate() += _('\n');
|
||||
text.mutate() += line;
|
||||
break;
|
||||
} case 'K': { // flavor text or part of text
|
||||
Defaultable<String>& text = card->value<TextValue>(_("flavor text")).value;
|
||||
if (!text().empty()) text.mutate() += _('\n');
|
||||
text.mutate() += line;
|
||||
break;
|
||||
} case 'L': { // card color (if not default)
|
||||
// decode color
|
||||
String color;
|
||||
if (line == _("1")) color = _("white");
|
||||
else if (line == _("2")) color = _("blue");
|
||||
else if (line == _("3")) color = _("black");
|
||||
else if (line == _("4")) color = _("red");
|
||||
else if (line == _("5")) color = _("green");
|
||||
else if (line == _("6")) color = _("colorless");
|
||||
else if (line == _("7")) color = _("land");
|
||||
else if (line == _("9")) color = _("multicolor");
|
||||
else color = _("colorless");
|
||||
card->value<ChoiceValue>(_("card color")).value.assign(color);
|
||||
break;
|
||||
} default: {
|
||||
throw ParseError(_("Not a valid MSE1 file"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+27
-27
@@ -16,34 +16,34 @@
|
||||
/// The file format of MSE2 files
|
||||
class MSE2FileFormat : public FileFormat {
|
||||
public:
|
||||
virtual String extension() { return _("mse-set"); }
|
||||
virtual String matches() { return _("*.mse-set;set"); }
|
||||
virtual String name() { return _("Magic Set Editor sets (*.mse-set)"); }
|
||||
virtual bool canImport() { return true; }
|
||||
virtual bool canExport(const Game&) { return true; }
|
||||
virtual SetP importSet(const String& filename) {
|
||||
wxString set_name = filename;
|
||||
// Strip "/set" from the end, newer wx versions have a function for this:
|
||||
// filename.EndsWith(_("/set"), &set_name);
|
||||
if (filename.size() > 4 && filename.substr(filename.size()-4) == _("/set")) {
|
||||
set_name = filename.substr(0, filename.size()-4);
|
||||
}
|
||||
SetP set(new Set);
|
||||
set->open(set_name);
|
||||
settings.addRecentFile(set_name);
|
||||
return set;
|
||||
}
|
||||
virtual void exportSet(Set& set, const String& filename, bool is_copy) {
|
||||
if (is_copy) {
|
||||
set.saveCopy(filename);
|
||||
} else {
|
||||
set.saveAs(filename);
|
||||
settings.addRecentFile(filename);
|
||||
set.actions.setSavePoint();
|
||||
}
|
||||
}
|
||||
virtual String extension() { return _("mse-set"); }
|
||||
virtual String matches() { return _("*.mse-set;set"); }
|
||||
virtual String name() { return _("Magic Set Editor sets (*.mse-set)"); }
|
||||
virtual bool canImport() { return true; }
|
||||
virtual bool canExport(const Game&) { return true; }
|
||||
virtual SetP importSet(const String& filename) {
|
||||
wxString set_name = filename;
|
||||
// Strip "/set" from the end, newer wx versions have a function for this:
|
||||
// filename.EndsWith(_("/set"), &set_name);
|
||||
if (filename.size() > 4 && filename.substr(filename.size()-4) == _("/set")) {
|
||||
set_name = filename.substr(0, filename.size()-4);
|
||||
}
|
||||
SetP set(new Set);
|
||||
set->open(set_name);
|
||||
settings.addRecentFile(set_name);
|
||||
return set;
|
||||
}
|
||||
virtual void exportSet(Set& set, const String& filename, bool is_copy) {
|
||||
if (is_copy) {
|
||||
set.saveCopy(filename);
|
||||
} else {
|
||||
set.saveAs(filename);
|
||||
settings.addRecentFile(filename);
|
||||
set.actions.setSavePoint();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
FileFormatP mse2_file_format() {
|
||||
return intrusive(new MSE2FileFormat());
|
||||
return intrusive(new MSE2FileFormat());
|
||||
}
|
||||
|
||||
+225
-225
@@ -25,253 +25,253 @@ DECLARE_TYPEOF_COLLECTION(CardP);
|
||||
/// The file format of Mtg Editor files
|
||||
class MtgEditorFileFormat : public FileFormat {
|
||||
public:
|
||||
virtual String extension() { return _("set"); }
|
||||
virtual String name() { return _("Mtg Editor files (*.set)"); }
|
||||
virtual bool canImport() { return true; }
|
||||
virtual bool canExport(const Game&) { return false; }
|
||||
virtual SetP importSet(const String& filename);
|
||||
virtual String extension() { return _("set"); }
|
||||
virtual String name() { return _("Mtg Editor files (*.set)"); }
|
||||
virtual bool canImport() { return true; }
|
||||
virtual bool canExport(const Game&) { return false; }
|
||||
virtual SetP importSet(const String& filename);
|
||||
private:
|
||||
// Filter: se filename -> image directory
|
||||
// based on MtgEditor's: CardSet.getImageFolder
|
||||
String filter1(const String& str);
|
||||
// Filter: card name -> image name
|
||||
// based on MtgEditor's: Tools::purgeSpecialChars
|
||||
String filter2(const String& str);
|
||||
// Remove mtg editor tags
|
||||
void untag(const CardP& card, const String& field);
|
||||
// Translate all tags, mana tags get converted to <sym>, other tags are removed
|
||||
void translateTags(String& value);
|
||||
// Filter: se filename -> image directory
|
||||
// based on MtgEditor's: CardSet.getImageFolder
|
||||
String filter1(const String& str);
|
||||
// Filter: card name -> image name
|
||||
// based on MtgEditor's: Tools::purgeSpecialChars
|
||||
String filter2(const String& str);
|
||||
// Remove mtg editor tags
|
||||
void untag(const CardP& card, const String& field);
|
||||
// Translate all tags, mana tags get converted to <sym>, other tags are removed
|
||||
void translateTags(String& value);
|
||||
};
|
||||
|
||||
FileFormatP mtg_editor_file_format() {
|
||||
return intrusive(new MtgEditorFileFormat());
|
||||
return intrusive(new MtgEditorFileFormat());
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Importing
|
||||
|
||||
SetP MtgEditorFileFormat::importSet(const String& filename) {
|
||||
wxFileInputStream f(filename);
|
||||
#ifdef UNICODE
|
||||
wxTextInputStream file(f, _('\n'), wxConvLibc);
|
||||
#else
|
||||
wxTextInputStream file(f);
|
||||
#endif
|
||||
// create set
|
||||
SetP set(new Set(Game::byName(_("magic"))));
|
||||
|
||||
// parsing state
|
||||
CardP current_card;
|
||||
Defaultable<String>* target = nullptr; // value we are currently reading
|
||||
String layout = _("8e");
|
||||
String set_date, card_date;
|
||||
bool first = true;
|
||||
|
||||
// read file
|
||||
while (!f.Eof()) {
|
||||
// read a line
|
||||
if (!current_card) current_card = intrusive(new Card(*set->game));
|
||||
String line = file.ReadLine();
|
||||
if (line == _("#SET###########")) { // set.title
|
||||
target = &set->value<TextValue>(_("title")).value;
|
||||
} else if (line == _("#SETDATE#######")) { // date
|
||||
// remember date for generation of illustration filename
|
||||
target = nullptr;
|
||||
set_date = file.ReadLine();
|
||||
} else if (line == _("#SHOWRARITY####") ||
|
||||
line == _("#FONTS#########") || line == _("#COUNT#########")) { // ignore
|
||||
target = nullptr;
|
||||
file.ReadLine();
|
||||
} else if (line == _("#LAYOUT########")) { // layout type
|
||||
target = nullptr;
|
||||
layout = file.ReadLine();
|
||||
} else if (line == _("#CARD##########") || line == _("#EOF###########")) { // begin/end of card
|
||||
if (!first) {
|
||||
// First [CARD##] indicates only the start of a card, subsequent ones also the end of the previous
|
||||
// card. We only care about the latter
|
||||
// remove all tags from all text values
|
||||
untag(current_card, _("name"));
|
||||
untag(current_card, _("super type"));
|
||||
untag(current_card, _("sub type"));
|
||||
untag(current_card, _("casting cost"));
|
||||
untag(current_card, _("flavor text"));
|
||||
untag(current_card, _("illustrator"));
|
||||
untag(current_card, _("copyright"));
|
||||
untag(current_card, _("power"));
|
||||
untag(current_card, _("toughness"));
|
||||
// translate mtg editor tags to mse2 tags
|
||||
translateTags(current_card->value<TextValue>(_("rule text")).value.mutate());
|
||||
// add the card to the set
|
||||
set->cards.push_back(current_card);
|
||||
}
|
||||
first = false;
|
||||
current_card = intrusive(new Card(*set->game));
|
||||
target = ¤t_card->value<TextValue>(_("name")).value;
|
||||
} else if (line == _("#DATE##########")) { // date
|
||||
// remember date for generation of illustration filename
|
||||
target = nullptr;
|
||||
card_date = file.ReadLine();
|
||||
} else if (line == _("#TYPE##########")) { // super type
|
||||
target = ¤t_card->value<TextValue>(_("super type")).value;
|
||||
} else if (line == _("#SUBTYPE#######")) { // sub type
|
||||
target = ¤t_card->value<TextValue>(_("sub type")).value;
|
||||
} else if (line == _("#COST##########")) { // casting cost
|
||||
target = ¤t_card->value<TextValue>(_("casting cost")).value;
|
||||
} else if (line == _("#RARITY########") || line == _("#FREQUENCY#####")) { // rarity
|
||||
target = 0;
|
||||
line = file.ReadLine();
|
||||
if (line == _("0")) current_card->value<ChoiceValue>(_("rarity")).value.assign(_("common"));
|
||||
else if (line == _("1")) current_card->value<ChoiceValue>(_("rarity")).value.assign(_("uncommon"));
|
||||
else current_card->value<ChoiceValue>(_("rarity")).value.assign(_("rare"));
|
||||
} else if (line == _("#COLOR#########")) { // card color
|
||||
target = 0;
|
||||
line = file.ReadLine();
|
||||
current_card->value<ChoiceValue>(_("card color")).value.assign(line);
|
||||
} else if (line == _("#AUTOBG########")) { // card color.isDefault
|
||||
target = 0;
|
||||
line = file.ReadLine();
|
||||
if (line == _("TRUE")) {
|
||||
current_card->value<ChoiceValue>(_("card color")).value.makeDefault();
|
||||
}
|
||||
} else if (line == _("#RULETEXT######")) { // rule text
|
||||
target = ¤t_card->value<TextValue>(_("rule text")).value;
|
||||
} else if (line == _("#FLAVORTEXT####")) { // flavor text
|
||||
target = ¤t_card->value<TextValue>(_("flavor text")).value;
|
||||
} else if (line == _("#ARTIST########")) { // illustrator
|
||||
target = ¤t_card->value<TextValue>(_("illustrator")).value;
|
||||
} else if (line == _("#COPYRIGHT#####")) { // copyright
|
||||
target = ¤t_card->value<TextValue>(_("copyright")).value;
|
||||
} else if (line == _("#POWER#########")) { // power
|
||||
target = ¤t_card->value<TextValue>(_("power")).value;
|
||||
} else if (line == _("#TOUGHNESS#####")) { // toughness
|
||||
target = ¤t_card->value<TextValue>(_("toughness")).value;
|
||||
} else if (line == _("#ILLUSTRATION##") || line == _("#ILLUSTRATION8#")) { // image
|
||||
target = 0;
|
||||
line = file.ReadLine();
|
||||
if (!wxFileExists(line)) {
|
||||
// based on card name and date
|
||||
line = filter1(filename) + set_date + _("/") +
|
||||
filter2(current_card->value<TextValue>(_("name")).value) + card_date + _(".jpg");
|
||||
}
|
||||
// copy image into set
|
||||
if (wxFileExists(line)) {
|
||||
String image_file = set->newFileName(_("image"),_(""));
|
||||
if (wxCopyFile(line, set->nameOut(image_file), true)) {
|
||||
current_card->value<ImageValue>(_("image")).filename = image_file;
|
||||
}
|
||||
}
|
||||
} else if (line == _("#TOMBSTONE#####")) { // tombstone
|
||||
target = 0;
|
||||
line = file.ReadLine();
|
||||
current_card->value<ChoiceValue>(_("card symbol")).value.assign(
|
||||
line==_("TRUE") ? _("tombstone") : _("none")
|
||||
);
|
||||
} else {
|
||||
// normal text
|
||||
if (target != 0) { // value of a text field
|
||||
if (!target->isDefault()) target->mutate() += _("\n");
|
||||
target->mutate() += line;
|
||||
} else {
|
||||
throw ParseError(_("Error in Mtg Editor file, unexpected text:\n") + line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set defaults for artist and copyright to that of the first card
|
||||
if (!set->cards.empty()) {
|
||||
String artist = set->cards[0]->value<TextValue>(_("illustrator")).value;
|
||||
String copyright = set->cards[0]->value<TextValue>(_("copyright")) .value;
|
||||
set->value<TextValue>(_("artist")) .value.assign(artist);
|
||||
set->value<TextValue>(_("copyright")).value.assign(copyright);
|
||||
// which cards have this value?
|
||||
FOR_EACH(card, set->cards) {
|
||||
Defaultable<String>& card_artist = card->value<TextValue>(_("illustrator")).value;
|
||||
Defaultable<String>& card_copyright = card->value<TextValue>(_("copyright")) .value;
|
||||
if (card_artist == artist) card_artist.makeDefault();
|
||||
if (card_copyright == copyright) card_copyright.makeDefault();
|
||||
}
|
||||
}
|
||||
|
||||
// Load stylesheet
|
||||
if (layout != _("8e")) {
|
||||
try {
|
||||
set->stylesheet = StyleSheet::byGameAndName(*set->game, _("old"));
|
||||
} catch (const Error&) {
|
||||
// If old style doesn't work try the new one
|
||||
set->stylesheet = StyleSheet::byGameAndName(*set->game, _("new"));
|
||||
}
|
||||
} else {
|
||||
set->stylesheet = StyleSheet::byGameAndName(*set->game, _("new"));
|
||||
}
|
||||
|
||||
set->validate();
|
||||
return set;
|
||||
wxFileInputStream f(filename);
|
||||
#ifdef UNICODE
|
||||
wxTextInputStream file(f, _('\n'), wxConvLibc);
|
||||
#else
|
||||
wxTextInputStream file(f);
|
||||
#endif
|
||||
// create set
|
||||
SetP set(new Set(Game::byName(_("magic"))));
|
||||
|
||||
// parsing state
|
||||
CardP current_card;
|
||||
Defaultable<String>* target = nullptr; // value we are currently reading
|
||||
String layout = _("8e");
|
||||
String set_date, card_date;
|
||||
bool first = true;
|
||||
|
||||
// read file
|
||||
while (!f.Eof()) {
|
||||
// read a line
|
||||
if (!current_card) current_card = intrusive(new Card(*set->game));
|
||||
String line = file.ReadLine();
|
||||
if (line == _("#SET###########")) { // set.title
|
||||
target = &set->value<TextValue>(_("title")).value;
|
||||
} else if (line == _("#SETDATE#######")) { // date
|
||||
// remember date for generation of illustration filename
|
||||
target = nullptr;
|
||||
set_date = file.ReadLine();
|
||||
} else if (line == _("#SHOWRARITY####") ||
|
||||
line == _("#FONTS#########") || line == _("#COUNT#########")) { // ignore
|
||||
target = nullptr;
|
||||
file.ReadLine();
|
||||
} else if (line == _("#LAYOUT########")) { // layout type
|
||||
target = nullptr;
|
||||
layout = file.ReadLine();
|
||||
} else if (line == _("#CARD##########") || line == _("#EOF###########")) { // begin/end of card
|
||||
if (!first) {
|
||||
// First [CARD##] indicates only the start of a card, subsequent ones also the end of the previous
|
||||
// card. We only care about the latter
|
||||
// remove all tags from all text values
|
||||
untag(current_card, _("name"));
|
||||
untag(current_card, _("super type"));
|
||||
untag(current_card, _("sub type"));
|
||||
untag(current_card, _("casting cost"));
|
||||
untag(current_card, _("flavor text"));
|
||||
untag(current_card, _("illustrator"));
|
||||
untag(current_card, _("copyright"));
|
||||
untag(current_card, _("power"));
|
||||
untag(current_card, _("toughness"));
|
||||
// translate mtg editor tags to mse2 tags
|
||||
translateTags(current_card->value<TextValue>(_("rule text")).value.mutate());
|
||||
// add the card to the set
|
||||
set->cards.push_back(current_card);
|
||||
}
|
||||
first = false;
|
||||
current_card = intrusive(new Card(*set->game));
|
||||
target = ¤t_card->value<TextValue>(_("name")).value;
|
||||
} else if (line == _("#DATE##########")) { // date
|
||||
// remember date for generation of illustration filename
|
||||
target = nullptr;
|
||||
card_date = file.ReadLine();
|
||||
} else if (line == _("#TYPE##########")) { // super type
|
||||
target = ¤t_card->value<TextValue>(_("super type")).value;
|
||||
} else if (line == _("#SUBTYPE#######")) { // sub type
|
||||
target = ¤t_card->value<TextValue>(_("sub type")).value;
|
||||
} else if (line == _("#COST##########")) { // casting cost
|
||||
target = ¤t_card->value<TextValue>(_("casting cost")).value;
|
||||
} else if (line == _("#RARITY########") || line == _("#FREQUENCY#####")) { // rarity
|
||||
target = 0;
|
||||
line = file.ReadLine();
|
||||
if (line == _("0")) current_card->value<ChoiceValue>(_("rarity")).value.assign(_("common"));
|
||||
else if (line == _("1")) current_card->value<ChoiceValue>(_("rarity")).value.assign(_("uncommon"));
|
||||
else current_card->value<ChoiceValue>(_("rarity")).value.assign(_("rare"));
|
||||
} else if (line == _("#COLOR#########")) { // card color
|
||||
target = 0;
|
||||
line = file.ReadLine();
|
||||
current_card->value<ChoiceValue>(_("card color")).value.assign(line);
|
||||
} else if (line == _("#AUTOBG########")) { // card color.isDefault
|
||||
target = 0;
|
||||
line = file.ReadLine();
|
||||
if (line == _("TRUE")) {
|
||||
current_card->value<ChoiceValue>(_("card color")).value.makeDefault();
|
||||
}
|
||||
} else if (line == _("#RULETEXT######")) { // rule text
|
||||
target = ¤t_card->value<TextValue>(_("rule text")).value;
|
||||
} else if (line == _("#FLAVORTEXT####")) { // flavor text
|
||||
target = ¤t_card->value<TextValue>(_("flavor text")).value;
|
||||
} else if (line == _("#ARTIST########")) { // illustrator
|
||||
target = ¤t_card->value<TextValue>(_("illustrator")).value;
|
||||
} else if (line == _("#COPYRIGHT#####")) { // copyright
|
||||
target = ¤t_card->value<TextValue>(_("copyright")).value;
|
||||
} else if (line == _("#POWER#########")) { // power
|
||||
target = ¤t_card->value<TextValue>(_("power")).value;
|
||||
} else if (line == _("#TOUGHNESS#####")) { // toughness
|
||||
target = ¤t_card->value<TextValue>(_("toughness")).value;
|
||||
} else if (line == _("#ILLUSTRATION##") || line == _("#ILLUSTRATION8#")) { // image
|
||||
target = 0;
|
||||
line = file.ReadLine();
|
||||
if (!wxFileExists(line)) {
|
||||
// based on card name and date
|
||||
line = filter1(filename) + set_date + _("/") +
|
||||
filter2(current_card->value<TextValue>(_("name")).value) + card_date + _(".jpg");
|
||||
}
|
||||
// copy image into set
|
||||
if (wxFileExists(line)) {
|
||||
String image_file = set->newFileName(_("image"),_(""));
|
||||
if (wxCopyFile(line, set->nameOut(image_file), true)) {
|
||||
current_card->value<ImageValue>(_("image")).filename = image_file;
|
||||
}
|
||||
}
|
||||
} else if (line == _("#TOMBSTONE#####")) { // tombstone
|
||||
target = 0;
|
||||
line = file.ReadLine();
|
||||
current_card->value<ChoiceValue>(_("card symbol")).value.assign(
|
||||
line==_("TRUE") ? _("tombstone") : _("none")
|
||||
);
|
||||
} else {
|
||||
// normal text
|
||||
if (target != 0) { // value of a text field
|
||||
if (!target->isDefault()) target->mutate() += _("\n");
|
||||
target->mutate() += line;
|
||||
} else {
|
||||
throw ParseError(_("Error in Mtg Editor file, unexpected text:\n") + line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set defaults for artist and copyright to that of the first card
|
||||
if (!set->cards.empty()) {
|
||||
String artist = set->cards[0]->value<TextValue>(_("illustrator")).value;
|
||||
String copyright = set->cards[0]->value<TextValue>(_("copyright")) .value;
|
||||
set->value<TextValue>(_("artist")) .value.assign(artist);
|
||||
set->value<TextValue>(_("copyright")).value.assign(copyright);
|
||||
// which cards have this value?
|
||||
FOR_EACH(card, set->cards) {
|
||||
Defaultable<String>& card_artist = card->value<TextValue>(_("illustrator")).value;
|
||||
Defaultable<String>& card_copyright = card->value<TextValue>(_("copyright")) .value;
|
||||
if (card_artist == artist) card_artist.makeDefault();
|
||||
if (card_copyright == copyright) card_copyright.makeDefault();
|
||||
}
|
||||
}
|
||||
|
||||
// Load stylesheet
|
||||
if (layout != _("8e")) {
|
||||
try {
|
||||
set->stylesheet = StyleSheet::byGameAndName(*set->game, _("old"));
|
||||
} catch (const Error&) {
|
||||
// If old style doesn't work try the new one
|
||||
set->stylesheet = StyleSheet::byGameAndName(*set->game, _("new"));
|
||||
}
|
||||
} else {
|
||||
set->stylesheet = StyleSheet::byGameAndName(*set->game, _("new"));
|
||||
}
|
||||
|
||||
set->validate();
|
||||
return set;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Filtering
|
||||
|
||||
String MtgEditorFileFormat::filter1(const String& str) {
|
||||
String before, after, ret;
|
||||
// split path name
|
||||
size_t pos = str.find_last_of(_("/\\"));
|
||||
if (pos == String::npos) {
|
||||
before = _(""); after = str;
|
||||
} else {
|
||||
before = str.substr(0, pos + 1);
|
||||
after = str.substr(pos + 1);
|
||||
}
|
||||
// filter
|
||||
FOR_EACH(c, after) {
|
||||
if (isAlnum(c)) ret += c;
|
||||
else ret += _('_');
|
||||
}
|
||||
return before + ret;
|
||||
String before, after, ret;
|
||||
// split path name
|
||||
size_t pos = str.find_last_of(_("/\\"));
|
||||
if (pos == String::npos) {
|
||||
before = _(""); after = str;
|
||||
} else {
|
||||
before = str.substr(0, pos + 1);
|
||||
after = str.substr(pos + 1);
|
||||
}
|
||||
// filter
|
||||
FOR_EACH(c, after) {
|
||||
if (isAlnum(c)) ret += c;
|
||||
else ret += _('_');
|
||||
}
|
||||
return before + ret;
|
||||
}
|
||||
|
||||
String MtgEditorFileFormat::filter2(const String& str) {
|
||||
String ret;
|
||||
FOR_EACH_CONST(c, str) {
|
||||
if (isAlnum(c)) ret += c;
|
||||
else if (c==_(' ') || c==_('-')) ret += _('_');
|
||||
}
|
||||
return ret;
|
||||
String ret;
|
||||
FOR_EACH_CONST(c, str) {
|
||||
if (isAlnum(c)) ret += c;
|
||||
else if (c==_(' ') || c==_('-')) ret += _('_');
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void MtgEditorFileFormat::untag(const CardP& card, const String& field) {
|
||||
Defaultable<String>& value = card->value<TextValue>(field).value;
|
||||
value.assignDontChangeDefault(untag_no_escape(value));
|
||||
Defaultable<String>& value = card->value<TextValue>(field).value;
|
||||
value.assignDontChangeDefault(untag_no_escape(value));
|
||||
}
|
||||
|
||||
|
||||
void MtgEditorFileFormat::translateTags(String& value) {
|
||||
// Translate tags
|
||||
String ret;
|
||||
size_t pos = 0;
|
||||
while (pos < value.size()) {
|
||||
Char c = value.GetChar(pos);
|
||||
++pos;
|
||||
if (c == _('<')) {
|
||||
String tag;
|
||||
while (pos < value.size()) {
|
||||
c = value.GetChar(pos);
|
||||
++pos;
|
||||
if (c == _('>')) break;
|
||||
else tag += c;
|
||||
}
|
||||
tag.MakeUpper();
|
||||
unsigned long number;
|
||||
if (tag==_("W") || tag==_("U") || tag==_("B") || tag==_("R") || tag==_("G") || tag==_("X") || tag==_("Y") || tag==_("Z")) {
|
||||
ret += _("<sym>") + tag + _("</sym>");
|
||||
} else if (tag.ToULong(&number)) {
|
||||
ret += _("<sym>") + tag + _("</sym>");
|
||||
} else if (tag==_("T") || tag==_("TAP")) {
|
||||
ret += _("<sym>T</sym>");
|
||||
} else if (tag==_("THIS")) {
|
||||
ret += _("~");
|
||||
}
|
||||
} else {
|
||||
ret += c;
|
||||
}
|
||||
}
|
||||
// Join adjecent symbol sections
|
||||
value = replace_all(ret, _("</sym><sym>"), _(""));
|
||||
// Translate tags
|
||||
String ret;
|
||||
size_t pos = 0;
|
||||
while (pos < value.size()) {
|
||||
Char c = value.GetChar(pos);
|
||||
++pos;
|
||||
if (c == _('<')) {
|
||||
String tag;
|
||||
while (pos < value.size()) {
|
||||
c = value.GetChar(pos);
|
||||
++pos;
|
||||
if (c == _('>')) break;
|
||||
else tag += c;
|
||||
}
|
||||
tag.MakeUpper();
|
||||
unsigned long number;
|
||||
if (tag==_("W") || tag==_("U") || tag==_("B") || tag==_("R") || tag==_("G") || tag==_("X") || tag==_("Y") || tag==_("Z")) {
|
||||
ret += _("<sym>") + tag + _("</sym>");
|
||||
} else if (tag.ToULong(&number)) {
|
||||
ret += _("<sym>") + tag + _("</sym>");
|
||||
} else if (tag==_("T") || tag==_("TAP")) {
|
||||
ret += _("<sym>T</sym>");
|
||||
} else if (tag==_("THIS")) {
|
||||
ret += _("~");
|
||||
}
|
||||
} else {
|
||||
ret += c;
|
||||
}
|
||||
}
|
||||
// Join adjecent symbol sections
|
||||
value = replace_all(ret, _("</sym><sym>"), _(""));
|
||||
}
|
||||
|
||||
+70
-70
@@ -23,89 +23,89 @@ DECLARE_TYPEOF_COLLECTION(CardP);
|
||||
|
||||
/// Convert a tagged string to MWS format: \t\t before each line beyond the first
|
||||
String untag_mws(const String& str) {
|
||||
// TODO : em dashes?
|
||||
return replace_all(untag(curly_quotes(str,false)), _("\n"), _("\n\t\t") );
|
||||
// TODO : em dashes?
|
||||
return replace_all(untag(curly_quotes(str,false)), _("\n"), _("\n\t\t") );
|
||||
}
|
||||
//String untag_mws(const Defaultable<String>& str) {
|
||||
// str.
|
||||
// str.
|
||||
//}
|
||||
|
||||
/// Code for card color in MWS format
|
||||
String card_color_mws(const String& col) {
|
||||
if (col == _("white")) return _("W");
|
||||
if (col == _("blue")) return _("U");
|
||||
if (col == _("black")) return _("B");
|
||||
if (col == _("red")) return _("R");
|
||||
if (col == _("green")) return _("G");
|
||||
if (col == _("artifact")) return _("Art");
|
||||
if (col == _("colorless")) return _("Art");
|
||||
if (col.find(_("land")) != String::npos) {
|
||||
return _("Lnd"); // land
|
||||
} else {
|
||||
return _("Gld"); // multicolor
|
||||
}
|
||||
if (col == _("white")) return _("W");
|
||||
if (col == _("blue")) return _("U");
|
||||
if (col == _("black")) return _("B");
|
||||
if (col == _("red")) return _("R");
|
||||
if (col == _("green")) return _("G");
|
||||
if (col == _("artifact")) return _("Art");
|
||||
if (col == _("colorless")) return _("Art");
|
||||
if (col.find(_("land")) != String::npos) {
|
||||
return _("Lnd"); // land
|
||||
} else {
|
||||
return _("Gld"); // multicolor
|
||||
}
|
||||
}
|
||||
|
||||
/// Code for card rarity, used for MWS and Apprentice
|
||||
String card_rarity_code(const String& rarity) {
|
||||
if (rarity == _("rare")) return _("R");
|
||||
if (rarity == _("uncommon")) return _("U");
|
||||
else return _("C");
|
||||
if (rarity == _("rare")) return _("R");
|
||||
if (rarity == _("uncommon")) return _("U");
|
||||
else return _("C");
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : export_mws
|
||||
|
||||
void export_mws(Window* parent, const SetP& set) {
|
||||
if (!set->game->isMagic()) {
|
||||
throw Error(_("Can only export Magic sets to Magic Workstation"));
|
||||
}
|
||||
|
||||
// Select filename
|
||||
String name = wxFileSelector(_("Export to file"),settings.default_export_dir,_(""),_(""),
|
||||
_("Text files (*.txt)|*.txt|All Files|*"),
|
||||
wxFD_SAVE | wxFD_OVERWRITE_PROMPT, parent);
|
||||
if (name.empty()) return;
|
||||
settings.default_export_dir = wxPathOnly(name);
|
||||
wxBusyCursor busy;
|
||||
// Open file
|
||||
wxFileOutputStream f(name);
|
||||
wxTextOutputStream file(f, wxEOL_DOS);
|
||||
|
||||
// Write header
|
||||
file.WriteString(set->value<TextValue>(_("title")).value + _(" Spoiler List\n"));
|
||||
file.WriteString(_("Set exported using Magic Set Editor 2, version ") + app_version.toString() + _("\n\n"));
|
||||
wxDateTime now = wxDateTime::Now();
|
||||
file.WriteString(_("Spoiler List created on ") + now.FormatISODate() + _(" ") + now.FormatISOTime());
|
||||
file.WriteString(_("\n\n"));
|
||||
|
||||
// Write cards
|
||||
FOR_EACH(card, set->cards) {
|
||||
file.WriteString(_("Card Name:\t"));
|
||||
file.WriteString(untag_mws(card->value<TextValue>(_("name")).value));
|
||||
file.WriteString(_("\nCard Color:\t"));
|
||||
file.WriteString(card_color_mws(card->value<ChoiceValue>(_("card color")).value));
|
||||
file.WriteString(_("\nMana Cost:\t"));
|
||||
file.WriteString(untag_mws(card->value<TextValue>(_("casting cost")).value));
|
||||
file.WriteString(_("\nType & Class:\t"));
|
||||
String sup_type = untag_mws(card->value<TextValue>(_("super type")).value);
|
||||
String sub_type = untag_mws(card->value<TextValue>(_("sub type")).value);
|
||||
if (sub_type.empty()) {
|
||||
file.WriteString(sup_type);
|
||||
} else {
|
||||
file.WriteString(sup_type + _(" - ") + sub_type);
|
||||
}
|
||||
file.WriteString(_("\nPow/Tou:\t"));
|
||||
file.WriteString(untag_mws(card->value<TextValue>(_("pt")).value));
|
||||
file.WriteString(_("\nCard Text:\t"));
|
||||
file.WriteString(untag_mws(card->value<TextValue>(_("rule text")).value));
|
||||
file.WriteString(_("\nFlavor Text:\t"));
|
||||
file.WriteString(untag_mws(card->value<TextValue>(_("flavor text")).value));
|
||||
file.WriteString(_("\nArtist:\t\t"));
|
||||
file.WriteString(untag_mws(card->value<TextValue>(_("illustrator")).value));
|
||||
file.WriteString(_("\nRarity:\t\t"));
|
||||
file.WriteString(card_rarity_code(card->value<ChoiceValue>(_("rarity")).value));
|
||||
file.WriteString(_("\nCard #:\t\t"));
|
||||
file.WriteString(untag_mws(card->value<TextValue>(_("card number")).value));
|
||||
file.WriteString(_("\n\n"));
|
||||
}
|
||||
if (!set->game->isMagic()) {
|
||||
throw Error(_("Can only export Magic sets to Magic Workstation"));
|
||||
}
|
||||
|
||||
// Select filename
|
||||
String name = wxFileSelector(_("Export to file"),settings.default_export_dir,_(""),_(""),
|
||||
_("Text files (*.txt)|*.txt|All Files|*"),
|
||||
wxFD_SAVE | wxFD_OVERWRITE_PROMPT, parent);
|
||||
if (name.empty()) return;
|
||||
settings.default_export_dir = wxPathOnly(name);
|
||||
wxBusyCursor busy;
|
||||
// Open file
|
||||
wxFileOutputStream f(name);
|
||||
wxTextOutputStream file(f, wxEOL_DOS);
|
||||
|
||||
// Write header
|
||||
file.WriteString(set->value<TextValue>(_("title")).value + _(" Spoiler List\n"));
|
||||
file.WriteString(_("Set exported using Magic Set Editor 2, version ") + app_version.toString() + _("\n\n"));
|
||||
wxDateTime now = wxDateTime::Now();
|
||||
file.WriteString(_("Spoiler List created on ") + now.FormatISODate() + _(" ") + now.FormatISOTime());
|
||||
file.WriteString(_("\n\n"));
|
||||
|
||||
// Write cards
|
||||
FOR_EACH(card, set->cards) {
|
||||
file.WriteString(_("Card Name:\t"));
|
||||
file.WriteString(untag_mws(card->value<TextValue>(_("name")).value));
|
||||
file.WriteString(_("\nCard Color:\t"));
|
||||
file.WriteString(card_color_mws(card->value<ChoiceValue>(_("card color")).value));
|
||||
file.WriteString(_("\nMana Cost:\t"));
|
||||
file.WriteString(untag_mws(card->value<TextValue>(_("casting cost")).value));
|
||||
file.WriteString(_("\nType & Class:\t"));
|
||||
String sup_type = untag_mws(card->value<TextValue>(_("super type")).value);
|
||||
String sub_type = untag_mws(card->value<TextValue>(_("sub type")).value);
|
||||
if (sub_type.empty()) {
|
||||
file.WriteString(sup_type);
|
||||
} else {
|
||||
file.WriteString(sup_type + _(" - ") + sub_type);
|
||||
}
|
||||
file.WriteString(_("\nPow/Tou:\t"));
|
||||
file.WriteString(untag_mws(card->value<TextValue>(_("pt")).value));
|
||||
file.WriteString(_("\nCard Text:\t"));
|
||||
file.WriteString(untag_mws(card->value<TextValue>(_("rule text")).value));
|
||||
file.WriteString(_("\nFlavor Text:\t"));
|
||||
file.WriteString(untag_mws(card->value<TextValue>(_("flavor text")).value));
|
||||
file.WriteString(_("\nArtist:\t\t"));
|
||||
file.WriteString(untag_mws(card->value<TextValue>(_("illustrator")).value));
|
||||
file.WriteString(_("\nRarity:\t\t"));
|
||||
file.WriteString(card_rarity_code(card->value<ChoiceValue>(_("rarity")).value));
|
||||
file.WriteString(_("\nCard #:\t\t"));
|
||||
file.WriteString(untag_mws(card->value<TextValue>(_("card number")).value));
|
||||
file.WriteString(_("\n\n"));
|
||||
}
|
||||
}
|
||||
|
||||
+74
-74
@@ -26,16 +26,16 @@ DECLARE_TYPEOF_COLLECTION(StatsDimensionP);
|
||||
IMPLEMENT_DYNAMIC_ARG(Game*, game_for_reading, nullptr);
|
||||
|
||||
Game::Game()
|
||||
: has_keywords(false)
|
||||
, dependencies_initialized(false)
|
||||
: has_keywords(false)
|
||||
, dependencies_initialized(false)
|
||||
{}
|
||||
|
||||
GameP Game::byName(const String& name) {
|
||||
return package_manager.open<Game>(name + _(".mse-game"));
|
||||
return package_manager.open<Game>(name + _(".mse-game"));
|
||||
}
|
||||
|
||||
bool Game::isMagic() const {
|
||||
return name() == _("magic");
|
||||
return name() == _("magic");
|
||||
}
|
||||
|
||||
String Game::typeNameStatic() { return _("game"); }
|
||||
@@ -43,87 +43,87 @@ String Game::typeName() const { return _("game"); }
|
||||
Version Game::fileVersion() const { return file_version_game; }
|
||||
|
||||
IMPLEMENT_REFLECTION(Game) {
|
||||
REFLECT_BASE(Packaged);
|
||||
REFLECT_NO_SCRIPT(init_script);
|
||||
REFLECT_NO_SCRIPT(set_fields);
|
||||
REFLECT_IF_READING {
|
||||
default_set_style.init(set_fields);
|
||||
}
|
||||
REFLECT_NO_SCRIPT(default_set_style);
|
||||
REFLECT_NO_SCRIPT(card_fields);
|
||||
REFLECT_NO_SCRIPT(card_list_color_script);
|
||||
REFLECT_NO_SCRIPT(statistics_dimensions);
|
||||
REFLECT_NO_SCRIPT(statistics_categories);
|
||||
REFLECT_ALIAS(308, "pack item", "pack type");
|
||||
REFLECT_NO_SCRIPT(pack_types);
|
||||
REFLECT_NO_SCRIPT(keyword_match_script);
|
||||
REFLECT(has_keywords);
|
||||
REFLECT(keyword_modes);
|
||||
REFLECT(keyword_parameter_types);
|
||||
REFLECT_NO_SCRIPT(keywords);
|
||||
REFLECT_NO_SCRIPT(word_lists);
|
||||
REFLECT_NO_SCRIPT(add_cards_scripts);
|
||||
REFLECT_NO_SCRIPT(auto_replaces);
|
||||
REFLECT_BASE(Packaged);
|
||||
REFLECT_NO_SCRIPT(init_script);
|
||||
REFLECT_NO_SCRIPT(set_fields);
|
||||
REFLECT_IF_READING {
|
||||
default_set_style.init(set_fields);
|
||||
}
|
||||
REFLECT_NO_SCRIPT(default_set_style);
|
||||
REFLECT_NO_SCRIPT(card_fields);
|
||||
REFLECT_NO_SCRIPT(card_list_color_script);
|
||||
REFLECT_NO_SCRIPT(statistics_dimensions);
|
||||
REFLECT_NO_SCRIPT(statistics_categories);
|
||||
REFLECT_ALIAS(308, "pack item", "pack type");
|
||||
REFLECT_NO_SCRIPT(pack_types);
|
||||
REFLECT_NO_SCRIPT(keyword_match_script);
|
||||
REFLECT(has_keywords);
|
||||
REFLECT(keyword_modes);
|
||||
REFLECT(keyword_parameter_types);
|
||||
REFLECT_NO_SCRIPT(keywords);
|
||||
REFLECT_NO_SCRIPT(word_lists);
|
||||
REFLECT_NO_SCRIPT(add_cards_scripts);
|
||||
REFLECT_NO_SCRIPT(auto_replaces);
|
||||
}
|
||||
|
||||
void Game::validate(Version v) {
|
||||
Packaged::validate(v);
|
||||
// automatic statistics dimensions
|
||||
{
|
||||
vector<StatsDimensionP> dims;
|
||||
FOR_EACH(f, card_fields) {
|
||||
if (f->show_statistics) {
|
||||
dims.push_back(intrusive(new StatsDimension(*f)));
|
||||
}
|
||||
}
|
||||
statistics_dimensions.insert(statistics_dimensions.begin(), dims.begin(), dims.end()); // push front
|
||||
}
|
||||
// automatic statistics categories
|
||||
{
|
||||
vector<StatsCategoryP> cats;
|
||||
FOR_EACH(dim, statistics_dimensions) {
|
||||
cats.push_back(intrusive(new StatsCategory(dim)));
|
||||
}
|
||||
statistics_categories.insert(statistics_categories.begin(), cats.begin(), cats.end()); // push front
|
||||
}
|
||||
// automatic pack if there are none
|
||||
if (pack_types.empty()) {
|
||||
PackTypeP pack(new PackType);
|
||||
pack->name = _("Any card");
|
||||
pack->enabled = true;
|
||||
pack->selectable = true;
|
||||
pack->summary = true;
|
||||
pack->filter = OptionalScript(_("true"));
|
||||
pack->select = SELECT_NO_REPLACE;
|
||||
pack_types.push_back(pack);
|
||||
}
|
||||
Packaged::validate(v);
|
||||
// automatic statistics dimensions
|
||||
{
|
||||
vector<StatsDimensionP> dims;
|
||||
FOR_EACH(f, card_fields) {
|
||||
if (f->show_statistics) {
|
||||
dims.push_back(intrusive(new StatsDimension(*f)));
|
||||
}
|
||||
}
|
||||
statistics_dimensions.insert(statistics_dimensions.begin(), dims.begin(), dims.end()); // push front
|
||||
}
|
||||
// automatic statistics categories
|
||||
{
|
||||
vector<StatsCategoryP> cats;
|
||||
FOR_EACH(dim, statistics_dimensions) {
|
||||
cats.push_back(intrusive(new StatsCategory(dim)));
|
||||
}
|
||||
statistics_categories.insert(statistics_categories.begin(), cats.begin(), cats.end()); // push front
|
||||
}
|
||||
// automatic pack if there are none
|
||||
if (pack_types.empty()) {
|
||||
PackTypeP pack(new PackType);
|
||||
pack->name = _("Any card");
|
||||
pack->enabled = true;
|
||||
pack->selectable = true;
|
||||
pack->summary = true;
|
||||
pack->filter = OptionalScript(_("true"));
|
||||
pack->select = SELECT_NO_REPLACE;
|
||||
pack_types.push_back(pack);
|
||||
}
|
||||
}
|
||||
|
||||
void Game::initCardListColorScript() {
|
||||
if (card_list_color_script) return; // already done
|
||||
// find a field with choice_colors_cardlist
|
||||
FOR_EACH(s, card_fields) {
|
||||
ChoiceFieldP cf = dynamic_pointer_cast<ChoiceField>(s);
|
||||
if (cf && !cf->choice_colors_cardlist.empty()) {
|
||||
// found the field to use
|
||||
// initialize script: field.colors[card.field-name] or else rgb(0,0,0)
|
||||
Script& s = card_list_color_script.getMutableScript();
|
||||
s.addInstruction(I_PUSH_CONST, to_script(&cf->choice_colors_cardlist));
|
||||
s.addInstruction(I_GET_VAR, SCRIPT_VAR_card);
|
||||
s.addInstruction(I_MEMBER_C, cf->name);
|
||||
s.addInstruction(I_BINARY, I_MEMBER);
|
||||
s.addInstruction(I_PUSH_CONST, to_script(Color(0,0,0)));
|
||||
s.addInstruction(I_BINARY, I_OR_ELSE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (card_list_color_script) return; // already done
|
||||
// find a field with choice_colors_cardlist
|
||||
FOR_EACH(s, card_fields) {
|
||||
ChoiceFieldP cf = dynamic_pointer_cast<ChoiceField>(s);
|
||||
if (cf && !cf->choice_colors_cardlist.empty()) {
|
||||
// found the field to use
|
||||
// initialize script: field.colors[card.field-name] or else rgb(0,0,0)
|
||||
Script& s = card_list_color_script.getMutableScript();
|
||||
s.addInstruction(I_PUSH_CONST, to_script(&cf->choice_colors_cardlist));
|
||||
s.addInstruction(I_GET_VAR, SCRIPT_VAR_card);
|
||||
s.addInstruction(I_MEMBER_C, cf->name);
|
||||
s.addInstruction(I_BINARY, I_MEMBER);
|
||||
s.addInstruction(I_PUSH_CONST, to_script(Color(0,0,0)));
|
||||
s.addInstruction(I_BINARY, I_OR_ELSE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// special behaviour of reading/writing GamePs: only read/write the name
|
||||
|
||||
void Reader::handle(GameP& game) {
|
||||
game = Game::byName(getValue());
|
||||
game = Game::byName(getValue());
|
||||
}
|
||||
void Writer::handle(const GameP& game) {
|
||||
if (game) handle(game->name());
|
||||
if (game) handle(game->name());
|
||||
}
|
||||
|
||||
+42
-42
@@ -36,52 +36,52 @@ DECLARE_DYNAMIC_ARG(Game*, game_for_reading);
|
||||
/// A description of a card game
|
||||
class Game : public Packaged {
|
||||
public:
|
||||
Game();
|
||||
|
||||
OptionalScript init_script; ///< Script of variables available to other scripts in this game
|
||||
vector<FieldP> set_fields; ///< Fields for set information
|
||||
IndexMap<FieldP,StyleP> default_set_style; ///< Default style for the set fields, because it is often the same
|
||||
vector<FieldP> card_fields; ///< Fields on each card
|
||||
OptionalScript card_list_color_script; ///< Script that determines the color of items in the card list
|
||||
vector<StatsDimensionP> statistics_dimensions; ///< (Additional) statistics dimensions
|
||||
vector<StatsCategoryP> statistics_categories; ///< (Additional) statistics categories
|
||||
vector<PackTypeP> pack_types; ///< Types of random card packs to generate
|
||||
vector<WordListP> word_lists; ///< Word lists for editing with a drop down list
|
||||
vector<AddCardsScriptP> add_cards_scripts; ///< Scripts for adding multiple cards to the set
|
||||
vector<AutoReplaceP> auto_replaces; ///< Things to autoreplace in textboxes
|
||||
|
||||
bool has_keywords; ///< Does this game use keywords?
|
||||
OptionalScript keyword_match_script; ///< For the keyword editor
|
||||
vector<KeywordParamP> keyword_parameter_types;///< Types of keyword parameters
|
||||
vector<KeywordModeP> keyword_modes; ///< Modes of keywords
|
||||
vector<KeywordP> keywords; ///< Keywords for use in text
|
||||
|
||||
Dependencies dependent_scripts_cards; ///< scripts that depend on the card list
|
||||
Dependencies dependent_scripts_keywords; ///< scripts that depend on the keywords
|
||||
Dependencies dependent_scripts_stylesheet; ///< scripts that depend on the card's stylesheet
|
||||
bool dependencies_initialized; ///< are the script dependencies comming from this game all initialized?
|
||||
|
||||
/// Loads the game with a particular name, for example "magic"
|
||||
static GameP byName(const String& name);
|
||||
|
||||
/// Is this Magic the Gathering?
|
||||
bool isMagic() const;
|
||||
|
||||
/// Initialize card_list_color_script
|
||||
void initCardListColorScript();
|
||||
|
||||
static String typeNameStatic();
|
||||
virtual String typeName() const;
|
||||
Version fileVersion() const;
|
||||
|
||||
Game();
|
||||
|
||||
OptionalScript init_script; ///< Script of variables available to other scripts in this game
|
||||
vector<FieldP> set_fields; ///< Fields for set information
|
||||
IndexMap<FieldP,StyleP> default_set_style; ///< Default style for the set fields, because it is often the same
|
||||
vector<FieldP> card_fields; ///< Fields on each card
|
||||
OptionalScript card_list_color_script; ///< Script that determines the color of items in the card list
|
||||
vector<StatsDimensionP> statistics_dimensions; ///< (Additional) statistics dimensions
|
||||
vector<StatsCategoryP> statistics_categories; ///< (Additional) statistics categories
|
||||
vector<PackTypeP> pack_types; ///< Types of random card packs to generate
|
||||
vector<WordListP> word_lists; ///< Word lists for editing with a drop down list
|
||||
vector<AddCardsScriptP> add_cards_scripts; ///< Scripts for adding multiple cards to the set
|
||||
vector<AutoReplaceP> auto_replaces; ///< Things to autoreplace in textboxes
|
||||
|
||||
bool has_keywords; ///< Does this game use keywords?
|
||||
OptionalScript keyword_match_script; ///< For the keyword editor
|
||||
vector<KeywordParamP> keyword_parameter_types;///< Types of keyword parameters
|
||||
vector<KeywordModeP> keyword_modes; ///< Modes of keywords
|
||||
vector<KeywordP> keywords; ///< Keywords for use in text
|
||||
|
||||
Dependencies dependent_scripts_cards; ///< scripts that depend on the card list
|
||||
Dependencies dependent_scripts_keywords; ///< scripts that depend on the keywords
|
||||
Dependencies dependent_scripts_stylesheet; ///< scripts that depend on the card's stylesheet
|
||||
bool dependencies_initialized; ///< are the script dependencies comming from this game all initialized?
|
||||
|
||||
/// Loads the game with a particular name, for example "magic"
|
||||
static GameP byName(const String& name);
|
||||
|
||||
/// Is this Magic the Gathering?
|
||||
bool isMagic() const;
|
||||
|
||||
/// Initialize card_list_color_script
|
||||
void initCardListColorScript();
|
||||
|
||||
static String typeNameStatic();
|
||||
virtual String typeName() const;
|
||||
Version fileVersion() const;
|
||||
|
||||
protected:
|
||||
virtual void validate(Version);
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
virtual void validate(Version);
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
inline String type_name(const Game&) {
|
||||
return _TYPE_("game");
|
||||
return _TYPE_("game");
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
+11
-11
@@ -15,21 +15,21 @@
|
||||
|
||||
/// Types of graphs
|
||||
enum GraphType
|
||||
{ GRAPH_TYPE_PIE
|
||||
, GRAPH_TYPE_BAR
|
||||
, GRAPH_TYPE_STACK
|
||||
, GRAPH_TYPE_SCATTER
|
||||
, GRAPH_TYPE_SCATTER_PIE
|
||||
{ GRAPH_TYPE_PIE
|
||||
, GRAPH_TYPE_BAR
|
||||
, GRAPH_TYPE_STACK
|
||||
, GRAPH_TYPE_SCATTER
|
||||
, GRAPH_TYPE_SCATTER_PIE
|
||||
};
|
||||
|
||||
/// Dimensions for each graph type
|
||||
inline size_t dimensionality(GraphType type) {
|
||||
if (type == GRAPH_TYPE_PIE) return 1;
|
||||
if (type == GRAPH_TYPE_BAR) return 1;
|
||||
if (type == GRAPH_TYPE_STACK) return 2;
|
||||
if (type == GRAPH_TYPE_SCATTER) return 2;
|
||||
if (type == GRAPH_TYPE_SCATTER_PIE) return 3;
|
||||
else return 0;
|
||||
if (type == GRAPH_TYPE_PIE) return 1;
|
||||
if (type == GRAPH_TYPE_BAR) return 1;
|
||||
if (type == GRAPH_TYPE_STACK) return 2;
|
||||
if (type == GRAPH_TYPE_SCATTER) return 2;
|
||||
if (type == GRAPH_TYPE_SCATTER_PIE) return 3;
|
||||
else return 0;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
+448
-448
File diff suppressed because it is too large
Load Diff
+94
-94
@@ -32,22 +32,22 @@ DECLARE_POINTER_TYPE(InstallablePackage);
|
||||
*/
|
||||
class Installer : public Packaged {
|
||||
public:
|
||||
String prefered_filename; ///< What filename should be used (by default), when creating the installer
|
||||
vector<PackageDescriptionP> packages; ///< Packages to install
|
||||
|
||||
/// Add a package to the installer (if it is not already added).
|
||||
/** If the package is named *.mse-installer uses it as the filename instead */
|
||||
void addPackage(const String& package);
|
||||
/// Add a package to the installer (if it is not already added).
|
||||
/** The first package gives the name of the installer.
|
||||
*/
|
||||
void addPackage(Packaged& package);
|
||||
|
||||
String prefered_filename; ///< What filename should be used (by default), when creating the installer
|
||||
vector<PackageDescriptionP> packages; ///< Packages to install
|
||||
|
||||
/// Add a package to the installer (if it is not already added).
|
||||
/** If the package is named *.mse-installer uses it as the filename instead */
|
||||
void addPackage(const String& package);
|
||||
/// Add a package to the installer (if it is not already added).
|
||||
/** The first package gives the name of the installer.
|
||||
*/
|
||||
void addPackage(Packaged& package);
|
||||
|
||||
protected:
|
||||
virtual String typeName() const;
|
||||
virtual Version fileVersion() const;
|
||||
virtual void validate(Version file_app_version);
|
||||
DECLARE_REFLECTION();
|
||||
virtual String typeName() const;
|
||||
virtual Version fileVersion() const;
|
||||
virtual void validate(Version file_app_version);
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Installer descriptions
|
||||
@@ -58,75 +58,75 @@ class Installer : public Packaged {
|
||||
*/
|
||||
class PackageDescription : public IntrusivePtrBase<PackageDescription> {
|
||||
public:
|
||||
PackageDescription();
|
||||
PackageDescription(const Packaged& package);
|
||||
|
||||
String name; ///< Filename of the package
|
||||
Version version; ///< Version number of this package
|
||||
String short_name; ///< Short name of this package
|
||||
String full_name; ///< Name of this package, for menus etc.
|
||||
String icon_url; ///< Filename or URL of icon to use in package lists
|
||||
Image icon; ///< Icon for the package
|
||||
String installer_group; ///< Where to put this package in the installer
|
||||
int position_hint; ///< A hint for the package list
|
||||
String description; ///< Changelog/description
|
||||
vector<PackageDependencyP> dependencies; ///< Dependencies of this package
|
||||
|
||||
/// Merge two descriptions a package. This package takes precedence
|
||||
/** Usually one of the descriptions will refer to the locally installed one, the other to the new one */
|
||||
void merge(const PackageDescription& p2);
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
PackageDescription();
|
||||
PackageDescription(const Packaged& package);
|
||||
|
||||
String name; ///< Filename of the package
|
||||
Version version; ///< Version number of this package
|
||||
String short_name; ///< Short name of this package
|
||||
String full_name; ///< Name of this package, for menus etc.
|
||||
String icon_url; ///< Filename or URL of icon to use in package lists
|
||||
Image icon; ///< Icon for the package
|
||||
String installer_group; ///< Where to put this package in the installer
|
||||
int position_hint; ///< A hint for the package list
|
||||
String description; ///< Changelog/description
|
||||
vector<PackageDependencyP> dependencies; ///< Dependencies of this package
|
||||
|
||||
/// Merge two descriptions a package. This package takes precedence
|
||||
/** Usually one of the descriptions will refer to the locally installed one, the other to the new one */
|
||||
void merge(const PackageDescription& p2);
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
/// A description of the contents of an installer
|
||||
class InstallerDescription : public IntrusivePtrBase<InstallerDescription> {
|
||||
public:
|
||||
vector<PackageDescriptionP> packages;
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
vector<PackageDescriptionP> packages;
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
/// Information on an installer that can be downloaded
|
||||
class DownloadableInstaller : public IntrusivePtrBase<DownloadableInstaller> {
|
||||
public:
|
||||
DownloadableInstaller() : downloadable(true) {}
|
||||
DownloadableInstaller(const InstallerP& installer);
|
||||
~DownloadableInstaller();
|
||||
|
||||
InstallerP installer; ///< The installer, if it is loaded
|
||||
String installer_url; ///< The URL where the installer can be found
|
||||
String installer_file; ///< The temp file where the installer can be found (after downloading)
|
||||
bool downloadable; ///< Is the installer downloadable (in)directly from that url?
|
||||
vector<PackageDescriptionP> packages; ///< Packages provided by this installer
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
DownloadableInstaller() : downloadable(true) {}
|
||||
DownloadableInstaller(const InstallerP& installer);
|
||||
~DownloadableInstaller();
|
||||
|
||||
InstallerP installer; ///< The installer, if it is loaded
|
||||
String installer_url; ///< The URL where the installer can be found
|
||||
String installer_file; ///< The temp file where the installer can be found (after downloading)
|
||||
bool downloadable; ///< Is the installer downloadable (in)directly from that url?
|
||||
vector<PackageDescriptionP> packages; ///< Packages provided by this installer
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Installable package
|
||||
|
||||
/// Installation status of a package
|
||||
enum PackageStatus
|
||||
{ PACKAGE_NOT_INSTALLED = 0x0000
|
||||
, PACKAGE_INSTALLED = 0x0001
|
||||
, PACKAGE_REMOVABLE = 0x0002
|
||||
, PACKAGE_INSTALLER = 0x0010 ///< Package can be installed (there is an installer)
|
||||
, PACKAGE_INSTALLABLE = 0x0110 ///< Package can be installed (and it makes sense to do so)
|
||||
, PACKAGE_NOT_UP_TO_DATE= 0x0200 ///< The local installed version (if any) is not up to date
|
||||
, PACKAGE_UPDATES = 0x0311 ///< Remote updates available
|
||||
, PACKAGE_MODIFIED = 0x1001 ///< Local changes made
|
||||
, PACKAGE_MISSING_DEP = 0x2000 ///< Missing a dependency for installation
|
||||
, PACKAGE_CONFLICTS = PACKAGE_UPDATES | PACKAGE_MODIFIED
|
||||
{ PACKAGE_NOT_INSTALLED = 0x0000
|
||||
, PACKAGE_INSTALLED = 0x0001
|
||||
, PACKAGE_REMOVABLE = 0x0002
|
||||
, PACKAGE_INSTALLER = 0x0010 ///< Package can be installed (there is an installer)
|
||||
, PACKAGE_INSTALLABLE = 0x0110 ///< Package can be installed (and it makes sense to do so)
|
||||
, PACKAGE_NOT_UP_TO_DATE= 0x0200 ///< The local installed version (if any) is not up to date
|
||||
, PACKAGE_UPDATES = 0x0311 ///< Remote updates available
|
||||
, PACKAGE_MODIFIED = 0x1001 ///< Local changes made
|
||||
, PACKAGE_MISSING_DEP = 0x2000 ///< Missing a dependency for installation
|
||||
, PACKAGE_CONFLICTS = PACKAGE_UPDATES | PACKAGE_MODIFIED
|
||||
};
|
||||
|
||||
/// (un)install a package?
|
||||
enum PackageAction
|
||||
{ PACKAGE_ACT_NOTHING = 0x001 ///< Don't change anything
|
||||
, PACKAGE_ACT_INSTALL = 0x002 ///< Install or upgrade the package
|
||||
, PACKAGE_ACT_REMOVE = 0x004 ///< Remove the package (if it was installed)
|
||||
, PACKAGE_ACT_LOCAL = 0x010 ///< In the local package directory
|
||||
, PACKAGE_ACT_GLOBAL = 0x020 ///< In the global package directory
|
||||
, PACKAGE_ACT_WHERE = PACKAGE_ACT_LOCAL | PACKAGE_ACT_GLOBAL
|
||||
{ PACKAGE_ACT_NOTHING = 0x001 ///< Don't change anything
|
||||
, PACKAGE_ACT_INSTALL = 0x002 ///< Install or upgrade the package
|
||||
, PACKAGE_ACT_REMOVE = 0x004 ///< Remove the package (if it was installed)
|
||||
, PACKAGE_ACT_LOCAL = 0x010 ///< In the local package directory
|
||||
, PACKAGE_ACT_GLOBAL = 0x020 ///< In the global package directory
|
||||
, PACKAGE_ACT_WHERE = PACKAGE_ACT_LOCAL | PACKAGE_ACT_GLOBAL
|
||||
};
|
||||
// bit twidling
|
||||
inline PackageAction operator | (PackageAction a, PackageAction b) { return (PackageAction)((int)a | (int) b); }
|
||||
@@ -135,36 +135,36 @@ inline bool flag(int flags, int flag) { return (flags & flag) == flag; }
|
||||
/// A package that can be installed, or is already installed
|
||||
class InstallablePackage : public IntrusivePtrVirtualBase {
|
||||
public:
|
||||
/// A new package
|
||||
InstallablePackage(const PackageDescriptionP&, const DownloadableInstallerP&);
|
||||
/// An installed package
|
||||
InstallablePackage(const PackageDescriptionP&, const PackageVersionP&);
|
||||
|
||||
PackageDescriptionP description; ///< The details of the package. Either from the installed package or from an installer
|
||||
PackageVersionP installed; ///< The information of the installed package (if installed)
|
||||
DownloadableInstallerP installer; ///< The installer to install from (if updates are available)
|
||||
PackageStatus status; ///< Status of installation
|
||||
PackageAction action; ///< What to do with this package?
|
||||
|
||||
int automatic; ///< Install/upgrade/remove automaticly to satisfy this many packages
|
||||
|
||||
PackageAction old_action;
|
||||
int old_automatic;
|
||||
|
||||
void determineStatus();
|
||||
|
||||
/// After the action, will the package be installed?
|
||||
bool willBeInstalled() const;
|
||||
|
||||
/// Is the action possible?
|
||||
bool can(PackageAction act) const;
|
||||
/// Is the action currently selected?
|
||||
bool has(PackageAction act) const;
|
||||
/// Does the package have the given status bits all set?
|
||||
bool has(PackageStatus stat) const;
|
||||
|
||||
/// Merge two descriptions of installable packages
|
||||
void merge(const InstallablePackage& p2);
|
||||
/// A new package
|
||||
InstallablePackage(const PackageDescriptionP&, const DownloadableInstallerP&);
|
||||
/// An installed package
|
||||
InstallablePackage(const PackageDescriptionP&, const PackageVersionP&);
|
||||
|
||||
PackageDescriptionP description; ///< The details of the package. Either from the installed package or from an installer
|
||||
PackageVersionP installed; ///< The information of the installed package (if installed)
|
||||
DownloadableInstallerP installer; ///< The installer to install from (if updates are available)
|
||||
PackageStatus status; ///< Status of installation
|
||||
PackageAction action; ///< What to do with this package?
|
||||
|
||||
int automatic; ///< Install/upgrade/remove automaticly to satisfy this many packages
|
||||
|
||||
PackageAction old_action;
|
||||
int old_automatic;
|
||||
|
||||
void determineStatus();
|
||||
|
||||
/// After the action, will the package be installed?
|
||||
bool willBeInstalled() const;
|
||||
|
||||
/// Is the action possible?
|
||||
bool can(PackageAction act) const;
|
||||
/// Is the action currently selected?
|
||||
bool has(PackageAction act) const;
|
||||
/// Does the package have the given status bits all set?
|
||||
bool has(PackageStatus stat) const;
|
||||
|
||||
/// Merge two descriptions of installable packages
|
||||
void merge(const InstallablePackage& p2);
|
||||
};
|
||||
|
||||
|
||||
|
||||
+588
-588
File diff suppressed because it is too large
Load Diff
+129
-129
@@ -25,48 +25,48 @@ class Value;
|
||||
|
||||
class ParamReferenceType : public IntrusivePtrBase<ParamReferenceType> {
|
||||
public:
|
||||
String name; ///< Name of the parameter reference type
|
||||
String description; ///< Description (for status bar)
|
||||
StringScript script; ///< Code to insert into the reminder text script, input is the actual parameter name
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
String name; ///< Name of the parameter reference type
|
||||
String description; ///< Description (for status bar)
|
||||
StringScript script; ///< Code to insert into the reminder text script, input is the actual parameter name
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
/// Parameter type of keywords
|
||||
class KeywordParam : public IntrusivePtrBase<KeywordParam> {
|
||||
public:
|
||||
KeywordParam();
|
||||
String name; ///< Name of the parameter type
|
||||
String description; ///< Description of the parameter type
|
||||
String placeholder; ///< Placholder for <atom-kwpph>, name is used if this is empty
|
||||
bool optional; ///< Can this parameter be left out (a placeholder is then used)
|
||||
String match; ///< Regular expression to match (including separators)
|
||||
String separator_before_is; ///< Regular expression of separator before the param
|
||||
Regex separator_before_re; ///< Regular expression of separator before the param, compiled
|
||||
Regex separator_before_eat;///< Regular expression of separator before the param, if eat_separator
|
||||
String separator_after_is; ///< Regular expression of separator after the param
|
||||
Regex separator_after_re; ///< Regular expression of separator after the param, compiled
|
||||
Regex 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
|
||||
OptionalScript separator_script; ///< Transformation of the separator
|
||||
String example; ///< Example for the keyword editor
|
||||
vector<ParamReferenceTypeP> refer_scripts;///< Way to refer to a parameter from the reminder text script
|
||||
|
||||
//% /// Make a string that can function as a separator before the parameter
|
||||
//% /** This tries to decode the separator_before_is regex */
|
||||
//% String make_separator_before() const;
|
||||
|
||||
/// Compile regexes for separators
|
||||
void compile();
|
||||
|
||||
/// Remove separator_before from the end of the text
|
||||
void eat_separator_before(String& text);
|
||||
/// Advance i past separator_before if it is at position i in the text
|
||||
void eat_separator_after(const String& text, size_t& i);
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
KeywordParam();
|
||||
String name; ///< Name of the parameter type
|
||||
String description; ///< Description of the parameter type
|
||||
String placeholder; ///< Placholder for <atom-kwpph>, name is used if this is empty
|
||||
bool optional; ///< Can this parameter be left out (a placeholder is then used)
|
||||
String match; ///< Regular expression to match (including separators)
|
||||
String separator_before_is; ///< Regular expression of separator before the param
|
||||
Regex separator_before_re; ///< Regular expression of separator before the param, compiled
|
||||
Regex separator_before_eat;///< Regular expression of separator before the param, if eat_separator
|
||||
String separator_after_is; ///< Regular expression of separator after the param
|
||||
Regex separator_after_re; ///< Regular expression of separator after the param, compiled
|
||||
Regex 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
|
||||
OptionalScript separator_script; ///< Transformation of the separator
|
||||
String example; ///< Example for the keyword editor
|
||||
vector<ParamReferenceTypeP> refer_scripts;///< Way to refer to a parameter from the reminder text script
|
||||
|
||||
//% /// Make a string that can function as a separator before the parameter
|
||||
//% /** This tries to decode the separator_before_is regex */
|
||||
//% String make_separator_before() const;
|
||||
|
||||
/// Compile regexes for separators
|
||||
void compile();
|
||||
|
||||
/// Remove separator_before from the end of the text
|
||||
void eat_separator_before(String& text);
|
||||
/// Advance i past separator_before if it is at position i in the text
|
||||
void eat_separator_after(const String& text, size_t& i);
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Keyword mode
|
||||
@@ -74,13 +74,13 @@ class KeywordParam : public IntrusivePtrBase<KeywordParam> {
|
||||
/// Information on when and how to use a keyword
|
||||
class KeywordMode : public IntrusivePtrBase<KeywordMode> {
|
||||
public:
|
||||
KeywordMode() : is_default(false) {}
|
||||
|
||||
String name; ///< Name of the mode
|
||||
String description; ///< Description of the type
|
||||
bool is_default; ///< This is the default mode for new keywords
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
KeywordMode() : is_default(false) {}
|
||||
|
||||
String name; ///< Name of the mode
|
||||
String description; ///< Description of the type
|
||||
bool is_default; ///< This is the default mode for new keywords
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Keyword expansion
|
||||
@@ -88,45 +88,45 @@ class KeywordMode : public IntrusivePtrBase<KeywordMode> {
|
||||
/// A keyword for a set or a game
|
||||
class Keyword : public IntrusivePtrVirtualBase {
|
||||
public:
|
||||
Keyword() : fixed(false), valid(false) {}
|
||||
|
||||
String keyword; ///< The keyword, only for human use
|
||||
String rules; ///< Rules/explanation
|
||||
String match; ///< String to match, <atom-param> tags are used for parameters
|
||||
vector<KeywordParamP> parameters; ///< The types of parameters
|
||||
StringScript reminder; ///< Reminder text of the keyword
|
||||
String mode; ///< Mode of use, can be used by scripts (only gives the name)
|
||||
/// Regular expression to match and split parameters, automatically generated.
|
||||
/** The regex has exactly 2 * parameters.size() + 1 captures (excluding the entire match, caputure 0),
|
||||
* captures 1,3,... capture the plain text of the match string
|
||||
* captures 2,4,... capture the separators and parameters
|
||||
*/
|
||||
Regex match_re;
|
||||
bool fixed; ///< Is this keyword uneditable? (true for game keywods, false for set keywords)
|
||||
bool valid; ///< Is this keyword okay (reminder text compiles & runs; match does not match "")
|
||||
|
||||
/// Find the index of the mode in a list of possibilities.
|
||||
/** Returns the default if not found and 0 if there is no default */
|
||||
size_t findMode(const vector<KeywordModeP>& modes) const;
|
||||
|
||||
/// Prepare the expansion: (re)generate matchRe and the list of parameters.
|
||||
/** Throws when there is an error in the input
|
||||
* @param param_types A list of all parameter types.
|
||||
* @param force Re-prepare even if the regex¶meters are okay
|
||||
*/
|
||||
void prepare(const vector<KeywordParamP>& param_types, bool force = false);
|
||||
|
||||
/// Does the keyword contain the given query word?
|
||||
bool contains(String const& word) const;
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
Keyword() : fixed(false), valid(false) {}
|
||||
|
||||
String keyword; ///< The keyword, only for human use
|
||||
String rules; ///< Rules/explanation
|
||||
String match; ///< String to match, <atom-param> tags are used for parameters
|
||||
vector<KeywordParamP> parameters; ///< The types of parameters
|
||||
StringScript reminder; ///< Reminder text of the keyword
|
||||
String mode; ///< Mode of use, can be used by scripts (only gives the name)
|
||||
/// Regular expression to match and split parameters, automatically generated.
|
||||
/** The regex has exactly 2 * parameters.size() + 1 captures (excluding the entire match, caputure 0),
|
||||
* captures 1,3,... capture the plain text of the match string
|
||||
* captures 2,4,... capture the separators and parameters
|
||||
*/
|
||||
Regex match_re;
|
||||
bool fixed; ///< Is this keyword uneditable? (true for game keywods, false for set keywords)
|
||||
bool valid; ///< Is this keyword okay (reminder text compiles & runs; match does not match "")
|
||||
|
||||
/// Find the index of the mode in a list of possibilities.
|
||||
/** Returns the default if not found and 0 if there is no default */
|
||||
size_t findMode(const vector<KeywordModeP>& modes) const;
|
||||
|
||||
/// Prepare the expansion: (re)generate matchRe and the list of parameters.
|
||||
/** Throws when there is an error in the input
|
||||
* @param param_types A list of all parameter types.
|
||||
* @param force Re-prepare even if the regex¶meters are okay
|
||||
*/
|
||||
void prepare(const vector<KeywordParamP>& param_types, bool force = false);
|
||||
|
||||
/// Does the keyword contain the given query word?
|
||||
bool contains(String const& word) const;
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
inline String type_name(const Keyword&) {
|
||||
return _TYPE_("keyword");
|
||||
return _TYPE_("keyword");
|
||||
}
|
||||
inline String type_name(const vector<KeywordP>&) {
|
||||
return _TYPE_("keywords"); // not actually used, only for locale.pl script
|
||||
return _TYPE_("keywords"); // not actually used, only for locale.pl script
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Using keywords
|
||||
@@ -141,42 +141,42 @@ DECLARE_DYNAMIC_ARG(KeywordUsageStatistics*, keyword_usage_statistics);
|
||||
*/
|
||||
class KeywordDatabase {
|
||||
public:
|
||||
KeywordDatabase();
|
||||
~KeywordDatabase();
|
||||
|
||||
/// Add a list of keywords to be matched
|
||||
void add(const vector<KeywordP>&);
|
||||
/// Add a keyword to be matched
|
||||
void add(const Keyword&);
|
||||
|
||||
/// Prepare the parameters and match regex for a list of keywords
|
||||
static void prepare_parameters(const vector<KeywordParamP>&, const vector<KeywordP>&);
|
||||
|
||||
/// Clear the database
|
||||
void clear();
|
||||
/// Is the database empty?
|
||||
inline bool empty() const { return !root; }
|
||||
|
||||
/// Expand/update all keywords in the given string.
|
||||
/** @param expand_default script function indicating whether reminder text should be shown by default
|
||||
* @param combine_script script function to combine keyword and reminder text in some way
|
||||
* @param case_sensitive case sensitive matching of keywords?
|
||||
* @param ctx context for evaluation of scripts
|
||||
*/
|
||||
String expand(const String& text, const ScriptValueP& match_condition, const ScriptValueP& expand_default, const ScriptValueP& combine_script, Context& ctx) const;
|
||||
|
||||
KeywordDatabase();
|
||||
~KeywordDatabase();
|
||||
|
||||
/// Add a list of keywords to be matched
|
||||
void add(const vector<KeywordP>&);
|
||||
/// Add a keyword to be matched
|
||||
void add(const Keyword&);
|
||||
|
||||
/// Prepare the parameters and match regex for a list of keywords
|
||||
static void prepare_parameters(const vector<KeywordParamP>&, const vector<KeywordP>&);
|
||||
|
||||
/// Clear the database
|
||||
void clear();
|
||||
/// Is the database empty?
|
||||
inline bool empty() const { return !root; }
|
||||
|
||||
/// Expand/update all keywords in the given string.
|
||||
/** @param expand_default script function indicating whether reminder text should be shown by default
|
||||
* @param combine_script script function to combine keyword and reminder text in some way
|
||||
* @param case_sensitive case sensitive matching of keywords?
|
||||
* @param ctx context for evaluation of scripts
|
||||
*/
|
||||
String expand(const String& text, const ScriptValueP& match_condition, const ScriptValueP& expand_default, const ScriptValueP& combine_script, Context& ctx) const;
|
||||
|
||||
private:
|
||||
KeywordTrie* root; ///< Data structure for finding keywords
|
||||
|
||||
/// (try to) expand a single keyword
|
||||
/** If the keyword matches:
|
||||
* - add the result to out
|
||||
* - advance the tagged and untagged string by dropping a part from the front
|
||||
* - return true
|
||||
*/
|
||||
bool tryExpand(const Keyword& kw, size_t pos, String& tagged, String& untagged, String& out, char expand_type,
|
||||
const ScriptValueP& match_condition, const ScriptValueP& expand_default, const ScriptValueP& combine_script, Context& ctx,
|
||||
KeywordUsageStatistics* stat, Value* stat_key) const;
|
||||
KeywordTrie* root; ///< Data structure for finding keywords
|
||||
|
||||
/// (try to) expand a single keyword
|
||||
/** If the keyword matches:
|
||||
* - add the result to out
|
||||
* - advance the tagged and untagged string by dropping a part from the front
|
||||
* - return true
|
||||
*/
|
||||
bool tryExpand(const Keyword& kw, size_t pos, String& tagged, String& untagged, String& out, char expand_type,
|
||||
const ScriptValueP& match_condition, const ScriptValueP& expand_default, const ScriptValueP& combine_script, Context& ctx,
|
||||
KeywordUsageStatistics* stat, Value* stat_key) const;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Processing parameters
|
||||
@@ -184,22 +184,22 @@ class KeywordDatabase {
|
||||
/// A script value containing the value of a keyword parameter
|
||||
class KeywordParamValue : public ScriptValue {
|
||||
public:
|
||||
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_before, separator_after;
|
||||
String value;
|
||||
|
||||
virtual ScriptType type() const;
|
||||
virtual String typeName() const;
|
||||
virtual operator String() const;
|
||||
virtual operator int() const;
|
||||
virtual operator bool() const;
|
||||
virtual operator double() const;
|
||||
virtual operator AColor() const;
|
||||
virtual int itemCount() const;
|
||||
virtual ScriptValueP getMember(const String& name) const;
|
||||
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_before, separator_after;
|
||||
String value;
|
||||
|
||||
virtual ScriptType type() const;
|
||||
virtual String typeName() const;
|
||||
virtual operator String() const;
|
||||
virtual operator int() const;
|
||||
virtual operator bool() const;
|
||||
virtual operator double() const;
|
||||
virtual operator AColor() const;
|
||||
virtual int itemCount() const;
|
||||
virtual ScriptValueP getMember(const String& name) const;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
+159
-159
@@ -18,7 +18,7 @@
|
||||
|
||||
#include <wx/stdpaths.h>
|
||||
#if defined(__WXMSW__)
|
||||
#include <wx/mstream.h>
|
||||
#include <wx/mstream.h>
|
||||
#endif
|
||||
|
||||
DECLARE_TYPEOF(map<String COMMA SubLocaleP>);
|
||||
@@ -31,9 +31,9 @@ typedef void (*ReaderPragmaHandler)(String&);
|
||||
DECLARE_DYNAMIC_ARG(ReaderPragmaHandler,reader_pragma_handler);
|
||||
|
||||
void ignore_add_pragma(String& str) {
|
||||
if (starts_with(str,_("#_ADD "))) str = str.substr(6);
|
||||
else if (starts_with(str,_("#_ADD"))) str = str.substr(5);
|
||||
else if (starts_with(str,_("#_DEL"))) str.clear();
|
||||
if (starts_with(str,_("#_ADD "))) str = str.substr(6);
|
||||
else if (starts_with(str,_("#_ADD"))) str = str.substr(5);
|
||||
else if (starts_with(str,_("#_DEL"))) str.clear();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Locale class
|
||||
@@ -44,92 +44,92 @@ String Locale::typeName() const { return _("locale"); }
|
||||
Version Locale::fileVersion() const { return file_version_locale; }
|
||||
|
||||
LocaleP Locale::byName(const String& name) {
|
||||
return package_manager.open<Locale>(name + _(".mse-locale"));
|
||||
return package_manager.open<Locale>(name + _(".mse-locale"));
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION_NO_SCRIPT(Locale) {
|
||||
REFLECT_BASE(Packaged);
|
||||
WITH_DYNAMIC_ARG(reader_pragma_handler, ignore_add_pragma);
|
||||
REFLECT_N("menu", translations[LOCALE_CAT_MENU]);
|
||||
REFLECT_N("help", translations[LOCALE_CAT_HELP]);
|
||||
REFLECT_N("tool", translations[LOCALE_CAT_TOOL]);
|
||||
REFLECT_N("tooltip", translations[LOCALE_CAT_TOOLTIP]);
|
||||
REFLECT_N("label", translations[LOCALE_CAT_LABEL]);
|
||||
REFLECT_N("button", translations[LOCALE_CAT_BUTTON]);
|
||||
REFLECT_N("title", translations[LOCALE_CAT_TITLE]);
|
||||
REFLECT_N("action", translations[LOCALE_CAT_ACTION]);
|
||||
REFLECT_N("error", translations[LOCALE_CAT_ERROR]);
|
||||
REFLECT_N("type", translations[LOCALE_CAT_TYPE]);
|
||||
REFLECT_N("package", package_translations);
|
||||
REFLECT_BASE(Packaged);
|
||||
WITH_DYNAMIC_ARG(reader_pragma_handler, ignore_add_pragma);
|
||||
REFLECT_N("menu", translations[LOCALE_CAT_MENU]);
|
||||
REFLECT_N("help", translations[LOCALE_CAT_HELP]);
|
||||
REFLECT_N("tool", translations[LOCALE_CAT_TOOL]);
|
||||
REFLECT_N("tooltip", translations[LOCALE_CAT_TOOLTIP]);
|
||||
REFLECT_N("label", translations[LOCALE_CAT_LABEL]);
|
||||
REFLECT_N("button", translations[LOCALE_CAT_BUTTON]);
|
||||
REFLECT_N("title", translations[LOCALE_CAT_TITLE]);
|
||||
REFLECT_N("action", translations[LOCALE_CAT_ACTION]);
|
||||
REFLECT_N("error", translations[LOCALE_CAT_ERROR]);
|
||||
REFLECT_N("type", translations[LOCALE_CAT_TYPE]);
|
||||
REFLECT_N("package", package_translations);
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION_NO_GET_MEMBER(SubLocale) {
|
||||
REFLECT_NAMELESS(translations);
|
||||
REFLECT_NAMELESS(translations);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Wildcards
|
||||
|
||||
bool match_wildcard(const String& wildcard, const String& name) {
|
||||
return Regex(replace_all(replace_all(wildcard, _("."), _("\\.")), _("*"), _(".*"))).matches(name);
|
||||
return Regex(replace_all(replace_all(wildcard, _("."), _("\\.")), _("*"), _(".*"))).matches(name);
|
||||
}
|
||||
|
||||
SubLocaleP find_wildcard(map<String,SubLocaleP>& items, const String& name) {
|
||||
FOR_EACH_CONST(i, items) {
|
||||
if (i.second && match_wildcard(i.first, name)) return i.second;
|
||||
}
|
||||
return intrusive(new SubLocale()); // so we don't search again
|
||||
FOR_EACH_CONST(i, items) {
|
||||
if (i.second && match_wildcard(i.first, name)) return i.second;
|
||||
}
|
||||
return intrusive(new SubLocale()); // so we don't search again
|
||||
}
|
||||
SubLocaleP find_wildcard_and_set(map<String,SubLocaleP>& items, const String& name) {
|
||||
return items[name] = find_wildcard(items, name);
|
||||
return items[name] = find_wildcard(items, name);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Translation
|
||||
|
||||
String warn_and_identity(const String& key) {
|
||||
queue_message(MESSAGE_WARNING, _("Missing key in locale: ") + key);
|
||||
return key;
|
||||
queue_message(MESSAGE_WARNING, _("Missing key in locale: ") + key);
|
||||
return key;
|
||||
}
|
||||
|
||||
String SubLocale::tr(const String& key, DefaultLocaleFun def) {
|
||||
map<String,String>::const_iterator it = translations.find(key);
|
||||
if (it == translations.end()) {
|
||||
return def(key);
|
||||
} else {
|
||||
return it->second;
|
||||
}
|
||||
map<String,String>::const_iterator it = translations.find(key);
|
||||
if (it == translations.end()) {
|
||||
return def(key);
|
||||
} else {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
String SubLocale::tr(const String& subcat, const String& key, DefaultLocaleFun def) {
|
||||
map<String,String>::const_iterator it = translations.find(subcat + _(" ") + key);
|
||||
if (it == translations.end()) {
|
||||
return def(key);
|
||||
} else {
|
||||
return it->second;
|
||||
}
|
||||
map<String,String>::const_iterator it = translations.find(subcat + _(" ") + key);
|
||||
if (it == translations.end()) {
|
||||
return def(key);
|
||||
} else {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
|
||||
// from util/locale.hpp
|
||||
|
||||
String tr(LocaleCategory cat, const String& key, DefaultLocaleFun def) {
|
||||
if (!the_locale) return def(key); // no locale loaded (yet)
|
||||
return the_locale->translations[cat].tr(key,def);
|
||||
if (!the_locale) return def(key); // no locale loaded (yet)
|
||||
return the_locale->translations[cat].tr(key,def);
|
||||
}
|
||||
|
||||
String tr(const Package& pkg, const String& key, DefaultLocaleFun def) {
|
||||
if (!the_locale) return def(key);
|
||||
SubLocaleP loc = the_locale->package_translations[pkg.relativeFilename()];
|
||||
if (!loc) {
|
||||
loc = find_wildcard_and_set(the_locale->package_translations, pkg.relativeFilename());
|
||||
}
|
||||
return loc->tr(key, def);
|
||||
if (!the_locale) return def(key);
|
||||
SubLocaleP loc = the_locale->package_translations[pkg.relativeFilename()];
|
||||
if (!loc) {
|
||||
loc = find_wildcard_and_set(the_locale->package_translations, pkg.relativeFilename());
|
||||
}
|
||||
return loc->tr(key, def);
|
||||
}
|
||||
|
||||
String tr(const Package& pkg, const String& subcat, const String& key, DefaultLocaleFun def) {
|
||||
if (!the_locale) return def(key);
|
||||
SubLocaleP loc = the_locale->package_translations[pkg.relativeFilename()];
|
||||
if (!loc) {
|
||||
loc = find_wildcard_and_set(the_locale->package_translations, pkg.relativeFilename());
|
||||
}
|
||||
return loc->tr(subcat, key, def);
|
||||
if (!the_locale) return def(key);
|
||||
SubLocaleP loc = the_locale->package_translations[pkg.relativeFilename()];
|
||||
if (!loc) {
|
||||
loc = find_wildcard_and_set(the_locale->package_translations, pkg.relativeFilename());
|
||||
}
|
||||
return loc->tr(subcat, key, def);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Validation
|
||||
@@ -138,61 +138,61 @@ DECLARE_POINTER_TYPE(SubLocaleValidator);
|
||||
|
||||
class KeyValidator {
|
||||
public:
|
||||
int args;
|
||||
bool optional;
|
||||
DECLARE_REFLECTION();
|
||||
int args;
|
||||
bool optional;
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
class SubLocaleValidator : public IntrusivePtrBase<SubLocaleValidator> {
|
||||
public:
|
||||
map<String,KeyValidator> keys; ///< Arg count for each key
|
||||
DECLARE_REFLECTION();
|
||||
map<String,KeyValidator> keys; ///< Arg count for each key
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
/// Validation information for locales
|
||||
class LocaleValidator {
|
||||
public:
|
||||
map<String, SubLocaleValidatorP> sublocales;
|
||||
DECLARE_REFLECTION();
|
||||
map<String, SubLocaleValidatorP> sublocales;
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
template <> void Reader::handle(KeyValidator& k) {
|
||||
String v = getValue();
|
||||
if (starts_with(v, _("optional, "))) {
|
||||
k.optional = true;
|
||||
v = v.substr(10);
|
||||
} else {
|
||||
k.optional = false;
|
||||
}
|
||||
long l = 0;
|
||||
v.ToLong(&l);
|
||||
k.args = l;
|
||||
String v = getValue();
|
||||
if (starts_with(v, _("optional, "))) {
|
||||
k.optional = true;
|
||||
v = v.substr(10);
|
||||
} else {
|
||||
k.optional = false;
|
||||
}
|
||||
long l = 0;
|
||||
v.ToLong(&l);
|
||||
k.args = l;
|
||||
}
|
||||
template <> void Writer::handle(const KeyValidator& v) {
|
||||
assert(false);
|
||||
assert(false);
|
||||
}
|
||||
IMPLEMENT_REFLECTION_NO_SCRIPT(SubLocaleValidator) {
|
||||
REFLECT_NAMELESS(keys);
|
||||
REFLECT_NAMELESS(keys);
|
||||
}
|
||||
IMPLEMENT_REFLECTION_NO_SCRIPT(LocaleValidator) {
|
||||
REFLECT_NAMELESS(sublocales);
|
||||
REFLECT_NAMELESS(sublocales);
|
||||
}
|
||||
|
||||
/// Count "%s" in str
|
||||
int string_format_args(const String& str) {
|
||||
int count = 0;
|
||||
bool in_percent = false;
|
||||
FOR_EACH_CONST(c, str) {
|
||||
if (in_percent) {
|
||||
if (c == _('s')) {
|
||||
count++;
|
||||
}
|
||||
in_percent = false;
|
||||
} else if (c == _('%')) {
|
||||
in_percent = true;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
int count = 0;
|
||||
bool in_percent = false;
|
||||
FOR_EACH_CONST(c, str) {
|
||||
if (in_percent) {
|
||||
if (c == _('s')) {
|
||||
count++;
|
||||
}
|
||||
in_percent = false;
|
||||
} else if (c == _('%')) {
|
||||
in_percent = true;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/// Load a text file from a resource
|
||||
@@ -200,21 +200,21 @@ int string_format_args(const String& str) {
|
||||
*/
|
||||
InputStreamP load_resource_text(const String& name);
|
||||
InputStreamP load_resource_text(const String& name) {
|
||||
#if defined(__WXMSW__) && !defined(__GNUC__)
|
||||
HRSRC hResource = ::FindResource(wxGetInstance(), name, _("TEXT"));
|
||||
if ( hResource == 0 ) throw InternalError(String::Format(_("Resource not found: %s"), name));
|
||||
HGLOBAL hData = ::LoadResource(wxGetInstance(), hResource);
|
||||
if ( hData == 0 ) throw InternalError(String::Format(_("Resource not text: %s"), name));
|
||||
char* data = (char *)::LockResource(hData);
|
||||
if ( !data ) throw InternalError(String::Format(_("Resource cannot be locked: %s"), name));
|
||||
int len = ::SizeofResource(wxGetInstance(), hResource);
|
||||
return shared(new wxMemoryInputStream(data, len));
|
||||
#else
|
||||
#if defined(__WXMSW__) && !defined(__GNUC__)
|
||||
HRSRC hResource = ::FindResource(wxGetInstance(), name, _("TEXT"));
|
||||
if ( hResource == 0 ) throw InternalError(String::Format(_("Resource not found: %s"), name));
|
||||
HGLOBAL hData = ::LoadResource(wxGetInstance(), hResource);
|
||||
if ( hData == 0 ) throw InternalError(String::Format(_("Resource not text: %s"), name));
|
||||
char* data = (char *)::LockResource(hData);
|
||||
if ( !data ) throw InternalError(String::Format(_("Resource cannot be locked: %s"), name));
|
||||
int len = ::SizeofResource(wxGetInstance(), hResource);
|
||||
return shared(new wxMemoryInputStream(data, len));
|
||||
#else
|
||||
static String path = wxStandardPaths::Get().GetDataDir() + _("/resource/");
|
||||
static String local_path = wxStandardPaths::Get().GetUserDataDir() + _("/resource/");
|
||||
if (wxFileExists(path + name)) return shared(new wxFileInputStream(path + name));
|
||||
else return shared(new wxFileInputStream(local_path + name));
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -222,69 +222,69 @@ DECLARE_TYPEOF(map<String COMMA String>);
|
||||
DECLARE_TYPEOF(map<String COMMA KeyValidator>);
|
||||
|
||||
void Locale::validate(Version ver) {
|
||||
Packaged::validate(ver);
|
||||
// load locale validator
|
||||
LocaleValidator v;
|
||||
Reader r(load_resource_text(_("expected_locale_keys")), nullptr, _("expected_locale_keys"));
|
||||
r.handle_greedy(v);
|
||||
// validate
|
||||
String errors;
|
||||
errors += translations[LOCALE_CAT_MENU ].validate(_("menu"), v.sublocales[_("menu") ]);
|
||||
errors += translations[LOCALE_CAT_HELP ].validate(_("help"), v.sublocales[_("help") ]);
|
||||
errors += translations[LOCALE_CAT_TOOL ].validate(_("tool"), v.sublocales[_("tool") ]);
|
||||
errors += translations[LOCALE_CAT_TOOLTIP].validate(_("tooltip"), v.sublocales[_("tooltip")]);
|
||||
errors += translations[LOCALE_CAT_LABEL ].validate(_("label"), v.sublocales[_("label") ]);
|
||||
errors += translations[LOCALE_CAT_BUTTON ].validate(_("button"), v.sublocales[_("button") ]);
|
||||
errors += translations[LOCALE_CAT_TITLE ].validate(_("title"), v.sublocales[_("title") ]);
|
||||
errors += translations[LOCALE_CAT_ACTION ].validate(_("action"), v.sublocales[_("action") ]);
|
||||
errors += translations[LOCALE_CAT_ERROR ].validate(_("error"), v.sublocales[_("error") ]);
|
||||
errors += translations[LOCALE_CAT_TYPE ].validate(_("type"), v.sublocales[_("type") ]);
|
||||
// errors?
|
||||
if (!errors.empty()) {
|
||||
if (ver != file_version_locale) {
|
||||
errors = _("Errors in locale file ") + short_name + _(":") + errors;
|
||||
} else {
|
||||
errors = _("Errors in locale file ") + short_name +
|
||||
_("\nThis is probably because the locale was made for a different version of MSE.") + errors;
|
||||
}
|
||||
} else if (ver != file_version_locale) {
|
||||
errors = _("Errors in locale file ") + short_name + _(":")
|
||||
+ _("\n Locale file out of date, expected: mse version: ") + file_version_locale.toString()
|
||||
+ _("\n found: ") + ver.toString();
|
||||
}
|
||||
if (!errors.empty()) {
|
||||
queue_message(MESSAGE_WARNING, errors);
|
||||
}
|
||||
Packaged::validate(ver);
|
||||
// load locale validator
|
||||
LocaleValidator v;
|
||||
Reader r(load_resource_text(_("expected_locale_keys")), nullptr, _("expected_locale_keys"));
|
||||
r.handle_greedy(v);
|
||||
// validate
|
||||
String errors;
|
||||
errors += translations[LOCALE_CAT_MENU ].validate(_("menu"), v.sublocales[_("menu") ]);
|
||||
errors += translations[LOCALE_CAT_HELP ].validate(_("help"), v.sublocales[_("help") ]);
|
||||
errors += translations[LOCALE_CAT_TOOL ].validate(_("tool"), v.sublocales[_("tool") ]);
|
||||
errors += translations[LOCALE_CAT_TOOLTIP].validate(_("tooltip"), v.sublocales[_("tooltip")]);
|
||||
errors += translations[LOCALE_CAT_LABEL ].validate(_("label"), v.sublocales[_("label") ]);
|
||||
errors += translations[LOCALE_CAT_BUTTON ].validate(_("button"), v.sublocales[_("button") ]);
|
||||
errors += translations[LOCALE_CAT_TITLE ].validate(_("title"), v.sublocales[_("title") ]);
|
||||
errors += translations[LOCALE_CAT_ACTION ].validate(_("action"), v.sublocales[_("action") ]);
|
||||
errors += translations[LOCALE_CAT_ERROR ].validate(_("error"), v.sublocales[_("error") ]);
|
||||
errors += translations[LOCALE_CAT_TYPE ].validate(_("type"), v.sublocales[_("type") ]);
|
||||
// errors?
|
||||
if (!errors.empty()) {
|
||||
if (ver != file_version_locale) {
|
||||
errors = _("Errors in locale file ") + short_name + _(":") + errors;
|
||||
} else {
|
||||
errors = _("Errors in locale file ") + short_name +
|
||||
_("\nThis is probably because the locale was made for a different version of MSE.") + errors;
|
||||
}
|
||||
} else if (ver != file_version_locale) {
|
||||
errors = _("Errors in locale file ") + short_name + _(":")
|
||||
+ _("\n Locale file out of date, expected: mse version: ") + file_version_locale.toString()
|
||||
+ _("\n found: ") + ver.toString();
|
||||
}
|
||||
if (!errors.empty()) {
|
||||
queue_message(MESSAGE_WARNING, errors);
|
||||
}
|
||||
}
|
||||
|
||||
String SubLocale::validate(const String& name, const SubLocaleValidatorP& v) const {
|
||||
if (!v) {
|
||||
return _("\nInternal error validating local file: expected keys file missing for \"") + name + _("\" section.");
|
||||
}
|
||||
String errors;
|
||||
// 1. keys in v but not in this, check arg count
|
||||
FOR_EACH_CONST(kc, v->keys) {
|
||||
map<String,String>::const_iterator it = translations.find(kc.first);
|
||||
if (it == translations.end()) {
|
||||
if (!kc.second.optional) {
|
||||
errors += _("\n Missing key:\t\t\t") + name + _(": ") + kc.first;
|
||||
}
|
||||
} else if (string_format_args(it->second) != kc.second.args) {
|
||||
errors += _("\n Incorrect number of arguments for:\t") + name + _(": ") + kc.first
|
||||
+ String::Format(_("\t expected: %d, found %d"), kc.second.args, string_format_args(it->second));
|
||||
}
|
||||
}
|
||||
// 2. keys in this but not in v
|
||||
FOR_EACH_CONST(kv, translations) {
|
||||
map<String,KeyValidator>::const_iterator it = v->keys.find(kv.first);
|
||||
if (it == v->keys.end() && !kv.second.empty()) {
|
||||
// allow extra keys with empty values as a kind of documentation
|
||||
// for example in the help stirngs:
|
||||
// help:
|
||||
// file:
|
||||
// new set: blah blah
|
||||
errors += _("\n Unexpected key:\t\t\t") + name + _(": ") + kv.first;
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
if (!v) {
|
||||
return _("\nInternal error validating local file: expected keys file missing for \"") + name + _("\" section.");
|
||||
}
|
||||
String errors;
|
||||
// 1. keys in v but not in this, check arg count
|
||||
FOR_EACH_CONST(kc, v->keys) {
|
||||
map<String,String>::const_iterator it = translations.find(kc.first);
|
||||
if (it == translations.end()) {
|
||||
if (!kc.second.optional) {
|
||||
errors += _("\n Missing key:\t\t\t") + name + _(": ") + kc.first;
|
||||
}
|
||||
} else if (string_format_args(it->second) != kc.second.args) {
|
||||
errors += _("\n Incorrect number of arguments for:\t") + name + _(": ") + kc.first
|
||||
+ String::Format(_("\t expected: %d, found %d"), kc.second.args, string_format_args(it->second));
|
||||
}
|
||||
}
|
||||
// 2. keys in this but not in v
|
||||
FOR_EACH_CONST(kv, translations) {
|
||||
map<String,KeyValidator>::const_iterator it = v->keys.find(kv.first);
|
||||
if (it == v->keys.end() && !kv.second.empty()) {
|
||||
// allow extra keys with empty values as a kind of documentation
|
||||
// for example in the help stirngs:
|
||||
// help:
|
||||
// file:
|
||||
// new set: blah blah
|
||||
errors += _("\n Unexpected key:\t\t\t") + name + _(": ") + kv.first;
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
+24
-24
@@ -23,36 +23,36 @@ DECLARE_POINTER_TYPE(SubLocaleValidator);
|
||||
/// Translations of the texts of a game/stylesheet/symbolfont
|
||||
class SubLocale : public IntrusivePtrBase<SubLocale> {
|
||||
public:
|
||||
map<String,String> translations;
|
||||
|
||||
/// Translate a key, if not found, apply the default function to the key
|
||||
String tr(const String& key, DefaultLocaleFun def);
|
||||
String tr(const String& subcat, const String& key, DefaultLocaleFun def);
|
||||
|
||||
/// Is this a valid sublocale? Returns errors
|
||||
String validate(const String& name, const SubLocaleValidatorP&) const;
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
map<String,String> translations;
|
||||
|
||||
/// Translate a key, if not found, apply the default function to the key
|
||||
String tr(const String& key, DefaultLocaleFun def);
|
||||
String tr(const String& subcat, const String& key, DefaultLocaleFun def);
|
||||
|
||||
/// Is this a valid sublocale? Returns errors
|
||||
String validate(const String& name, const SubLocaleValidatorP&) const;
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
/// A collection of translations of messages
|
||||
class Locale : public Packaged {
|
||||
public:
|
||||
/// Translations of UI strings in each category
|
||||
SubLocale translations[LOCALE_CAT_MAX];
|
||||
/// Translations of Package specific texts, by relativeFilename
|
||||
map<String,SubLocaleP> package_translations;
|
||||
|
||||
/// Open a locale with the given name
|
||||
static LocaleP byName(const String& name);
|
||||
|
||||
/// Validate that the locale is valid for this MSE version
|
||||
virtual void validate(Version = app_version);
|
||||
|
||||
/// Translations of UI strings in each category
|
||||
SubLocale translations[LOCALE_CAT_MAX];
|
||||
/// Translations of Package specific texts, by relativeFilename
|
||||
map<String,SubLocaleP> package_translations;
|
||||
|
||||
/// Open a locale with the given name
|
||||
static LocaleP byName(const String& name);
|
||||
|
||||
/// Validate that the locale is valid for this MSE version
|
||||
virtual void validate(Version = app_version);
|
||||
|
||||
protected:
|
||||
String typeName() const;
|
||||
Version fileVersion() const;
|
||||
DECLARE_REFLECTION();
|
||||
String typeName() const;
|
||||
Version fileVersion() const;
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
/// The global locale object
|
||||
|
||||
+338
-338
@@ -23,424 +23,424 @@ DECLARE_TYPEOF_CONST(map<String COMMA PackInstanceP>);
|
||||
|
||||
|
||||
IMPLEMENT_REFLECTION_ENUM(PackSelectType) {
|
||||
VALUE_N("auto", SELECT_AUTO);
|
||||
VALUE_N("all", SELECT_ALL);
|
||||
VALUE_N("no replace", SELECT_NO_REPLACE);
|
||||
VALUE_N("replace", SELECT_REPLACE);
|
||||
VALUE_N("proportional", SELECT_PROPORTIONAL);
|
||||
VALUE_N("nonempty", SELECT_NONEMPTY);
|
||||
VALUE_N("equal", SELECT_EQUAL);
|
||||
VALUE_N("equal proportional", SELECT_EQUAL_PROPORTIONAL);
|
||||
VALUE_N("equal nonempty", SELECT_NONEMPTY);
|
||||
VALUE_N("first", SELECT_FIRST);
|
||||
VALUE_N("auto", SELECT_AUTO);
|
||||
VALUE_N("all", SELECT_ALL);
|
||||
VALUE_N("no replace", SELECT_NO_REPLACE);
|
||||
VALUE_N("replace", SELECT_REPLACE);
|
||||
VALUE_N("proportional", SELECT_PROPORTIONAL);
|
||||
VALUE_N("nonempty", SELECT_NONEMPTY);
|
||||
VALUE_N("equal", SELECT_EQUAL);
|
||||
VALUE_N("equal proportional", SELECT_EQUAL_PROPORTIONAL);
|
||||
VALUE_N("equal nonempty", SELECT_NONEMPTY);
|
||||
VALUE_N("first", SELECT_FIRST);
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION(PackType) {
|
||||
REFLECT(name);
|
||||
REFLECT(enabled);
|
||||
REFLECT(selectable);
|
||||
REFLECT(summary);
|
||||
REFLECT(select);
|
||||
REFLECT(filter);
|
||||
REFLECT(items);
|
||||
REFLECT_IF_READING {
|
||||
if (select == SELECT_AUTO) {
|
||||
if (filter) select = SELECT_NO_REPLACE;
|
||||
else if (!items.empty()) select = SELECT_ALL;
|
||||
}
|
||||
if (indeterminate(summary)) {
|
||||
if (filter) summary = true;
|
||||
else if (!items.empty()) summary = false;
|
||||
}
|
||||
if (indeterminate(selectable)) {
|
||||
if (filter) selectable = false;
|
||||
else if (!items.empty()) selectable = true;
|
||||
}
|
||||
}
|
||||
REFLECT(name);
|
||||
REFLECT(enabled);
|
||||
REFLECT(selectable);
|
||||
REFLECT(summary);
|
||||
REFLECT(select);
|
||||
REFLECT(filter);
|
||||
REFLECT(items);
|
||||
REFLECT_IF_READING {
|
||||
if (select == SELECT_AUTO) {
|
||||
if (filter) select = SELECT_NO_REPLACE;
|
||||
else if (!items.empty()) select = SELECT_ALL;
|
||||
}
|
||||
if (indeterminate(summary)) {
|
||||
if (filter) summary = true;
|
||||
else if (!items.empty()) summary = false;
|
||||
}
|
||||
if (indeterminate(selectable)) {
|
||||
if (filter) selectable = false;
|
||||
else if (!items.empty()) selectable = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION(PackItem) {
|
||||
if (!tag.isComplex()) {
|
||||
REFLECT_NAMELESS(name);
|
||||
} else {
|
||||
REFLECT(name);
|
||||
REFLECT(amount);
|
||||
REFLECT(weight);
|
||||
}
|
||||
if (!tag.isComplex()) {
|
||||
REFLECT_NAMELESS(name);
|
||||
} else {
|
||||
REFLECT(name);
|
||||
REFLECT(amount);
|
||||
REFLECT(weight);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
PackType::PackType()
|
||||
: enabled(true)
|
||||
, selectable(indeterminate)
|
||||
, summary(indeterminate)
|
||||
, select(SELECT_AUTO)
|
||||
: enabled(true)
|
||||
, selectable(indeterminate)
|
||||
, summary(indeterminate)
|
||||
, select(SELECT_AUTO)
|
||||
{}
|
||||
|
||||
PackItem::PackItem()
|
||||
: amount(1)
|
||||
, weight(1)
|
||||
: amount(1)
|
||||
, weight(1)
|
||||
{}
|
||||
|
||||
PackItem::PackItem(const String& name, int amount)
|
||||
: name(name)
|
||||
, amount(amount)
|
||||
, weight(1)
|
||||
: name(name)
|
||||
, amount(amount)
|
||||
, weight(1)
|
||||
{}
|
||||
|
||||
|
||||
bool PackType::update(Context& ctx) {
|
||||
bool change = enabled.update(ctx);
|
||||
FOR_EACH(item, items) {
|
||||
change |= item->update(ctx);
|
||||
}
|
||||
return change;
|
||||
bool change = enabled.update(ctx);
|
||||
FOR_EACH(item, items) {
|
||||
change |= item->update(ctx);
|
||||
}
|
||||
return change;
|
||||
}
|
||||
|
||||
bool PackItem::update(Context& ctx) {
|
||||
return amount.update(ctx)
|
||||
| weight.update(ctx);
|
||||
return amount.update(ctx)
|
||||
| weight.update(ctx);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------- : PackInstance
|
||||
|
||||
PackInstance::PackInstance(const PackType& pack_type, PackGenerator& parent)
|
||||
: pack_type(pack_type)
|
||||
, parent(parent)
|
||||
, requested_copies(0)
|
||||
, card_copies(0)
|
||||
, expected_copies(0)
|
||||
: pack_type(pack_type)
|
||||
, parent(parent)
|
||||
, requested_copies(0)
|
||||
, card_copies(0)
|
||||
, expected_copies(0)
|
||||
{
|
||||
// Filter cards
|
||||
if (pack_type.filter) {
|
||||
FOR_EACH(card, parent.set->cards) {
|
||||
Context& ctx = parent.set->getContext(card);
|
||||
bool keep = *pack_type.filter.invoke(ctx);
|
||||
if (keep) {
|
||||
cards.push_back(card);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Sum of weights
|
||||
if (pack_type.select == SELECT_FIRST) {
|
||||
total_weight = cards.empty() ? 0 : 1;
|
||||
} else {
|
||||
total_weight = cards.size();
|
||||
}
|
||||
FOR_EACH_CONST(item, pack_type.items) {
|
||||
if (pack_type.select == SELECT_PROPORTIONAL || pack_type.select == SELECT_EQUAL_PROPORTIONAL) {
|
||||
total_weight += item->weight * parent.get(item->name).total_weight;
|
||||
} else if (pack_type.select == SELECT_NONEMPTY || pack_type.select == SELECT_EQUAL_NONEMPTY) {
|
||||
if (parent.get(item->name).total_weight > 0) {
|
||||
total_weight += item->weight;
|
||||
}
|
||||
} else if (pack_type.select == SELECT_FIRST) {
|
||||
if (total_weight <= 0) {
|
||||
total_weight = item->weight;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
total_weight += item->weight;
|
||||
}
|
||||
}
|
||||
// Depth
|
||||
depth = 0;
|
||||
FOR_EACH_CONST(item, pack_type.items) {
|
||||
depth = max(depth, 1 + parent.get(item->name).depth);
|
||||
}
|
||||
// Filter cards
|
||||
if (pack_type.filter) {
|
||||
FOR_EACH(card, parent.set->cards) {
|
||||
Context& ctx = parent.set->getContext(card);
|
||||
bool keep = *pack_type.filter.invoke(ctx);
|
||||
if (keep) {
|
||||
cards.push_back(card);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Sum of weights
|
||||
if (pack_type.select == SELECT_FIRST) {
|
||||
total_weight = cards.empty() ? 0 : 1;
|
||||
} else {
|
||||
total_weight = cards.size();
|
||||
}
|
||||
FOR_EACH_CONST(item, pack_type.items) {
|
||||
if (pack_type.select == SELECT_PROPORTIONAL || pack_type.select == SELECT_EQUAL_PROPORTIONAL) {
|
||||
total_weight += item->weight * parent.get(item->name).total_weight;
|
||||
} else if (pack_type.select == SELECT_NONEMPTY || pack_type.select == SELECT_EQUAL_NONEMPTY) {
|
||||
if (parent.get(item->name).total_weight > 0) {
|
||||
total_weight += item->weight;
|
||||
}
|
||||
} else if (pack_type.select == SELECT_FIRST) {
|
||||
if (total_weight <= 0) {
|
||||
total_weight = item->weight;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
total_weight += item->weight;
|
||||
}
|
||||
}
|
||||
// Depth
|
||||
depth = 0;
|
||||
FOR_EACH_CONST(item, pack_type.items) {
|
||||
depth = max(depth, 1 + parent.get(item->name).depth);
|
||||
}
|
||||
}
|
||||
|
||||
void PackInstance::expect_copy(double copies) {
|
||||
this->expected_copies += copies;
|
||||
// propagate
|
||||
FOR_EACH_CONST(item, pack_type.items) {
|
||||
PackInstance& i = parent.get(item->name);
|
||||
if (pack_type.select == SELECT_ALL) {
|
||||
i.expect_copy(copies * item->amount);
|
||||
} else if (pack_type.select == SELECT_PROPORTIONAL || pack_type.select == SELECT_EQUAL_PROPORTIONAL) {
|
||||
i.expect_copy(copies * item->amount * item->weight * i.total_weight / total_weight);
|
||||
} else if (pack_type.select == SELECT_NONEMPTY || pack_type.select == SELECT_EQUAL_NONEMPTY) {
|
||||
if (i.total_weight > 0) {
|
||||
i.expect_copy(copies * item->amount * item->weight / total_weight);
|
||||
}
|
||||
} else if (pack_type.select == SELECT_FIRST) {
|
||||
if (i.total_weight > 0 && cards.empty()) {
|
||||
i.expect_copy(copies * item->amount);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
i.expect_copy(copies * item->amount * item->weight / total_weight);
|
||||
}
|
||||
}
|
||||
this->expected_copies += copies;
|
||||
// propagate
|
||||
FOR_EACH_CONST(item, pack_type.items) {
|
||||
PackInstance& i = parent.get(item->name);
|
||||
if (pack_type.select == SELECT_ALL) {
|
||||
i.expect_copy(copies * item->amount);
|
||||
} else if (pack_type.select == SELECT_PROPORTIONAL || pack_type.select == SELECT_EQUAL_PROPORTIONAL) {
|
||||
i.expect_copy(copies * item->amount * item->weight * i.total_weight / total_weight);
|
||||
} else if (pack_type.select == SELECT_NONEMPTY || pack_type.select == SELECT_EQUAL_NONEMPTY) {
|
||||
if (i.total_weight > 0) {
|
||||
i.expect_copy(copies * item->amount * item->weight / total_weight);
|
||||
}
|
||||
} else if (pack_type.select == SELECT_FIRST) {
|
||||
if (i.total_weight > 0 && cards.empty()) {
|
||||
i.expect_copy(copies * item->amount);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
i.expect_copy(copies * item->amount * item->weight / total_weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PackInstance::request_copy(size_t copies) {
|
||||
requested_copies += copies;
|
||||
requested_copies += copies;
|
||||
}
|
||||
|
||||
/// Random generator with random numbers in a range
|
||||
template <typename Gen>
|
||||
struct RandomRange {
|
||||
RandomRange(Gen& gen) : gen(gen) {}
|
||||
unsigned operator () (unsigned max) { return gen() % max; }
|
||||
Gen& gen;
|
||||
RandomRange(Gen& gen) : gen(gen) {}
|
||||
unsigned operator () (unsigned max) { return gen() % max; }
|
||||
Gen& gen;
|
||||
};
|
||||
|
||||
struct WeightedItem {
|
||||
double weight;
|
||||
int count;
|
||||
int tiebreaker;
|
||||
double weight;
|
||||
int count;
|
||||
int tiebreaker;
|
||||
};
|
||||
|
||||
struct CompareWeightedItems{
|
||||
inline bool operator () (WeightedItem* a, WeightedItem* b) {
|
||||
// compare (a->count+1)/a->weight <> (b->count+1)/b->weight
|
||||
// prefer the one where this is lower, return true if b is prefered
|
||||
double delta = b->weight * (a->count + 1) - a->weight * (b->count + 1);
|
||||
if (delta < 0) return false;
|
||||
if (delta > 0) return true;
|
||||
return b->tiebreaker < a->tiebreaker;
|
||||
}
|
||||
inline bool operator () (WeightedItem* a, WeightedItem* b) {
|
||||
// compare (a->count+1)/a->weight <> (b->count+1)/b->weight
|
||||
// prefer the one where this is lower, return true if b is prefered
|
||||
double delta = b->weight * (a->count + 1) - a->weight * (b->count + 1);
|
||||
if (delta < 0) return false;
|
||||
if (delta > 0) return true;
|
||||
return b->tiebreaker < a->tiebreaker;
|
||||
}
|
||||
};
|
||||
|
||||
/// Distribute 'total' among the weighted items, higher weight items get chosen more often
|
||||
void weighted_equal_divide(vector<WeightedItem>& items, int total) {
|
||||
assert(!items.empty());
|
||||
if (items.size() == 1) {
|
||||
items.front().count = total;
|
||||
} else {
|
||||
priority_queue<WeightedItem*,vector<WeightedItem*>,CompareWeightedItems> pq;
|
||||
for (size_t i = 0 ; i < items.size() ; ++i) {
|
||||
pq.push(&items[i]);
|
||||
}
|
||||
while (total > 0) {
|
||||
// repeatedly pick the item that minimizes, after incrementing count:
|
||||
// max_wi wi->count/wi->weight
|
||||
WeightedItem* wi = pq.top();pq.pop();
|
||||
wi->count++;
|
||||
total--;
|
||||
pq.push(wi);
|
||||
}
|
||||
}
|
||||
assert(!items.empty());
|
||||
if (items.size() == 1) {
|
||||
items.front().count = total;
|
||||
} else {
|
||||
priority_queue<WeightedItem*,vector<WeightedItem*>,CompareWeightedItems> pq;
|
||||
for (size_t i = 0 ; i < items.size() ; ++i) {
|
||||
pq.push(&items[i]);
|
||||
}
|
||||
while (total > 0) {
|
||||
// repeatedly pick the item that minimizes, after incrementing count:
|
||||
// max_wi wi->count/wi->weight
|
||||
WeightedItem* wi = pq.top();pq.pop();
|
||||
wi->count++;
|
||||
total--;
|
||||
pq.push(wi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PackInstance::generate(vector<CardP>* out) {
|
||||
card_copies = 0;
|
||||
if (requested_copies == 0) return;
|
||||
if (pack_type.select == SELECT_ALL) {
|
||||
// add all cards
|
||||
generate_all(out, requested_copies);
|
||||
card_copies = 0;
|
||||
if (requested_copies == 0) return;
|
||||
if (pack_type.select == SELECT_ALL) {
|
||||
// add all cards
|
||||
generate_all(out, requested_copies);
|
||||
|
||||
} else if (pack_type.select == SELECT_REPLACE
|
||||
|| pack_type.select == SELECT_PROPORTIONAL
|
||||
|| pack_type.select == SELECT_NONEMPTY) {
|
||||
// multiple copies
|
||||
for (size_t i = 0 ; i < requested_copies ; ++i) {
|
||||
generate_one_random(out);
|
||||
}
|
||||
} else if (pack_type.select == SELECT_REPLACE
|
||||
|| pack_type.select == SELECT_PROPORTIONAL
|
||||
|| pack_type.select == SELECT_NONEMPTY) {
|
||||
// multiple copies
|
||||
for (size_t i = 0 ; i < requested_copies ; ++i) {
|
||||
generate_one_random(out);
|
||||
}
|
||||
|
||||
} else if (pack_type.select == SELECT_NO_REPLACE) {
|
||||
if (!pack_type.items.empty()) {
|
||||
throw Error(_("'select:no replace' is not yet supported in combination with 'items', only with 'filter'."));
|
||||
}
|
||||
card_copies += requested_copies;
|
||||
// NOTE: there is no way to pick items without replacement
|
||||
if (out && !cards.empty()) {
|
||||
// to prevent us from being too predictable for small sets, periodically reshuffle
|
||||
RandomRange<boost::mt19937> gen_range(parent.gen);
|
||||
int max_per_batch = ((int)cards.size() + 1) / 2;
|
||||
int rem = (int)requested_copies;
|
||||
while (rem > 0) {
|
||||
random_shuffle(cards.begin(), cards.end(), gen_range);
|
||||
out->insert(out->end(), cards.begin(), cards.begin() + min(rem, max_per_batch));
|
||||
rem -= max_per_batch;
|
||||
}
|
||||
}
|
||||
} else if (pack_type.select == SELECT_NO_REPLACE) {
|
||||
if (!pack_type.items.empty()) {
|
||||
throw Error(_("'select:no replace' is not yet supported in combination with 'items', only with 'filter'."));
|
||||
}
|
||||
card_copies += requested_copies;
|
||||
// NOTE: there is no way to pick items without replacement
|
||||
if (out && !cards.empty()) {
|
||||
// to prevent us from being too predictable for small sets, periodically reshuffle
|
||||
RandomRange<boost::mt19937> gen_range(parent.gen);
|
||||
int max_per_batch = ((int)cards.size() + 1) / 2;
|
||||
int rem = (int)requested_copies;
|
||||
while (rem > 0) {
|
||||
random_shuffle(cards.begin(), cards.end(), gen_range);
|
||||
out->insert(out->end(), cards.begin(), cards.begin() + min(rem, max_per_batch));
|
||||
rem -= max_per_batch;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (pack_type.select == SELECT_EQUAL
|
||||
|| pack_type.select == SELECT_EQUAL_PROPORTIONAL
|
||||
|| pack_type.select == SELECT_EQUAL_NONEMPTY) {
|
||||
// equal selection instead of random
|
||||
if (requested_copies == 1) {
|
||||
// somewhat of a hack to keep things fair: just pick at random
|
||||
// otherwise we would end up picking the lowest weight item
|
||||
generate_one_random(out);
|
||||
} else {
|
||||
// 1. the weights of each item, and of the cards
|
||||
vector<WeightedItem> weighted_items;
|
||||
FOR_EACH_CONST(item, pack_type.items) {
|
||||
WeightedItem wi = {0,0,parent.gen()};
|
||||
if (pack_type.select == SELECT_EQUAL_PROPORTIONAL) {
|
||||
wi.weight = item->weight * parent.get(item->name).total_weight;
|
||||
} else if (pack_type.select == SELECT_EQUAL_NONEMPTY) {
|
||||
wi.weight = parent.get(item->name).total_weight > 0 ? static_cast<int>(item->weight) : 0;
|
||||
} else {
|
||||
wi.weight = item->weight;
|
||||
}
|
||||
weighted_items.push_back(wi);
|
||||
}
|
||||
WeightedItem wi = {cards.size(),0,parent.gen()};
|
||||
weighted_items.push_back(wi);
|
||||
// 2. divide the requested_copies among the cards and the items, taking the weights into account
|
||||
weighted_equal_divide(weighted_items, (int)requested_copies);
|
||||
// 3a. propagate to items
|
||||
for (size_t j = 0 ; j < pack_type.items.size() ; ++j) {
|
||||
const PackItem& item = *pack_type.items[j];
|
||||
PackInstance& i = parent.get(item.name);
|
||||
i.request_copy(item.amount * weighted_items[j].count);
|
||||
}
|
||||
// 3b. pick some cards
|
||||
int new_card_copies = weighted_items.back().count;
|
||||
card_copies += new_card_copies;
|
||||
if (out && !cards.empty()) {
|
||||
int div = new_card_copies / (int)cards.size();
|
||||
int rem = new_card_copies % (int)cards.size();
|
||||
// some copies of all cards
|
||||
for (int i = 0 ; i < div ; ++i) {
|
||||
out->insert(out->end(), cards.begin(), cards.end());
|
||||
}
|
||||
// pick the remainder at random
|
||||
for (int i = 0 ; i < rem ; ++i) {
|
||||
int nr = parent.gen() % cards.size();
|
||||
out->push_back(cards.at(nr));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (pack_type.select == SELECT_EQUAL
|
||||
|| pack_type.select == SELECT_EQUAL_PROPORTIONAL
|
||||
|| pack_type.select == SELECT_EQUAL_NONEMPTY) {
|
||||
// equal selection instead of random
|
||||
if (requested_copies == 1) {
|
||||
// somewhat of a hack to keep things fair: just pick at random
|
||||
// otherwise we would end up picking the lowest weight item
|
||||
generate_one_random(out);
|
||||
} else {
|
||||
// 1. the weights of each item, and of the cards
|
||||
vector<WeightedItem> weighted_items;
|
||||
FOR_EACH_CONST(item, pack_type.items) {
|
||||
WeightedItem wi = {0,0,parent.gen()};
|
||||
if (pack_type.select == SELECT_EQUAL_PROPORTIONAL) {
|
||||
wi.weight = item->weight * parent.get(item->name).total_weight;
|
||||
} else if (pack_type.select == SELECT_EQUAL_NONEMPTY) {
|
||||
wi.weight = parent.get(item->name).total_weight > 0 ? static_cast<int>(item->weight) : 0;
|
||||
} else {
|
||||
wi.weight = item->weight;
|
||||
}
|
||||
weighted_items.push_back(wi);
|
||||
}
|
||||
WeightedItem wi = {cards.size(),0,parent.gen()};
|
||||
weighted_items.push_back(wi);
|
||||
// 2. divide the requested_copies among the cards and the items, taking the weights into account
|
||||
weighted_equal_divide(weighted_items, (int)requested_copies);
|
||||
// 3a. propagate to items
|
||||
for (size_t j = 0 ; j < pack_type.items.size() ; ++j) {
|
||||
const PackItem& item = *pack_type.items[j];
|
||||
PackInstance& i = parent.get(item.name);
|
||||
i.request_copy(item.amount * weighted_items[j].count);
|
||||
}
|
||||
// 3b. pick some cards
|
||||
int new_card_copies = weighted_items.back().count;
|
||||
card_copies += new_card_copies;
|
||||
if (out && !cards.empty()) {
|
||||
int div = new_card_copies / (int)cards.size();
|
||||
int rem = new_card_copies % (int)cards.size();
|
||||
// some copies of all cards
|
||||
for (int i = 0 ; i < div ; ++i) {
|
||||
out->insert(out->end(), cards.begin(), cards.end());
|
||||
}
|
||||
// pick the remainder at random
|
||||
for (int i = 0 ; i < rem ; ++i) {
|
||||
int nr = parent.gen() % cards.size();
|
||||
out->push_back(cards.at(nr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else if (pack_type.select == SELECT_FIRST) {
|
||||
if (!cards.empty()) {
|
||||
// there is a card, pick it
|
||||
card_copies += requested_copies;
|
||||
if (out) out->insert(out->end(), requested_copies, cards.front());
|
||||
} else {
|
||||
// pick first nonempty item
|
||||
FOR_EACH_CONST(item, pack_type.items) {
|
||||
PackInstance& i = parent.get(item->name);
|
||||
if (i.total_weight > 0) {
|
||||
i.request_copy(requested_copies * item->amount);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
requested_copies = 0;
|
||||
} else if (pack_type.select == SELECT_FIRST) {
|
||||
if (!cards.empty()) {
|
||||
// there is a card, pick it
|
||||
card_copies += requested_copies;
|
||||
if (out) out->insert(out->end(), requested_copies, cards.front());
|
||||
} else {
|
||||
// pick first nonempty item
|
||||
FOR_EACH_CONST(item, pack_type.items) {
|
||||
PackInstance& i = parent.get(item->name);
|
||||
if (i.total_weight > 0) {
|
||||
i.request_copy(requested_copies * item->amount);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
requested_copies = 0;
|
||||
}
|
||||
|
||||
void PackInstance::generate_all(vector<CardP>* out, size_t copies) {
|
||||
card_copies += copies * cards.size();
|
||||
if (out) {
|
||||
for (size_t i = 0 ; i < copies ; ++i) {
|
||||
out->insert(out->end(), cards.begin(), cards.end());
|
||||
}
|
||||
}
|
||||
// and all items
|
||||
FOR_EACH_CONST(item, pack_type.items) {
|
||||
PackInstance& i = parent.get(item->name);
|
||||
i.request_copy(copies * item->amount);
|
||||
}
|
||||
card_copies += copies * cards.size();
|
||||
if (out) {
|
||||
for (size_t i = 0 ; i < copies ; ++i) {
|
||||
out->insert(out->end(), cards.begin(), cards.end());
|
||||
}
|
||||
}
|
||||
// and all items
|
||||
FOR_EACH_CONST(item, pack_type.items) {
|
||||
PackInstance& i = parent.get(item->name);
|
||||
i.request_copy(copies * item->amount);
|
||||
}
|
||||
}
|
||||
|
||||
void PackInstance::generate_one_random(vector<CardP>* out) {
|
||||
double r = parent.gen() * total_weight / parent.gen.max();
|
||||
if (r < cards.size()) {
|
||||
// pick a card
|
||||
card_copies++;
|
||||
if (out) {
|
||||
int i = (int)r;
|
||||
out->push_back(cards[i]);
|
||||
}
|
||||
} else {
|
||||
// pick an item
|
||||
r -= cards.size();
|
||||
FOR_EACH_CONST(item, pack_type.items) {
|
||||
PackInstance& i = parent.get(item->name);
|
||||
if (pack_type.select == SELECT_PROPORTIONAL || pack_type.select == SELECT_EQUAL_PROPORTIONAL) {
|
||||
r -= item->weight * i.total_weight;
|
||||
} else if (pack_type.select == SELECT_NONEMPTY || pack_type.select == SELECT_EQUAL_NONEMPTY) {
|
||||
if (i.total_weight > 0) r -= item->weight;
|
||||
} else {
|
||||
r -= item->weight;
|
||||
}
|
||||
// have we reached the item we were looking for?
|
||||
if (r < 0) {
|
||||
i.request_copy(item->amount);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
double r = parent.gen() * total_weight / parent.gen.max();
|
||||
if (r < cards.size()) {
|
||||
// pick a card
|
||||
card_copies++;
|
||||
if (out) {
|
||||
int i = (int)r;
|
||||
out->push_back(cards[i]);
|
||||
}
|
||||
} else {
|
||||
// pick an item
|
||||
r -= cards.size();
|
||||
FOR_EACH_CONST(item, pack_type.items) {
|
||||
PackInstance& i = parent.get(item->name);
|
||||
if (pack_type.select == SELECT_PROPORTIONAL || pack_type.select == SELECT_EQUAL_PROPORTIONAL) {
|
||||
r -= item->weight * i.total_weight;
|
||||
} else if (pack_type.select == SELECT_NONEMPTY || pack_type.select == SELECT_EQUAL_NONEMPTY) {
|
||||
if (i.total_weight > 0) r -= item->weight;
|
||||
} else {
|
||||
r -= item->weight;
|
||||
}
|
||||
// have we reached the item we were looking for?
|
||||
if (r < 0) {
|
||||
i.request_copy(item->amount);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : PackGenerator
|
||||
|
||||
void PackGenerator::reset(const SetP& set, int seed) {
|
||||
this->set = set;
|
||||
gen.seed((unsigned)seed);
|
||||
max_depth = 0;
|
||||
instances.clear();
|
||||
this->set = set;
|
||||
gen.seed((unsigned)seed);
|
||||
max_depth = 0;
|
||||
instances.clear();
|
||||
}
|
||||
void PackGenerator::reset(int seed) {
|
||||
gen.seed((unsigned)seed);
|
||||
gen.seed((unsigned)seed);
|
||||
}
|
||||
|
||||
PackInstance& PackGenerator::get(const String& name) {
|
||||
assert(set);
|
||||
PackInstanceP& instance = instances[name];
|
||||
if (instance) {
|
||||
return *instance;
|
||||
} else {
|
||||
FOR_EACH_CONST(type, set->pack_types) {
|
||||
if (type->name == name) {
|
||||
instance = PackInstanceP(new PackInstance(*type,*this));
|
||||
max_depth = max(max_depth, instance->get_depth());
|
||||
return *instance;
|
||||
}
|
||||
}
|
||||
FOR_EACH_CONST(type, set->game->pack_types) {
|
||||
if (type->name == name) {
|
||||
instance = PackInstanceP(new PackInstance(*type,*this));
|
||||
max_depth = max(max_depth, instance->get_depth());
|
||||
return *instance;
|
||||
}
|
||||
}
|
||||
throw Error(_ERROR_1_("pack type not found",name));
|
||||
}
|
||||
assert(set);
|
||||
PackInstanceP& instance = instances[name];
|
||||
if (instance) {
|
||||
return *instance;
|
||||
} else {
|
||||
FOR_EACH_CONST(type, set->pack_types) {
|
||||
if (type->name == name) {
|
||||
instance = PackInstanceP(new PackInstance(*type,*this));
|
||||
max_depth = max(max_depth, instance->get_depth());
|
||||
return *instance;
|
||||
}
|
||||
}
|
||||
FOR_EACH_CONST(type, set->game->pack_types) {
|
||||
if (type->name == name) {
|
||||
instance = PackInstanceP(new PackInstance(*type,*this));
|
||||
max_depth = max(max_depth, instance->get_depth());
|
||||
return *instance;
|
||||
}
|
||||
}
|
||||
throw Error(_ERROR_1_("pack type not found",name));
|
||||
}
|
||||
}
|
||||
PackInstance& PackGenerator::get(const PackTypeP& type) {
|
||||
return get(type->name);
|
||||
return get(type->name);
|
||||
}
|
||||
|
||||
void PackGenerator::generate(vector<CardP>& out) {
|
||||
if (!set) return;
|
||||
// We generate from depth max_depth to 0
|
||||
// instances can refer to other instances of lower depth, and generate
|
||||
// can change the number of copies of those lower depth instances
|
||||
for (int depth = max_depth ; depth >= 0 ; --depth) {
|
||||
// in game file order
|
||||
FOR_EACH_CONST(type, set->game->pack_types) {
|
||||
PackInstance& i = get(type);
|
||||
if (i.get_depth() == depth) {
|
||||
i.generate(&out);
|
||||
}
|
||||
}
|
||||
// ...and then set file order
|
||||
FOR_EACH_CONST(type, set->pack_types) {
|
||||
PackInstance& i = get(type);
|
||||
if (i.get_depth() == depth) {
|
||||
i.generate(&out);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!set) return;
|
||||
// We generate from depth max_depth to 0
|
||||
// instances can refer to other instances of lower depth, and generate
|
||||
// can change the number of copies of those lower depth instances
|
||||
for (int depth = max_depth ; depth >= 0 ; --depth) {
|
||||
// in game file order
|
||||
FOR_EACH_CONST(type, set->game->pack_types) {
|
||||
PackInstance& i = get(type);
|
||||
if (i.get_depth() == depth) {
|
||||
i.generate(&out);
|
||||
}
|
||||
}
|
||||
// ...and then set file order
|
||||
FOR_EACH_CONST(type, set->pack_types) {
|
||||
PackInstance& i = get(type);
|
||||
if (i.get_depth() == depth) {
|
||||
i.generate(&out);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PackGenerator::update_card_counts() {
|
||||
if (!set) return;
|
||||
// update card_counts by using generate()
|
||||
for (int depth = max_depth ; depth >= 0 ; --depth) {
|
||||
FOR_EACH_CONST(i,instances) {
|
||||
if (i.second->get_depth() == depth) {
|
||||
i.second->generate(nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!set) return;
|
||||
// update card_counts by using generate()
|
||||
for (int depth = max_depth ; depth >= 0 ; --depth) {
|
||||
FOR_EACH_CONST(i,instances) {
|
||||
if (i.second->get_depth() == depth) {
|
||||
i.second->generate(nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+88
-88
@@ -26,57 +26,57 @@ class PackGenerator;
|
||||
// ----------------------------------------------------------------------------- : PackType
|
||||
|
||||
enum PackSelectType
|
||||
{ SELECT_AUTO
|
||||
, SELECT_ALL
|
||||
, SELECT_NO_REPLACE
|
||||
, SELECT_REPLACE
|
||||
, SELECT_PROPORTIONAL
|
||||
, SELECT_NONEMPTY
|
||||
, SELECT_EQUAL
|
||||
, SELECT_EQUAL_PROPORTIONAL
|
||||
, SELECT_EQUAL_NONEMPTY
|
||||
, SELECT_FIRST
|
||||
{ SELECT_AUTO
|
||||
, SELECT_ALL
|
||||
, SELECT_NO_REPLACE
|
||||
, SELECT_REPLACE
|
||||
, SELECT_PROPORTIONAL
|
||||
, SELECT_NONEMPTY
|
||||
, SELECT_EQUAL
|
||||
, SELECT_EQUAL_PROPORTIONAL
|
||||
, SELECT_EQUAL_NONEMPTY
|
||||
, SELECT_FIRST
|
||||
};
|
||||
|
||||
/// A card pack description for playtesting
|
||||
class PackType : public IntrusivePtrBase<PackType> {
|
||||
public:
|
||||
PackType();
|
||||
|
||||
String name; ///< Name of this pack
|
||||
Scriptable<bool> enabled; ///< Is this pack enabled?
|
||||
tribool selectable; ///< Is this pack listed in the UI?
|
||||
tribool summary; ///< Should the total be listed for this type?
|
||||
PackSelectType select; ///< What cards/items to select
|
||||
OptionalScript filter; ///< Filter to select this type of cards
|
||||
vector<PackItemP> items; ///< Subpacks in this pack
|
||||
|
||||
/// Update scripts, returns true if there is a change
|
||||
bool update(Context& ctx);
|
||||
|
||||
PackType();
|
||||
|
||||
String name; ///< Name of this pack
|
||||
Scriptable<bool> enabled; ///< Is this pack enabled?
|
||||
tribool selectable; ///< Is this pack listed in the UI?
|
||||
tribool summary; ///< Should the total be listed for this type?
|
||||
PackSelectType select; ///< What cards/items to select
|
||||
OptionalScript filter; ///< Filter to select this type of cards
|
||||
vector<PackItemP> items; ///< Subpacks in this pack
|
||||
|
||||
/// Update scripts, returns true if there is a change
|
||||
bool update(Context& ctx);
|
||||
|
||||
private:
|
||||
DECLARE_REFLECTION();
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
/// An item in a PackType
|
||||
class PackItem : public IntrusivePtrBase<PackItem> {
|
||||
public:
|
||||
PackItem();
|
||||
PackItem(const String& name, int amount);
|
||||
|
||||
String name; ///< Name of the pack to select cards from
|
||||
Scriptable<int> amount; ///< Number of cards of this type
|
||||
Scriptable<double> weight; ///< Relative probability of picking this item
|
||||
|
||||
/// Update scripts, returns true if there is a change
|
||||
bool update(Context& ctx);
|
||||
|
||||
PackItem();
|
||||
PackItem(const String& name, int amount);
|
||||
|
||||
String name; ///< Name of the pack to select cards from
|
||||
Scriptable<int> amount; ///< Number of cards of this type
|
||||
Scriptable<double> weight; ///< Relative probability of picking this item
|
||||
|
||||
/// Update scripts, returns true if there is a change
|
||||
bool update(Context& ctx);
|
||||
|
||||
private:
|
||||
DECLARE_REFLECTION();
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
inline String type_name(const PackType&) {
|
||||
return _TYPE_("pack");
|
||||
return _TYPE_("pack");
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Generating / counting
|
||||
@@ -85,64 +85,64 @@ inline String type_name(const PackType&) {
|
||||
// i.e. we now know the actual cards
|
||||
class PackInstance : public IntrusivePtrBase<PackInstance> {
|
||||
public:
|
||||
PackInstance(const PackType& pack_type, PackGenerator& parent);
|
||||
|
||||
/// Expect to pick this many copies from this pack, updates expected_copies
|
||||
void expect_copy(double copies = 1);
|
||||
/// Request some copies of this pack
|
||||
void request_copy(size_t copies = 1);
|
||||
|
||||
/// Generate cards if depth == at_depth
|
||||
/** Some cards are (optionally) added to out and card_copies
|
||||
* And also the copies of referenced items might be incremented
|
||||
*
|
||||
* Resets the count of this instance to 0 */
|
||||
void generate(vector<CardP>* out);
|
||||
|
||||
inline int get_depth() const { return depth; }
|
||||
inline bool has_cards() const { return !cards.empty(); }
|
||||
inline size_t get_card_copies() const { return card_copies; }
|
||||
inline double get_expected_copies() const { return expected_copies; }
|
||||
|
||||
PackInstance(const PackType& pack_type, PackGenerator& parent);
|
||||
|
||||
/// Expect to pick this many copies from this pack, updates expected_copies
|
||||
void expect_copy(double copies = 1);
|
||||
/// Request some copies of this pack
|
||||
void request_copy(size_t copies = 1);
|
||||
|
||||
/// Generate cards if depth == at_depth
|
||||
/** Some cards are (optionally) added to out and card_copies
|
||||
* And also the copies of referenced items might be incremented
|
||||
*
|
||||
* Resets the count of this instance to 0 */
|
||||
void generate(vector<CardP>* out);
|
||||
|
||||
inline int get_depth() const { return depth; }
|
||||
inline bool has_cards() const { return !cards.empty(); }
|
||||
inline size_t get_card_copies() const { return card_copies; }
|
||||
inline double get_expected_copies() const { return expected_copies; }
|
||||
|
||||
private:
|
||||
const PackType& pack_type;
|
||||
PackGenerator& parent;
|
||||
int depth; //< 0 = no items, otherwise 1+max depth of items refered to
|
||||
vector<CardP> cards; //< All cards that pass the filter
|
||||
double total_weight; //< Sum of item and card weights
|
||||
size_t requested_copies; //< The requested number of copies of this pack
|
||||
size_t card_copies; //< The number of cards that were chosen to come from this pack
|
||||
double expected_copies;
|
||||
|
||||
/// Generate some copies of all cards and items
|
||||
void generate_all(vector<CardP>* out, size_t copies);
|
||||
/// Generate one card/item chosen at random (using the select type)
|
||||
void generate_one_random(vector<CardP>* out);
|
||||
const PackType& pack_type;
|
||||
PackGenerator& parent;
|
||||
int depth; //< 0 = no items, otherwise 1+max depth of items refered to
|
||||
vector<CardP> cards; //< All cards that pass the filter
|
||||
double total_weight; //< Sum of item and card weights
|
||||
size_t requested_copies; //< The requested number of copies of this pack
|
||||
size_t card_copies; //< The number of cards that were chosen to come from this pack
|
||||
double expected_copies;
|
||||
|
||||
/// Generate some copies of all cards and items
|
||||
void generate_all(vector<CardP>* out, size_t copies);
|
||||
/// Generate one card/item chosen at random (using the select type)
|
||||
void generate_one_random(vector<CardP>* out);
|
||||
};
|
||||
|
||||
class PackGenerator {
|
||||
public:
|
||||
/// Reset the generator, possibly switching the set or reseeding
|
||||
void reset(const SetP& set, int seed);
|
||||
/// Reset the generator, but not the set
|
||||
void reset(int seed);
|
||||
|
||||
/// Find the PackInstance for the PackType with the given name
|
||||
PackInstance& get(const String& name);
|
||||
PackInstance& get(const PackTypeP& type);
|
||||
|
||||
/// Generate all cards, resets copies
|
||||
void generate(vector<CardP>& out);
|
||||
/// Update all card_copies counters, resets copies
|
||||
void update_card_counts();
|
||||
|
||||
// only for PackInstance
|
||||
SetP set; ///< The set
|
||||
boost::mt19937 gen; ///< Random generator
|
||||
/// Reset the generator, possibly switching the set or reseeding
|
||||
void reset(const SetP& set, int seed);
|
||||
/// Reset the generator, but not the set
|
||||
void reset(int seed);
|
||||
|
||||
/// Find the PackInstance for the PackType with the given name
|
||||
PackInstance& get(const String& name);
|
||||
PackInstance& get(const PackTypeP& type);
|
||||
|
||||
/// Generate all cards, resets copies
|
||||
void generate(vector<CardP>& out);
|
||||
/// Update all card_copies counters, resets copies
|
||||
void update_card_counts();
|
||||
|
||||
// only for PackInstance
|
||||
SetP set; ///< The set
|
||||
boost::mt19937 gen; ///< Random generator
|
||||
private:
|
||||
/// Details for each PackType
|
||||
map<String,PackInstanceP> instances;
|
||||
int max_depth;
|
||||
/// Details for each PackType
|
||||
map<String,PackInstanceP> instances;
|
||||
int max_depth;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
+194
-194
@@ -29,98 +29,98 @@ DECLARE_TYPEOF_NO_REV(IndexMap<FieldP COMMA ValueP>);
|
||||
// ----------------------------------------------------------------------------- : Set
|
||||
|
||||
Set::Set()
|
||||
: vcs (intrusive(new VCS()))
|
||||
, script_manager(new SetScriptManager(*this))
|
||||
: vcs (intrusive(new VCS()))
|
||||
, script_manager(new SetScriptManager(*this))
|
||||
{}
|
||||
|
||||
Set::Set(const GameP& game)
|
||||
: game(game)
|
||||
, vcs (intrusive(new VCS()))
|
||||
, script_manager(new SetScriptManager(*this))
|
||||
: game(game)
|
||||
, vcs (intrusive(new VCS()))
|
||||
, script_manager(new SetScriptManager(*this))
|
||||
{
|
||||
data.init(game->set_fields);
|
||||
data.init(game->set_fields);
|
||||
}
|
||||
|
||||
Set::Set(const StyleSheetP& stylesheet)
|
||||
: game(stylesheet->game)
|
||||
, stylesheet(stylesheet)
|
||||
, vcs (intrusive(new VCS()))
|
||||
, script_manager(new SetScriptManager(*this))
|
||||
: game(stylesheet->game)
|
||||
, stylesheet(stylesheet)
|
||||
, vcs (intrusive(new VCS()))
|
||||
, script_manager(new SetScriptManager(*this))
|
||||
{
|
||||
data.init(game->set_fields);
|
||||
data.init(game->set_fields);
|
||||
}
|
||||
|
||||
Set::~Set() {}
|
||||
|
||||
|
||||
Context& Set::getContext() {
|
||||
assert(wxThread::IsMain());
|
||||
return script_manager->getContext(stylesheet);
|
||||
assert(wxThread::IsMain());
|
||||
return script_manager->getContext(stylesheet);
|
||||
}
|
||||
Context& Set::getContext(const CardP& card) {
|
||||
assert(wxThread::IsMain());
|
||||
return script_manager->getContext(card);
|
||||
assert(wxThread::IsMain());
|
||||
return script_manager->getContext(card);
|
||||
}
|
||||
void Set::updateStyles(const CardP& card, bool only_content_dependent) {
|
||||
script_manager->updateStyles(card, only_content_dependent);
|
||||
script_manager->updateStyles(card, only_content_dependent);
|
||||
}
|
||||
void Set::updateDelayed() {
|
||||
script_manager->updateDelayed();
|
||||
script_manager->updateDelayed();
|
||||
}
|
||||
|
||||
Context& Set::getContextForThumbnails() {
|
||||
assert(!wxThread::IsMain());
|
||||
if (!thumbnail_script_context) {
|
||||
thumbnail_script_context.reset(new SetScriptContext(*this));
|
||||
}
|
||||
return thumbnail_script_context->getContext(stylesheet);
|
||||
assert(!wxThread::IsMain());
|
||||
if (!thumbnail_script_context) {
|
||||
thumbnail_script_context.reset(new SetScriptContext(*this));
|
||||
}
|
||||
return thumbnail_script_context->getContext(stylesheet);
|
||||
}
|
||||
Context& Set::getContextForThumbnails(const CardP& card) {
|
||||
assert(!wxThread::IsMain());
|
||||
if (!thumbnail_script_context) {
|
||||
thumbnail_script_context.reset(new SetScriptContext(*this));
|
||||
}
|
||||
return thumbnail_script_context->getContext(card);
|
||||
assert(!wxThread::IsMain());
|
||||
if (!thumbnail_script_context) {
|
||||
thumbnail_script_context.reset(new SetScriptContext(*this));
|
||||
}
|
||||
return thumbnail_script_context->getContext(card);
|
||||
}
|
||||
Context& Set::getContextForThumbnails(const StyleSheetP& stylesheet) {
|
||||
assert(!wxThread::IsMain());
|
||||
if (!thumbnail_script_context) {
|
||||
thumbnail_script_context.reset(new SetScriptContext(*this));
|
||||
}
|
||||
return thumbnail_script_context->getContext(stylesheet);
|
||||
assert(!wxThread::IsMain());
|
||||
if (!thumbnail_script_context) {
|
||||
thumbnail_script_context.reset(new SetScriptContext(*this));
|
||||
}
|
||||
return thumbnail_script_context->getContext(stylesheet);
|
||||
}
|
||||
|
||||
const StyleSheet& Set::stylesheetFor(const CardP& card) {
|
||||
if (card && card->stylesheet) return *card->stylesheet;
|
||||
else return *stylesheet;
|
||||
if (card && card->stylesheet) return *card->stylesheet;
|
||||
else return *stylesheet;
|
||||
}
|
||||
StyleSheetP Set::stylesheetForP(const CardP& card) {
|
||||
if (card && card->stylesheet) return card->stylesheet;
|
||||
else return stylesheet;
|
||||
if (card && card->stylesheet) return card->stylesheet;
|
||||
else return stylesheet;
|
||||
}
|
||||
|
||||
IndexMap<FieldP, ValueP>& Set::stylingDataFor(const StyleSheet& stylesheet) {
|
||||
return styling_data.get(stylesheet.name(), stylesheet.styling_fields);
|
||||
return styling_data.get(stylesheet.name(), stylesheet.styling_fields);
|
||||
}
|
||||
IndexMap<FieldP, ValueP>& Set::stylingDataFor(const CardP& card) {
|
||||
if (card && card->has_styling) return card->styling_data;
|
||||
else return stylingDataFor(stylesheetFor(card));
|
||||
if (card && card->has_styling) return card->styling_data;
|
||||
else return stylingDataFor(stylesheetFor(card));
|
||||
}
|
||||
|
||||
String Set::identification() const {
|
||||
// an identifying field
|
||||
FOR_EACH_CONST(v, data) {
|
||||
if (v->fieldP->identifying) {
|
||||
return v->toString();
|
||||
}
|
||||
}
|
||||
// otherwise the first non-information field
|
||||
FOR_EACH_CONST(v, data) {
|
||||
if (!dynamic_pointer_cast<InfoValue>(v)) {
|
||||
return v->toString();
|
||||
}
|
||||
}
|
||||
return wxEmptyString;
|
||||
// an identifying field
|
||||
FOR_EACH_CONST(v, data) {
|
||||
if (v->fieldP->identifying) {
|
||||
return v->toString();
|
||||
}
|
||||
}
|
||||
// otherwise the first non-information field
|
||||
FOR_EACH_CONST(v, data) {
|
||||
if (!dynamic_pointer_cast<InfoValue>(v)) {
|
||||
return v->toString();
|
||||
}
|
||||
}
|
||||
return wxEmptyString;
|
||||
}
|
||||
|
||||
|
||||
@@ -129,177 +129,177 @@ Version Set::fileVersion() const { return file_version_set; }
|
||||
|
||||
// fix values for versions < 0.2.7
|
||||
void fix_value_207(const ValueP& value) {
|
||||
if (TextValue* v = dynamic_cast<TextValue*>(value.get())) {
|
||||
// text value -> fix it
|
||||
v->value.assignDontChangeDefault( // don't change defaultness
|
||||
fix_old_tags(v->value()) // remove tags
|
||||
);
|
||||
}
|
||||
if (TextValue* v = dynamic_cast<TextValue*>(value.get())) {
|
||||
// text value -> fix it
|
||||
v->value.assignDontChangeDefault( // don't change defaultness
|
||||
fix_old_tags(v->value()) // remove tags
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void Set::validate(Version file_app_version) {
|
||||
Packaged::validate(file_app_version);
|
||||
// are the
|
||||
if (!game) {
|
||||
throw Error(_ERROR_1_("no game specified",_TYPE_("set")));
|
||||
}
|
||||
if (!stylesheet) {
|
||||
// TODO : Allow user to select a different style
|
||||
throw Error(_ERROR_("no stylesheet specified for the set"));
|
||||
}
|
||||
if (stylesheet->game != game) {
|
||||
throw Error(_ERROR_("stylesheet and set refer to different game"));
|
||||
}
|
||||
Packaged::validate(file_app_version);
|
||||
// are the
|
||||
if (!game) {
|
||||
throw Error(_ERROR_1_("no game specified",_TYPE_("set")));
|
||||
}
|
||||
if (!stylesheet) {
|
||||
// TODO : Allow user to select a different style
|
||||
throw Error(_ERROR_("no stylesheet specified for the set"));
|
||||
}
|
||||
if (stylesheet->game != game) {
|
||||
throw Error(_ERROR_("stylesheet and set refer to different game"));
|
||||
}
|
||||
|
||||
// This is our chance to fix version incompatabilities
|
||||
if (file_app_version < 207) {
|
||||
// Since 0.2.7 we use </tag> style close tags, in older versions it was </>
|
||||
// Walk over all fields and fix...
|
||||
FOR_EACH(c, cards) {
|
||||
FOR_EACH(v, c->data) fix_value_207(v);
|
||||
}
|
||||
FOR_EACH(v, data) fix_value_207(v);
|
||||
/* FOR_EACH(s, styleData) {
|
||||
FOR_EACH(v, s.second->data) fix_value_207(v);
|
||||
}
|
||||
*/ }
|
||||
// we want at least one card
|
||||
if (cards.empty()) cards.push_back(intrusive(new Card(*game)));
|
||||
// update scripts
|
||||
script_manager->updateAll();
|
||||
// This is our chance to fix version incompatabilities
|
||||
if (file_app_version < 207) {
|
||||
// Since 0.2.7 we use </tag> style close tags, in older versions it was </>
|
||||
// Walk over all fields and fix...
|
||||
FOR_EACH(c, cards) {
|
||||
FOR_EACH(v, c->data) fix_value_207(v);
|
||||
}
|
||||
FOR_EACH(v, data) fix_value_207(v);
|
||||
/* FOR_EACH(s, styleData) {
|
||||
FOR_EACH(v, s.second->data) fix_value_207(v);
|
||||
}
|
||||
*/ }
|
||||
// we want at least one card
|
||||
if (cards.empty()) cards.push_back(intrusive(new Card(*game)));
|
||||
// update scripts
|
||||
script_manager->updateAll();
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION(Set) {
|
||||
REFLECT_ALIAS(300, "style", "stylesheet"); // < 0.3.0 used style instead of stylesheet
|
||||
REFLECT_ALIAS(300, "extra set info", "styling");
|
||||
REFLECT(game);
|
||||
if (game) {
|
||||
REFLECT_IF_READING {
|
||||
data.init(game->set_fields);
|
||||
}
|
||||
WITH_DYNAMIC_ARG(game_for_reading, game.get());
|
||||
REFLECT(stylesheet);
|
||||
WITH_DYNAMIC_ARG(stylesheet_for_reading, stylesheet.get());
|
||||
REFLECT_N("set_info", data);
|
||||
if (stylesheet) {
|
||||
REFLECT_N("styling", styling_data);
|
||||
}
|
||||
// Experimental: save each card to a different file
|
||||
reflect_cards(tag);
|
||||
REFLECT(keywords);
|
||||
REFLECT(pack_types);
|
||||
}
|
||||
reflect_set_info_get_member(tag,data);
|
||||
REFLECT_NO_SCRIPT_N("version control", vcs);
|
||||
REFLECT(apprentice_code);
|
||||
REFLECT_ALIAS(300, "style", "stylesheet"); // < 0.3.0 used style instead of stylesheet
|
||||
REFLECT_ALIAS(300, "extra set info", "styling");
|
||||
REFLECT(game);
|
||||
if (game) {
|
||||
REFLECT_IF_READING {
|
||||
data.init(game->set_fields);
|
||||
}
|
||||
WITH_DYNAMIC_ARG(game_for_reading, game.get());
|
||||
REFLECT(stylesheet);
|
||||
WITH_DYNAMIC_ARG(stylesheet_for_reading, stylesheet.get());
|
||||
REFLECT_N("set_info", data);
|
||||
if (stylesheet) {
|
||||
REFLECT_N("styling", styling_data);
|
||||
}
|
||||
// Experimental: save each card to a different file
|
||||
reflect_cards(tag);
|
||||
REFLECT(keywords);
|
||||
REFLECT(pack_types);
|
||||
}
|
||||
reflect_set_info_get_member(tag,data);
|
||||
REFLECT_NO_SCRIPT_N("version control", vcs);
|
||||
REFLECT(apprentice_code);
|
||||
}
|
||||
|
||||
// TODO: make this a more generic function to be used elsewhere
|
||||
template <typename Tag>
|
||||
void Set::reflect_cards (Tag& tag) {
|
||||
REFLECT(cards);
|
||||
REFLECT(cards);
|
||||
}
|
||||
|
||||
template <>
|
||||
void Set::reflect_cards<Writer> (Writer& tag) {
|
||||
// When writing to a directory, we write each card in a separate file.
|
||||
// We don't do this in zipfiles because it leads to bloat.
|
||||
if (isZipfile()) {
|
||||
REFLECT(cards);
|
||||
} else {
|
||||
set<String> used;
|
||||
FOR_EACH(card, cards) {
|
||||
// pick a unique filename for this card
|
||||
// can't use Package::newFileName, because then we get conflicts with the previous save of the same card
|
||||
String filename = _("card ") + normalize_internal_filename(clean_filename(card->identification()));
|
||||
String full_name = filename;
|
||||
int i = 0;
|
||||
// When writing to a directory, we write each card in a separate file.
|
||||
// We don't do this in zipfiles because it leads to bloat.
|
||||
if (isZipfile()) {
|
||||
REFLECT(cards);
|
||||
} else {
|
||||
set<String> used;
|
||||
FOR_EACH(card, cards) {
|
||||
// pick a unique filename for this card
|
||||
// can't use Package::newFileName, because then we get conflicts with the previous save of the same card
|
||||
String filename = _("card ") + normalize_internal_filename(clean_filename(card->identification()));
|
||||
String full_name = filename;
|
||||
int i = 0;
|
||||
|
||||
while (used.find(full_name) != used.end()) {
|
||||
full_name = String(filename) << _(".") << ++i;
|
||||
}
|
||||
used.insert(full_name);
|
||||
while (used.find(full_name) != used.end()) {
|
||||
full_name = String(filename) << _(".") << ++i;
|
||||
}
|
||||
used.insert(full_name);
|
||||
|
||||
// writeFile won't quite work because we'd need
|
||||
// include file: card: filename
|
||||
// to do that
|
||||
Writer writer(openOut(full_name), app_version);
|
||||
writer.handle(_("card"), card);
|
||||
referenceFile(full_name);
|
||||
REFLECT_N("include file", full_name);
|
||||
}
|
||||
}
|
||||
// writeFile won't quite work because we'd need
|
||||
// include file: card: filename
|
||||
// to do that
|
||||
Writer writer(openOut(full_name), app_version);
|
||||
writer.handle(_("card"), card);
|
||||
referenceFile(full_name);
|
||||
REFLECT_N("include file", full_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Script utilities
|
||||
|
||||
ScriptValueP make_iterator(const Set& set) {
|
||||
return intrusive(new ScriptCollectionIterator<vector<CardP> >(&set.cards));
|
||||
return intrusive(new ScriptCollectionIterator<vector<CardP> >(&set.cards));
|
||||
}
|
||||
|
||||
void mark_dependency_member(const Set& set, const String& name, const Dependency& dep) {
|
||||
// is it the card list?
|
||||
if (name == _("cards")) {
|
||||
set.game->dependent_scripts_cards.add(dep);
|
||||
return;
|
||||
}
|
||||
// is it the keywords?
|
||||
if (name == _("keywords")) {
|
||||
set.game->dependent_scripts_keywords.add(dep);
|
||||
return;
|
||||
}
|
||||
// is it in the set data?
|
||||
mark_dependency_member(set.data, name, dep);
|
||||
// is it the card list?
|
||||
if (name == _("cards")) {
|
||||
set.game->dependent_scripts_cards.add(dep);
|
||||
return;
|
||||
}
|
||||
// is it the keywords?
|
||||
if (name == _("keywords")) {
|
||||
set.game->dependent_scripts_keywords.add(dep);
|
||||
return;
|
||||
}
|
||||
// is it in the set data?
|
||||
mark_dependency_member(set.data, name, dep);
|
||||
}
|
||||
|
||||
// in scripts, set.something is read from the set_info
|
||||
template <typename Tag>
|
||||
void reflect_set_info_get_member(Tag& tag, const IndexMap<FieldP, ValueP>& data) {}
|
||||
void reflect_set_info_get_member(GetMember& tag, const IndexMap<FieldP, ValueP>& data) {
|
||||
REFLECT_NAMELESS(data);
|
||||
REFLECT_NAMELESS(data);
|
||||
}
|
||||
|
||||
int Set::positionOfCard(const CardP& card, const ScriptValueP& order_by, const ScriptValueP& filter) {
|
||||
// TODO : Lock the map?
|
||||
assert(order_by);
|
||||
OrderCacheP& order = order_cache[make_pair(order_by,filter)];
|
||||
if (!order) {
|
||||
// 1. make a list of the order value for each card
|
||||
vector<String> values; values.reserve(cards.size());
|
||||
vector<int> keep; if(filter) keep.reserve(cards.size());
|
||||
FOR_EACH_CONST(c, cards) {
|
||||
Context& ctx = getContext(c);
|
||||
values.push_back(*order_by->eval(ctx));
|
||||
if (filter) {
|
||||
keep.push_back((bool)*filter->eval(ctx));
|
||||
}
|
||||
}
|
||||
#if USE_SCRIPT_PROFILING
|
||||
Timer t;
|
||||
Profiler prof(t, order_by.get(), _("init order cache"));
|
||||
#endif
|
||||
// 3. initialize order cache
|
||||
order = intrusive(new OrderCache<CardP>(cards, values, filter ? &keep : nullptr));
|
||||
}
|
||||
return order->find(card);
|
||||
// TODO : Lock the map?
|
||||
assert(order_by);
|
||||
OrderCacheP& order = order_cache[make_pair(order_by,filter)];
|
||||
if (!order) {
|
||||
// 1. make a list of the order value for each card
|
||||
vector<String> values; values.reserve(cards.size());
|
||||
vector<int> keep; if(filter) keep.reserve(cards.size());
|
||||
FOR_EACH_CONST(c, cards) {
|
||||
Context& ctx = getContext(c);
|
||||
values.push_back(*order_by->eval(ctx));
|
||||
if (filter) {
|
||||
keep.push_back((bool)*filter->eval(ctx));
|
||||
}
|
||||
}
|
||||
#if USE_SCRIPT_PROFILING
|
||||
Timer t;
|
||||
Profiler prof(t, order_by.get(), _("init order cache"));
|
||||
#endif
|
||||
// 3. initialize order cache
|
||||
order = intrusive(new OrderCache<CardP>(cards, values, filter ? &keep : nullptr));
|
||||
}
|
||||
return order->find(card);
|
||||
}
|
||||
int Set::numberOfCards(const ScriptValueP& filter) {
|
||||
if (!filter) return (int)cards.size();
|
||||
map<ScriptValueP,int>::const_iterator it = filter_cache.find(filter);
|
||||
if (it !=filter_cache.end()) {
|
||||
return it->second;
|
||||
} else {
|
||||
int n = 0;
|
||||
FOR_EACH_CONST(c, cards) {
|
||||
if (*filter->eval(getContext(c))) ++n;
|
||||
}
|
||||
filter_cache.insert(make_pair(filter,n));
|
||||
return n;
|
||||
}
|
||||
if (!filter) return (int)cards.size();
|
||||
map<ScriptValueP,int>::const_iterator it = filter_cache.find(filter);
|
||||
if (it !=filter_cache.end()) {
|
||||
return it->second;
|
||||
} else {
|
||||
int n = 0;
|
||||
FOR_EACH_CONST(c, cards) {
|
||||
if (*filter->eval(getContext(c))) ++n;
|
||||
}
|
||||
filter_cache.insert(make_pair(filter,n));
|
||||
return n;
|
||||
}
|
||||
}
|
||||
void Set::clearOrderCache() {
|
||||
order_cache.clear();
|
||||
filter_cache.clear();
|
||||
order_cache.clear();
|
||||
filter_cache.clear();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : SetView
|
||||
@@ -307,17 +307,17 @@ void Set::clearOrderCache() {
|
||||
SetView::SetView() {}
|
||||
|
||||
SetView::~SetView() {
|
||||
if (set) set->actions.removeListener(this);
|
||||
if (set) set->actions.removeListener(this);
|
||||
}
|
||||
|
||||
void SetView::setSet(const SetP& newSet) {
|
||||
// no longer listening to old set
|
||||
if (set) {
|
||||
onBeforeChangeSet();
|
||||
set->actions.removeListener(this);
|
||||
}
|
||||
set = newSet;
|
||||
// start listening to new set
|
||||
if (set) set->actions.addListener(this);
|
||||
onChangeSet();
|
||||
// no longer listening to old set
|
||||
if (set) {
|
||||
onBeforeChangeSet();
|
||||
set->actions.removeListener(this);
|
||||
}
|
||||
set = newSet;
|
||||
// start listening to new set
|
||||
if (set) set->actions.addListener(this);
|
||||
onChangeSet();
|
||||
}
|
||||
|
||||
+111
-111
@@ -39,112 +39,112 @@ typedef intrusive_ptr<OrderCache<CardP> > OrderCacheP;
|
||||
/// A set of cards
|
||||
class Set : public Packaged {
|
||||
public:
|
||||
/// Create a set, the set should be open()ed later
|
||||
Set();
|
||||
/// Create a set using the given game
|
||||
Set(const GameP& game);
|
||||
/// Create a set using the given stylesheet, and its game
|
||||
Set(const StyleSheetP& stylesheet);
|
||||
~Set();
|
||||
/// Create a set, the set should be open()ed later
|
||||
Set();
|
||||
/// Create a set using the given game
|
||||
Set(const GameP& game);
|
||||
/// Create a set using the given stylesheet, and its game
|
||||
Set(const StyleSheetP& stylesheet);
|
||||
~Set();
|
||||
|
||||
GameP game; ///< The game this set uses
|
||||
StyleSheetP stylesheet; ///< The default stylesheet
|
||||
/// The values on the fields of the set
|
||||
/** The indices should correspond to the set_fields in the Game */
|
||||
IndexMap<FieldP, ValueP> data;
|
||||
/// Extra values for specitic stylesheets, indexed by stylesheet name
|
||||
DelayedIndexMaps<FieldP,ValueP> styling_data;
|
||||
vector<CardP> cards; ///< The cards in the set
|
||||
vector<KeywordP> keywords; ///< Additional keywords used in this set
|
||||
vector<PackTypeP> pack_types; ///< Additional/replacement pack types
|
||||
String apprentice_code; ///< Code to use for apprentice (Magic only)
|
||||
GameP game; ///< The game this set uses
|
||||
StyleSheetP stylesheet; ///< The default stylesheet
|
||||
/// The values on the fields of the set
|
||||
/** The indices should correspond to the set_fields in the Game */
|
||||
IndexMap<FieldP, ValueP> data;
|
||||
/// Extra values for specitic stylesheets, indexed by stylesheet name
|
||||
DelayedIndexMaps<FieldP,ValueP> styling_data;
|
||||
vector<CardP> cards; ///< The cards in the set
|
||||
vector<KeywordP> keywords; ///< Additional keywords used in this set
|
||||
vector<PackTypeP> pack_types; ///< Additional/replacement pack types
|
||||
String apprentice_code; ///< Code to use for apprentice (Magic only)
|
||||
|
||||
ActionStack actions; ///< Actions performed on this set and the cards in it
|
||||
KeywordDatabase keyword_db; ///< Database for matching keywords, must be cleared when keywords change
|
||||
VCSP vcs; ///< The version control system to use
|
||||
|
||||
/// A context for performing scripts
|
||||
/** Should only be used from the main thread! */
|
||||
Context& getContext();
|
||||
/// A context for performing scripts on a particular card
|
||||
/** Should only be used from the main thread! */
|
||||
Context& getContext(const CardP& card);
|
||||
/// Update styles and extra_card_fields for a card
|
||||
void updateStyles(const CardP& card, bool only_content_dependent);
|
||||
/// Update scripts that were delayed
|
||||
void updateDelayed();
|
||||
/// A context for performing scripts
|
||||
/** Should only be used from the thumbnail thread! */
|
||||
Context& getContextForThumbnails();
|
||||
/// A context for performing scripts on a particular card
|
||||
/** Should only be used from the thumbnail thread! */
|
||||
Context& getContextForThumbnails(const CardP& card);
|
||||
/// A context for performing scripts on a particular stylesheet
|
||||
/** Should only be used from the thumbnail thread! */
|
||||
Context& getContextForThumbnails(const StyleSheetP& stylesheet);
|
||||
|
||||
/// Stylesheet to use for a particular card
|
||||
/** card may be null */
|
||||
const StyleSheet& stylesheetFor (const CardP& card);
|
||||
StyleSheetP stylesheetForP(const CardP& card);
|
||||
|
||||
/// Styling information for a particular stylesheet
|
||||
IndexMap<FieldP, ValueP>& stylingDataFor(const StyleSheet&);
|
||||
/// Styling information for a particular card
|
||||
IndexMap<FieldP, ValueP>& stylingDataFor(const CardP& card);
|
||||
|
||||
/// Get the identification of this set, an identification is something like a name, title, etc.
|
||||
/** May return "" */
|
||||
String identification() const;
|
||||
|
||||
/// Find a value in the data by name and type
|
||||
template <typename T> T& value(const String& name) {
|
||||
for(IndexMap<FieldP, ValueP>::iterator it = data.begin() ; it != data.end() ; ++it) {
|
||||
if ((*it)->fieldP->name == name) {
|
||||
T* ret = dynamic_cast<T*>(it->get());
|
||||
if (!ret) throw InternalError(_("Set field with name '")+name+_("' doesn't have the right type"));
|
||||
return *ret;
|
||||
}
|
||||
}
|
||||
throw InternalError(_("Expected a set field with name '")+name+_("'"));
|
||||
}
|
||||
|
||||
/// Find the position of a card in this set, when the card list is sorted using the given cirterium
|
||||
int positionOfCard(const CardP& card, const ScriptValueP& order_by, const ScriptValueP& filter);
|
||||
/// Find the number of cards that match the given filter
|
||||
int numberOfCards(const ScriptValueP& filter);
|
||||
/// Clear the order_cache used by positionOfCard
|
||||
void clearOrderCache();
|
||||
|
||||
virtual String typeName() const;
|
||||
Version fileVersion() const;
|
||||
/// Validate that the set is correctly loaded
|
||||
virtual void validate(Version = app_version);
|
||||
|
||||
ActionStack actions; ///< Actions performed on this set and the cards in it
|
||||
KeywordDatabase keyword_db; ///< Database for matching keywords, must be cleared when keywords change
|
||||
VCSP vcs; ///< The version control system to use
|
||||
|
||||
/// A context for performing scripts
|
||||
/** Should only be used from the main thread! */
|
||||
Context& getContext();
|
||||
/// A context for performing scripts on a particular card
|
||||
/** Should only be used from the main thread! */
|
||||
Context& getContext(const CardP& card);
|
||||
/// Update styles and extra_card_fields for a card
|
||||
void updateStyles(const CardP& card, bool only_content_dependent);
|
||||
/// Update scripts that were delayed
|
||||
void updateDelayed();
|
||||
/// A context for performing scripts
|
||||
/** Should only be used from the thumbnail thread! */
|
||||
Context& getContextForThumbnails();
|
||||
/// A context for performing scripts on a particular card
|
||||
/** Should only be used from the thumbnail thread! */
|
||||
Context& getContextForThumbnails(const CardP& card);
|
||||
/// A context for performing scripts on a particular stylesheet
|
||||
/** Should only be used from the thumbnail thread! */
|
||||
Context& getContextForThumbnails(const StyleSheetP& stylesheet);
|
||||
|
||||
/// Stylesheet to use for a particular card
|
||||
/** card may be null */
|
||||
const StyleSheet& stylesheetFor (const CardP& card);
|
||||
StyleSheetP stylesheetForP(const CardP& card);
|
||||
|
||||
/// Styling information for a particular stylesheet
|
||||
IndexMap<FieldP, ValueP>& stylingDataFor(const StyleSheet&);
|
||||
/// Styling information for a particular card
|
||||
IndexMap<FieldP, ValueP>& stylingDataFor(const CardP& card);
|
||||
|
||||
/// Get the identification of this set, an identification is something like a name, title, etc.
|
||||
/** May return "" */
|
||||
String identification() const;
|
||||
|
||||
/// Find a value in the data by name and type
|
||||
template <typename T> T& value(const String& name) {
|
||||
for(IndexMap<FieldP, ValueP>::iterator it = data.begin() ; it != data.end() ; ++it) {
|
||||
if ((*it)->fieldP->name == name) {
|
||||
T* ret = dynamic_cast<T*>(it->get());
|
||||
if (!ret) throw InternalError(_("Set field with name '")+name+_("' doesn't have the right type"));
|
||||
return *ret;
|
||||
}
|
||||
}
|
||||
throw InternalError(_("Expected a set field with name '")+name+_("'"));
|
||||
}
|
||||
|
||||
/// Find the position of a card in this set, when the card list is sorted using the given cirterium
|
||||
int positionOfCard(const CardP& card, const ScriptValueP& order_by, const ScriptValueP& filter);
|
||||
/// Find the number of cards that match the given filter
|
||||
int numberOfCards(const ScriptValueP& filter);
|
||||
/// Clear the order_cache used by positionOfCard
|
||||
void clearOrderCache();
|
||||
|
||||
virtual String typeName() const;
|
||||
Version fileVersion() const;
|
||||
/// Validate that the set is correctly loaded
|
||||
virtual void validate(Version = app_version);
|
||||
|
||||
protected:
|
||||
virtual VCSP getVCS() {
|
||||
return vcs;
|
||||
}
|
||||
virtual VCSP getVCS() {
|
||||
return vcs;
|
||||
}
|
||||
|
||||
private:
|
||||
DECLARE_REFLECTION();
|
||||
template <typename Tag>
|
||||
void reflect_cards (Tag& tag);
|
||||
|
||||
/// Object for managing and executing scripts
|
||||
scoped_ptr<SetScriptManager> script_manager;
|
||||
/// Object for executing scripts from the thumbnail thread
|
||||
scoped_ptr<SetScriptContext> thumbnail_script_context;
|
||||
/// Cache of cards ordered by some criterion
|
||||
map<pair<ScriptValueP,ScriptValueP>,OrderCacheP> order_cache;
|
||||
map<ScriptValueP,int> filter_cache;
|
||||
DECLARE_REFLECTION();
|
||||
template <typename Tag>
|
||||
void reflect_cards (Tag& tag);
|
||||
|
||||
/// Object for managing and executing scripts
|
||||
scoped_ptr<SetScriptManager> script_manager;
|
||||
/// Object for executing scripts from the thumbnail thread
|
||||
scoped_ptr<SetScriptContext> thumbnail_script_context;
|
||||
/// Cache of cards ordered by some criterion
|
||||
map<pair<ScriptValueP,ScriptValueP>,OrderCacheP> order_cache;
|
||||
map<ScriptValueP,int> filter_cache;
|
||||
};
|
||||
|
||||
inline String type_name(const Set&) {
|
||||
return _TYPE_("set");
|
||||
return _TYPE_("set");
|
||||
}
|
||||
inline int item_count(const Set& set) {
|
||||
return (int)set.cards.size();
|
||||
return (int)set.cards.size();
|
||||
}
|
||||
ScriptValueP make_iterator(const Set& set);
|
||||
|
||||
@@ -157,22 +157,22 @@ void mark_dependency_member(const Set& set, const String& name, const Dependency
|
||||
*/
|
||||
class SetView : public ActionListener {
|
||||
public:
|
||||
SetView();
|
||||
~SetView();
|
||||
|
||||
/// Get the set that is currently being viewed
|
||||
//inline SetP getSet() const { return set; }
|
||||
/// Change the set that is being viewed
|
||||
void setSet(const SetP& set);
|
||||
|
||||
SetView();
|
||||
~SetView();
|
||||
|
||||
/// Get the set that is currently being viewed
|
||||
//inline SetP getSet() const { return set; }
|
||||
/// Change the set that is being viewed
|
||||
void setSet(const SetP& set);
|
||||
|
||||
protected:
|
||||
/// The set that is currently being viewed, should not be modified directly!
|
||||
SetP set;
|
||||
|
||||
/// Called when another set is being viewed (using setSet)
|
||||
virtual void onChangeSet() {}
|
||||
/// Called when just before another set is being viewed (using setSet)
|
||||
virtual void onBeforeChangeSet() {}
|
||||
/// The set that is currently being viewed, should not be modified directly!
|
||||
SetP set;
|
||||
|
||||
/// Called when another set is being viewed (using setSet)
|
||||
virtual void onChangeSet() {}
|
||||
/// Called when just before another set is being viewed (using setSet)
|
||||
virtual void onBeforeChangeSet() {}
|
||||
};
|
||||
|
||||
|
||||
|
||||
+194
-194
@@ -28,135 +28,135 @@ DECLARE_TYPEOF_COLLECTION(AutoReplaceP);
|
||||
// ----------------------------------------------------------------------------- : Extra types
|
||||
|
||||
IMPLEMENT_REFLECTION_ENUM(CheckUpdates) {
|
||||
VALUE_N("if connected", CHECK_IF_CONNECTED); //default
|
||||
VALUE_N("always", CHECK_ALWAYS);
|
||||
VALUE_N("never", CHECK_NEVER);
|
||||
VALUE_N("if connected", CHECK_IF_CONNECTED); //default
|
||||
VALUE_N("always", CHECK_ALWAYS);
|
||||
VALUE_N("never", CHECK_NEVER);
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION_ENUM(InstallType) {
|
||||
VALUE_N("default", INSTALL_DEFAULT); //default
|
||||
VALUE_N("local", INSTALL_LOCAL);
|
||||
VALUE_N("global", INSTALL_GLOBAL);
|
||||
VALUE_N("default", INSTALL_DEFAULT); //default
|
||||
VALUE_N("local", INSTALL_LOCAL);
|
||||
VALUE_N("global", INSTALL_GLOBAL);
|
||||
}
|
||||
|
||||
bool is_install_local(InstallType type) {
|
||||
#ifdef __WXMSW__
|
||||
#define DEFAULT_INSTALL_LOCAL false
|
||||
#else
|
||||
#define DEFAULT_INSTALL_LOCAL true
|
||||
#endif
|
||||
return type == INSTALL_DEFAULT ? DEFAULT_INSTALL_LOCAL : type == INSTALL_LOCAL;
|
||||
#ifdef __WXMSW__
|
||||
#define DEFAULT_INSTALL_LOCAL false
|
||||
#else
|
||||
#define DEFAULT_INSTALL_LOCAL true
|
||||
#endif
|
||||
return type == INSTALL_DEFAULT ? DEFAULT_INSTALL_LOCAL : type == INSTALL_LOCAL;
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION_ENUM(FilenameConflicts) {
|
||||
VALUE_N("keep old", CONFLICT_KEEP_OLD);
|
||||
VALUE_N("overwrite", CONFLICT_OVERWRITE);
|
||||
VALUE_N("number", CONFLICT_NUMBER);
|
||||
VALUE_N("number overwrite", CONFLICT_NUMBER_OVERWRITE);
|
||||
VALUE_N("keep old", CONFLICT_KEEP_OLD);
|
||||
VALUE_N("overwrite", CONFLICT_OVERWRITE);
|
||||
VALUE_N("number", CONFLICT_NUMBER);
|
||||
VALUE_N("number overwrite", CONFLICT_NUMBER_OVERWRITE);
|
||||
}
|
||||
|
||||
const int COLUMN_NOT_INITIALIZED = -100000;
|
||||
|
||||
ColumnSettings::ColumnSettings()
|
||||
: width(100), position(COLUMN_NOT_INITIALIZED), visible(false)
|
||||
: width(100), position(COLUMN_NOT_INITIALIZED), visible(false)
|
||||
{}
|
||||
|
||||
// dummy for ColumnSettings reflection
|
||||
ScriptValueP to_script(const ColumnSettings&) { return script_nil; }
|
||||
|
||||
IMPLEMENT_REFLECTION_NO_SCRIPT(ColumnSettings) {
|
||||
REFLECT(width);
|
||||
REFLECT(position);
|
||||
REFLECT(visible);
|
||||
REFLECT(width);
|
||||
REFLECT(position);
|
||||
REFLECT(visible);
|
||||
}
|
||||
|
||||
GameSettings::GameSettings()
|
||||
: sort_cards_ascending(true)
|
||||
, images_export_filename(_("{card.name}.jpg"))
|
||||
, images_export_conflicts(CONFLICT_NUMBER_OVERWRITE)
|
||||
, use_auto_replace(true)
|
||||
, pack_seed_random(true)
|
||||
, pack_seed(123456)
|
||||
, initialized(false)
|
||||
: sort_cards_ascending(true)
|
||||
, images_export_filename(_("{card.name}.jpg"))
|
||||
, images_export_conflicts(CONFLICT_NUMBER_OVERWRITE)
|
||||
, use_auto_replace(true)
|
||||
, pack_seed_random(true)
|
||||
, pack_seed(123456)
|
||||
, initialized(false)
|
||||
{}
|
||||
|
||||
void GameSettings::initDefaults(const Game& game) {
|
||||
// Defer initialization until the game is fully loaded.
|
||||
// This prevents data that needs to be initialized from
|
||||
// being accessed from the new set window, but removes
|
||||
// the need to load the entire file, which takes too long.
|
||||
if (initialized || !game.isFullyLoaded()) return;
|
||||
initialized = true;
|
||||
// init auto_replaces, copy from game file
|
||||
FOR_EACH_CONST(ar, game.auto_replaces) {
|
||||
// do we have this one?
|
||||
bool already_have = false;
|
||||
FOR_EACH(ar2, auto_replaces) {
|
||||
if (ar->match == ar2->match) {
|
||||
ar2->custom = false;
|
||||
already_have = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!already_have) {
|
||||
// TODO: when we start saving games, clone here
|
||||
ar->custom = false;
|
||||
auto_replaces.push_back(ar);
|
||||
}
|
||||
}
|
||||
// Defer initialization until the game is fully loaded.
|
||||
// This prevents data that needs to be initialized from
|
||||
// being accessed from the new set window, but removes
|
||||
// the need to load the entire file, which takes too long.
|
||||
if (initialized || !game.isFullyLoaded()) return;
|
||||
initialized = true;
|
||||
// init auto_replaces, copy from game file
|
||||
FOR_EACH_CONST(ar, game.auto_replaces) {
|
||||
// do we have this one?
|
||||
bool already_have = false;
|
||||
FOR_EACH(ar2, auto_replaces) {
|
||||
if (ar->match == ar2->match) {
|
||||
ar2->custom = false;
|
||||
already_have = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!already_have) {
|
||||
// TODO: when we start saving games, clone here
|
||||
ar->custom = false;
|
||||
auto_replaces.push_back(ar);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION_NO_SCRIPT(GameSettings) {
|
||||
REFLECT(default_stylesheet);
|
||||
REFLECT(default_export);
|
||||
REFLECT_N("cardlist columns", columns);
|
||||
REFLECT(sort_cards_by);
|
||||
REFLECT(sort_cards_ascending);
|
||||
REFLECT(images_export_filename);
|
||||
REFLECT(images_export_conflicts);
|
||||
REFLECT(use_auto_replace);
|
||||
REFLECT(auto_replaces);
|
||||
REFLECT(pack_amounts);
|
||||
REFLECT(pack_seed_random);
|
||||
REFLECT(pack_seed);
|
||||
REFLECT(default_stylesheet);
|
||||
REFLECT(default_export);
|
||||
REFLECT_N("cardlist columns", columns);
|
||||
REFLECT(sort_cards_by);
|
||||
REFLECT(sort_cards_ascending);
|
||||
REFLECT(images_export_filename);
|
||||
REFLECT(images_export_conflicts);
|
||||
REFLECT(use_auto_replace);
|
||||
REFLECT(auto_replaces);
|
||||
REFLECT(pack_amounts);
|
||||
REFLECT(pack_seed_random);
|
||||
REFLECT(pack_seed);
|
||||
}
|
||||
|
||||
|
||||
StyleSheetSettings::StyleSheetSettings()
|
||||
: card_zoom (1.0, true)
|
||||
, card_angle (0, true)
|
||||
, card_anti_alias (true, true)
|
||||
, card_borders (true, true)
|
||||
, card_draw_editing (true, true)
|
||||
, card_normal_export (true, true)
|
||||
, card_spellcheck_enabled(true, true)
|
||||
: card_zoom (1.0, true)
|
||||
, card_angle (0, true)
|
||||
, card_anti_alias (true, true)
|
||||
, card_borders (true, true)
|
||||
, card_draw_editing (true, true)
|
||||
, card_normal_export (true, true)
|
||||
, card_spellcheck_enabled(true, true)
|
||||
{}
|
||||
|
||||
void StyleSheetSettings::useDefault(const StyleSheetSettings& ss) {
|
||||
if (card_zoom .isDefault()) card_zoom .assignDefault(ss.card_zoom);
|
||||
if (card_angle .isDefault()) card_angle .assignDefault(ss.card_angle);
|
||||
if (card_anti_alias .isDefault()) card_anti_alias .assignDefault(ss.card_anti_alias);
|
||||
if (card_borders .isDefault()) card_borders .assignDefault(ss.card_borders);
|
||||
if (card_draw_editing .isDefault()) card_draw_editing .assignDefault(ss.card_draw_editing);
|
||||
if (card_normal_export .isDefault()) card_normal_export .assignDefault(ss.card_normal_export);
|
||||
if (card_spellcheck_enabled.isDefault()) card_spellcheck_enabled.assignDefault(ss.card_spellcheck_enabled);
|
||||
if (card_zoom .isDefault()) card_zoom .assignDefault(ss.card_zoom);
|
||||
if (card_angle .isDefault()) card_angle .assignDefault(ss.card_angle);
|
||||
if (card_anti_alias .isDefault()) card_anti_alias .assignDefault(ss.card_anti_alias);
|
||||
if (card_borders .isDefault()) card_borders .assignDefault(ss.card_borders);
|
||||
if (card_draw_editing .isDefault()) card_draw_editing .assignDefault(ss.card_draw_editing);
|
||||
if (card_normal_export .isDefault()) card_normal_export .assignDefault(ss.card_normal_export);
|
||||
if (card_spellcheck_enabled.isDefault()) card_spellcheck_enabled.assignDefault(ss.card_spellcheck_enabled);
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION_NO_SCRIPT(StyleSheetSettings) {
|
||||
REFLECT(card_zoom);
|
||||
REFLECT(card_angle);
|
||||
REFLECT(card_anti_alias);
|
||||
REFLECT(card_borders);
|
||||
REFLECT(card_draw_editing);
|
||||
REFLECT(card_normal_export);
|
||||
REFLECT(card_spellcheck_enabled);
|
||||
REFLECT(card_zoom);
|
||||
REFLECT(card_angle);
|
||||
REFLECT(card_anti_alias);
|
||||
REFLECT(card_borders);
|
||||
REFLECT(card_draw_editing);
|
||||
REFLECT(card_normal_export);
|
||||
REFLECT(card_spellcheck_enabled);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Printing
|
||||
|
||||
IMPLEMENT_REFLECTION_ENUM(PageLayoutType) {
|
||||
VALUE_N("no space", LAYOUT_NO_SPACE);
|
||||
VALUE_N("equal space", LAYOUT_EQUAL_SPACE);
|
||||
VALUE_N("no space", LAYOUT_NO_SPACE);
|
||||
VALUE_N("equal space", LAYOUT_EQUAL_SPACE);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Settings
|
||||
@@ -164,145 +164,145 @@ IMPLEMENT_REFLECTION_ENUM(PageLayoutType) {
|
||||
Settings settings;
|
||||
|
||||
Settings::Settings()
|
||||
: locale (_("en"))
|
||||
, set_window_maximized (false)
|
||||
, set_window_width (790)
|
||||
, set_window_height (300)
|
||||
, card_notes_height (40)
|
||||
, open_sets_in_new_window(true)
|
||||
, symbol_grid_size (30)
|
||||
, symbol_grid (true)
|
||||
, symbol_grid_snap (false)
|
||||
, print_layout (LAYOUT_NO_SPACE)
|
||||
#if USE_OLD_STYLE_UPDATE_CHECKER
|
||||
, updates_url (_("http://magicseteditor.sourceforge.net/updates"))
|
||||
#endif
|
||||
, package_versions_url (_("http://magicseteditor.sourceforge.net/packages"))
|
||||
, installer_list_url (_("http://magicseteditor.sourceforge.net/installers"))
|
||||
, check_updates (CHECK_IF_CONNECTED)
|
||||
, check_updates_all (true)
|
||||
, website_url (_("http://magicseteditor.sourceforge.net/"))
|
||||
, install_type (INSTALL_DEFAULT)
|
||||
: locale (_("en"))
|
||||
, set_window_maximized (false)
|
||||
, set_window_width (790)
|
||||
, set_window_height (300)
|
||||
, card_notes_height (40)
|
||||
, open_sets_in_new_window(true)
|
||||
, symbol_grid_size (30)
|
||||
, symbol_grid (true)
|
||||
, symbol_grid_snap (false)
|
||||
, print_layout (LAYOUT_NO_SPACE)
|
||||
#if USE_OLD_STYLE_UPDATE_CHECKER
|
||||
, updates_url (_("http://magicseteditor.sourceforge.net/updates"))
|
||||
#endif
|
||||
, package_versions_url (_("http://magicseteditor.sourceforge.net/packages"))
|
||||
, installer_list_url (_("http://magicseteditor.sourceforge.net/installers"))
|
||||
, check_updates (CHECK_IF_CONNECTED)
|
||||
, check_updates_all (true)
|
||||
, website_url (_("http://magicseteditor.sourceforge.net/"))
|
||||
, install_type (INSTALL_DEFAULT)
|
||||
{}
|
||||
|
||||
void Settings::addRecentFile(const String& filename) {
|
||||
// get absolute path
|
||||
wxFileName fn(filename);
|
||||
fn.Normalize();
|
||||
String filenameAbs = fn.GetFullPath();
|
||||
// remove duplicates
|
||||
recent_sets.erase(
|
||||
remove(recent_sets.begin(), recent_sets.end(), filenameAbs),
|
||||
recent_sets.end()
|
||||
);
|
||||
// add to front of list
|
||||
recent_sets.insert(recent_sets.begin(), filenameAbs);
|
||||
// enforce size limit
|
||||
if (recent_sets.size() > max_recent_sets) recent_sets.resize(max_recent_sets);
|
||||
// get absolute path
|
||||
wxFileName fn(filename);
|
||||
fn.Normalize();
|
||||
String filenameAbs = fn.GetFullPath();
|
||||
// remove duplicates
|
||||
recent_sets.erase(
|
||||
remove(recent_sets.begin(), recent_sets.end(), filenameAbs),
|
||||
recent_sets.end()
|
||||
);
|
||||
// add to front of list
|
||||
recent_sets.insert(recent_sets.begin(), filenameAbs);
|
||||
// enforce size limit
|
||||
if (recent_sets.size() > max_recent_sets) recent_sets.resize(max_recent_sets);
|
||||
}
|
||||
|
||||
GameSettings& Settings::gameSettingsFor(const Game& game) {
|
||||
GameSettingsP& gs = game_settings[game.name()];
|
||||
if (!gs) gs = intrusive(new GameSettings);
|
||||
gs->initDefaults(game);
|
||||
return *gs;
|
||||
GameSettingsP& gs = game_settings[game.name()];
|
||||
if (!gs) gs = intrusive(new GameSettings);
|
||||
gs->initDefaults(game);
|
||||
return *gs;
|
||||
}
|
||||
ColumnSettings& Settings::columnSettingsFor(const Game& game, const Field& field) {
|
||||
// Get game info
|
||||
GameSettings& gs = gameSettingsFor(game);
|
||||
// Get column info
|
||||
ColumnSettings& cs = gs.columns[field.name];
|
||||
if (cs.position == COLUMN_NOT_INITIALIZED) {
|
||||
// column info not set, initialize based on the game
|
||||
cs.visible = field.card_list_visible;
|
||||
cs.position = field.card_list_column;
|
||||
cs.width = field.card_list_width;
|
||||
}
|
||||
return cs;
|
||||
// Get game info
|
||||
GameSettings& gs = gameSettingsFor(game);
|
||||
// Get column info
|
||||
ColumnSettings& cs = gs.columns[field.name];
|
||||
if (cs.position == COLUMN_NOT_INITIALIZED) {
|
||||
// column info not set, initialize based on the game
|
||||
cs.visible = field.card_list_visible;
|
||||
cs.position = field.card_list_column;
|
||||
cs.width = field.card_list_width;
|
||||
}
|
||||
return cs;
|
||||
}
|
||||
StyleSheetSettings& Settings::stylesheetSettingsFor(const StyleSheet& stylesheet) {
|
||||
StyleSheetSettingsP& ss = stylesheet_settings[stylesheet.name()];
|
||||
if (!ss) ss = intrusive(new StyleSheetSettings);
|
||||
ss->useDefault(default_stylesheet_settings); // update default settings
|
||||
return *ss;
|
||||
StyleSheetSettingsP& ss = stylesheet_settings[stylesheet.name()];
|
||||
if (!ss) ss = intrusive(new StyleSheetSettings);
|
||||
ss->useDefault(default_stylesheet_settings); // update default settings
|
||||
return *ss;
|
||||
}
|
||||
|
||||
IndexMap<FieldP,ValueP>& Settings::exportOptionsFor(const ExportTemplate& export_template) {
|
||||
return export_options.get(export_template.name(), export_template.option_fields);
|
||||
return export_options.get(export_template.name(), export_template.option_fields);
|
||||
}
|
||||
|
||||
/// Retrieve the directory to use for settings and other data files
|
||||
String user_settings_dir() {
|
||||
String dir = wxStandardPaths::Get().GetUserDataDir();
|
||||
if (!wxDirExists(dir)) wxMkdir(dir);
|
||||
return dir + _("/");
|
||||
String dir = wxStandardPaths::Get().GetUserDataDir();
|
||||
if (!wxDirExists(dir)) wxMkdir(dir);
|
||||
return dir + _("/");
|
||||
}
|
||||
|
||||
String Settings::settingsFile() {
|
||||
// return user_settings_dir() + _("mse.config");
|
||||
return user_settings_dir() + _("mse8.config"); // use different file during development of C++ port
|
||||
// return user_settings_dir() + _("mse.config");
|
||||
return user_settings_dir() + _("mse8.config"); // use different file during development of C++ port
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION_NO_SCRIPT(Settings) {
|
||||
REFLECT_ALIAS(300, "style settings", "stylesheet settings");
|
||||
REFLECT_ALIAS(300, "default style settings", "default stylesheet settings");
|
||||
REFLECT(locale);
|
||||
REFLECT(recent_sets);
|
||||
REFLECT(default_set_dir);
|
||||
REFLECT(default_image_dir);
|
||||
REFLECT(default_symbol_dir);
|
||||
REFLECT(default_export_dir);
|
||||
REFLECT(set_window_maximized);
|
||||
REFLECT(set_window_width);
|
||||
REFLECT(set_window_height);
|
||||
REFLECT(card_notes_height);
|
||||
REFLECT(open_sets_in_new_window);
|
||||
REFLECT(symbol_grid_size);
|
||||
REFLECT(symbol_grid);
|
||||
REFLECT(symbol_grid_snap);
|
||||
REFLECT(default_game);
|
||||
REFLECT(print_layout);
|
||||
REFLECT(apprentice_location);
|
||||
#if USE_OLD_STYLE_UPDATE_CHECKER
|
||||
REFLECT(updates_url);
|
||||
#else
|
||||
REFLECT_IGNORE(306,"updates url");
|
||||
#endif
|
||||
REFLECT(package_versions_url);
|
||||
REFLECT(installer_list_url);
|
||||
REFLECT(check_updates);
|
||||
REFLECT(check_updates_all);
|
||||
REFLECT(install_type);
|
||||
REFLECT(website_url);
|
||||
REFLECT(game_settings);
|
||||
REFLECT(stylesheet_settings);
|
||||
REFLECT(default_stylesheet_settings);
|
||||
REFLECT(export_options);
|
||||
REFLECT_ALIAS(300, "style settings", "stylesheet settings");
|
||||
REFLECT_ALIAS(300, "default style settings", "default stylesheet settings");
|
||||
REFLECT(locale);
|
||||
REFLECT(recent_sets);
|
||||
REFLECT(default_set_dir);
|
||||
REFLECT(default_image_dir);
|
||||
REFLECT(default_symbol_dir);
|
||||
REFLECT(default_export_dir);
|
||||
REFLECT(set_window_maximized);
|
||||
REFLECT(set_window_width);
|
||||
REFLECT(set_window_height);
|
||||
REFLECT(card_notes_height);
|
||||
REFLECT(open_sets_in_new_window);
|
||||
REFLECT(symbol_grid_size);
|
||||
REFLECT(symbol_grid);
|
||||
REFLECT(symbol_grid_snap);
|
||||
REFLECT(default_game);
|
||||
REFLECT(print_layout);
|
||||
REFLECT(apprentice_location);
|
||||
#if USE_OLD_STYLE_UPDATE_CHECKER
|
||||
REFLECT(updates_url);
|
||||
#else
|
||||
REFLECT_IGNORE(306,"updates url");
|
||||
#endif
|
||||
REFLECT(package_versions_url);
|
||||
REFLECT(installer_list_url);
|
||||
REFLECT(check_updates);
|
||||
REFLECT(check_updates_all);
|
||||
REFLECT(install_type);
|
||||
REFLECT(website_url);
|
||||
REFLECT(game_settings);
|
||||
REFLECT(stylesheet_settings);
|
||||
REFLECT(default_stylesheet_settings);
|
||||
REFLECT(export_options);
|
||||
}
|
||||
|
||||
void Settings::clear() {
|
||||
recent_sets.clear();
|
||||
game_settings.clear();
|
||||
stylesheet_settings.clear();
|
||||
default_stylesheet_settings = StyleSheetSettings();
|
||||
export_options.clear();
|
||||
recent_sets.clear();
|
||||
game_settings.clear();
|
||||
stylesheet_settings.clear();
|
||||
default_stylesheet_settings = StyleSheetSettings();
|
||||
export_options.clear();
|
||||
}
|
||||
|
||||
void Settings::read() {
|
||||
// clear current settings, otherwise we duplicate vector elements
|
||||
clear();
|
||||
// (re)load settings
|
||||
String filename = settingsFile();
|
||||
if (wxFileExists(filename)) {
|
||||
// settings file not existing is not an error
|
||||
shared_ptr<wxFileInputStream> file = shared(new wxFileInputStream(filename));
|
||||
if (!file->Ok()) return; // failure is not an error
|
||||
Reader reader(file, nullptr, filename);
|
||||
reader.handle_greedy(*this);
|
||||
}
|
||||
// clear current settings, otherwise we duplicate vector elements
|
||||
clear();
|
||||
// (re)load settings
|
||||
String filename = settingsFile();
|
||||
if (wxFileExists(filename)) {
|
||||
// settings file not existing is not an error
|
||||
shared_ptr<wxFileInputStream> file = shared(new wxFileInputStream(filename));
|
||||
if (!file->Ok()) return; // failure is not an error
|
||||
Reader reader(file, nullptr, filename);
|
||||
reader.handle_greedy(*this);
|
||||
}
|
||||
}
|
||||
|
||||
void Settings::write() {
|
||||
Writer writer(shared(new wxFileOutputStream(settingsFile())), app_version);
|
||||
writer.handle(*this);
|
||||
Writer writer(shared(new wxFileOutputStream(settingsFile())), app_version);
|
||||
writer.handle(*this);
|
||||
}
|
||||
|
||||
+141
-141
@@ -32,16 +32,16 @@ DECLARE_POINTER_TYPE(AutoReplace);
|
||||
|
||||
/// When to check for updates?
|
||||
enum CheckUpdates
|
||||
{ CHECK_ALWAYS
|
||||
, CHECK_IF_CONNECTED
|
||||
, CHECK_NEVER
|
||||
{ CHECK_ALWAYS
|
||||
, CHECK_IF_CONNECTED
|
||||
, CHECK_NEVER
|
||||
};
|
||||
|
||||
/// Where to install to?
|
||||
enum InstallType
|
||||
{ INSTALL_DEFAULT // the platform default.
|
||||
, INSTALL_LOCAL // install to the user's files
|
||||
, INSTALL_GLOBAL // install to the global files
|
||||
{ INSTALL_DEFAULT // the platform default.
|
||||
, INSTALL_LOCAL // install to the user's files
|
||||
, INSTALL_GLOBAL // install to the global files
|
||||
};
|
||||
|
||||
void parse_enum(const String&, InstallType&);
|
||||
@@ -49,75 +49,75 @@ bool is_install_local(InstallType type);
|
||||
|
||||
/// How to handle filename conflicts
|
||||
enum FilenameConflicts
|
||||
{ CONFLICT_KEEP_OLD // always keep old file
|
||||
, CONFLICT_OVERWRITE // always overwrite
|
||||
, CONFLICT_NUMBER // always add numbers ("file.1.something")
|
||||
, CONFLICT_NUMBER_OVERWRITE // only add numbers for conflicts inside a set, overwrite old stuff
|
||||
{ CONFLICT_KEEP_OLD // always keep old file
|
||||
, CONFLICT_OVERWRITE // always overwrite
|
||||
, CONFLICT_NUMBER // always add numbers ("file.1.something")
|
||||
, CONFLICT_NUMBER_OVERWRITE // only add numbers for conflicts inside a set, overwrite old stuff
|
||||
};
|
||||
|
||||
/// Settings of a single column in the card list
|
||||
class ColumnSettings {
|
||||
public:
|
||||
ColumnSettings();
|
||||
UInt width;
|
||||
int position;
|
||||
bool visible;
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
ColumnSettings();
|
||||
UInt width;
|
||||
int position;
|
||||
bool visible;
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
/// Settings for a Game
|
||||
class GameSettings : public IntrusivePtrBase<GameSettings> {
|
||||
public:
|
||||
GameSettings();
|
||||
|
||||
/// Where the settings have defaults, initialize with the values from the game
|
||||
void initDefaults(const Game& g);
|
||||
|
||||
String default_stylesheet;
|
||||
String default_export;
|
||||
map<String, ColumnSettings> columns;
|
||||
String sort_cards_by;
|
||||
bool sort_cards_ascending;
|
||||
String images_export_filename;
|
||||
FilenameConflicts images_export_conflicts;
|
||||
bool use_auto_replace;
|
||||
vector<AutoReplaceP> auto_replaces; ///< Things to autoreplace in textboxes
|
||||
map<String, int> pack_amounts;
|
||||
bool pack_seed_random;
|
||||
int pack_seed;
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
GameSettings();
|
||||
|
||||
/// Where the settings have defaults, initialize with the values from the game
|
||||
void initDefaults(const Game& g);
|
||||
|
||||
String default_stylesheet;
|
||||
String default_export;
|
||||
map<String, ColumnSettings> columns;
|
||||
String sort_cards_by;
|
||||
bool sort_cards_ascending;
|
||||
String images_export_filename;
|
||||
FilenameConflicts images_export_conflicts;
|
||||
bool use_auto_replace;
|
||||
vector<AutoReplaceP> auto_replaces; ///< Things to autoreplace in textboxes
|
||||
map<String, int> pack_amounts;
|
||||
bool pack_seed_random;
|
||||
int pack_seed;
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
private:
|
||||
bool initialized;
|
||||
bool initialized;
|
||||
};
|
||||
|
||||
/// Settings for a StyleSheet
|
||||
class StyleSheetSettings : public IntrusivePtrBase<StyleSheetSettings> {
|
||||
public:
|
||||
StyleSheetSettings();
|
||||
|
||||
// Rendering/display settings
|
||||
Defaultable<double> card_zoom;
|
||||
Defaultable<Degrees> card_angle;
|
||||
Defaultable<bool> card_anti_alias;
|
||||
Defaultable<bool> card_borders;
|
||||
Defaultable<bool> card_draw_editing;
|
||||
Defaultable<bool> card_normal_export;
|
||||
Defaultable<bool> card_spellcheck_enabled;
|
||||
|
||||
/// Where the settings are the default, use the value from ss
|
||||
void useDefault(const StyleSheetSettings& ss);
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
StyleSheetSettings();
|
||||
|
||||
// Rendering/display settings
|
||||
Defaultable<double> card_zoom;
|
||||
Defaultable<Degrees> card_angle;
|
||||
Defaultable<bool> card_anti_alias;
|
||||
Defaultable<bool> card_borders;
|
||||
Defaultable<bool> card_draw_editing;
|
||||
Defaultable<bool> card_normal_export;
|
||||
Defaultable<bool> card_spellcheck_enabled;
|
||||
|
||||
/// Where the settings are the default, use the value from ss
|
||||
void useDefault(const StyleSheetSettings& ss);
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Printing settings
|
||||
|
||||
enum PageLayoutType
|
||||
{ LAYOUT_NO_SPACE
|
||||
, LAYOUT_EQUAL_SPACE
|
||||
//, LAYOUT_CUSTOM
|
||||
{ LAYOUT_NO_SPACE
|
||||
, LAYOUT_EQUAL_SPACE
|
||||
//, LAYOUT_CUSTOM
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Settings
|
||||
@@ -128,98 +128,98 @@ enum PageLayoutType
|
||||
*/
|
||||
class Settings {
|
||||
public:
|
||||
/// Default constructor initializes default settings
|
||||
Settings();
|
||||
|
||||
// --------------------------------------------------- : Locale
|
||||
|
||||
String locale;
|
||||
|
||||
// --------------------------------------------------- : Recently opened sets
|
||||
vector<String> recent_sets;
|
||||
static const UInt max_recent_sets = 9; // store this many recent sets
|
||||
|
||||
/// Add a file to the list of recent files
|
||||
void addRecentFile(const String& filename);
|
||||
|
||||
// --------------------------------------------------- : Files/directories
|
||||
String default_set_dir; ///< Where to look for .mse-set files
|
||||
String default_image_dir; ///< Where to look for images to import
|
||||
String default_symbol_dir; ///< Where to look for .mse-symbol files
|
||||
String default_export_dir; ///< Where to export to by default
|
||||
|
||||
// --------------------------------------------------- : Set window
|
||||
bool set_window_maximized;
|
||||
UInt set_window_width;
|
||||
UInt set_window_height;
|
||||
UInt card_notes_height;
|
||||
bool open_sets_in_new_window;
|
||||
|
||||
// --------------------------------------------------- : Symbol editor
|
||||
UInt symbol_grid_size;
|
||||
bool symbol_grid;
|
||||
bool symbol_grid_snap;
|
||||
|
||||
// --------------------------------------------------- : Default pacakge selections
|
||||
String default_game;
|
||||
|
||||
// --------------------------------------------------- : Game/stylesheet specific
|
||||
|
||||
/// Get the settings object for a specific game
|
||||
GameSettings& gameSettingsFor (const Game& game);
|
||||
/// Get the settings for a column for a specific field in a game
|
||||
ColumnSettings& columnSettingsFor (const Game& game, const Field& field);
|
||||
/// Get the settings object for a specific stylesheet
|
||||
StyleSheetSettings& stylesheetSettingsFor(const StyleSheet& stylesheet);
|
||||
|
||||
/// Default constructor initializes default settings
|
||||
Settings();
|
||||
|
||||
// --------------------------------------------------- : Locale
|
||||
|
||||
String locale;
|
||||
|
||||
// --------------------------------------------------- : Recently opened sets
|
||||
vector<String> recent_sets;
|
||||
static const UInt max_recent_sets = 9; // store this many recent sets
|
||||
|
||||
/// Add a file to the list of recent files
|
||||
void addRecentFile(const String& filename);
|
||||
|
||||
// --------------------------------------------------- : Files/directories
|
||||
String default_set_dir; ///< Where to look for .mse-set files
|
||||
String default_image_dir; ///< Where to look for images to import
|
||||
String default_symbol_dir; ///< Where to look for .mse-symbol files
|
||||
String default_export_dir; ///< Where to export to by default
|
||||
|
||||
// --------------------------------------------------- : Set window
|
||||
bool set_window_maximized;
|
||||
UInt set_window_width;
|
||||
UInt set_window_height;
|
||||
UInt card_notes_height;
|
||||
bool open_sets_in_new_window;
|
||||
|
||||
// --------------------------------------------------- : Symbol editor
|
||||
UInt symbol_grid_size;
|
||||
bool symbol_grid;
|
||||
bool symbol_grid_snap;
|
||||
|
||||
// --------------------------------------------------- : Default pacakge selections
|
||||
String default_game;
|
||||
|
||||
// --------------------------------------------------- : Game/stylesheet specific
|
||||
|
||||
/// Get the settings object for a specific game
|
||||
GameSettings& gameSettingsFor (const Game& game);
|
||||
/// Get the settings for a column for a specific field in a game
|
||||
ColumnSettings& columnSettingsFor (const Game& game, const Field& field);
|
||||
/// Get the settings object for a specific stylesheet
|
||||
StyleSheetSettings& stylesheetSettingsFor(const StyleSheet& stylesheet);
|
||||
|
||||
private:
|
||||
map<String,GameSettingsP> game_settings;
|
||||
map<String,StyleSheetSettingsP> stylesheet_settings;
|
||||
map<String,GameSettingsP> game_settings;
|
||||
map<String,StyleSheetSettingsP> stylesheet_settings;
|
||||
public:
|
||||
StyleSheetSettings default_stylesheet_settings; ///< The default settings for stylesheets
|
||||
|
||||
// --------------------------------------------------- : Exports
|
||||
StyleSheetSettings default_stylesheet_settings; ///< The default settings for stylesheets
|
||||
|
||||
// --------------------------------------------------- : Exports
|
||||
private:
|
||||
DelayedIndexMaps<FieldP,ValueP> export_options;
|
||||
DelayedIndexMaps<FieldP,ValueP> export_options;
|
||||
public:
|
||||
|
||||
/// Get the options for an export template
|
||||
IndexMap<FieldP,ValueP>& exportOptionsFor(const ExportTemplate& export_template);
|
||||
|
||||
// --------------------------------------------------- : Printing
|
||||
|
||||
PageLayoutType print_layout;
|
||||
|
||||
// --------------------------------------------------- : Special game stuff
|
||||
String apprentice_location;
|
||||
|
||||
// --------------------------------------------------- : Update checking
|
||||
#if USE_OLD_STYLE_UPDATE_CHECKER
|
||||
String updates_url;
|
||||
#endif
|
||||
String package_versions_url; ///< latest package versions
|
||||
String installer_list_url; ///< available installers
|
||||
CheckUpdates check_updates;
|
||||
bool check_updates_all; ///< Check updates of all packages, not just the program
|
||||
String website_url;
|
||||
|
||||
// --------------------------------------------------- : Installation settings
|
||||
InstallType install_type;
|
||||
|
||||
// --------------------------------------------------- : The io
|
||||
|
||||
/// Read the settings file from the standard location
|
||||
void read();
|
||||
/// Store the settings in the standard location
|
||||
void write();
|
||||
|
||||
|
||||
/// Get the options for an export template
|
||||
IndexMap<FieldP,ValueP>& exportOptionsFor(const ExportTemplate& export_template);
|
||||
|
||||
// --------------------------------------------------- : Printing
|
||||
|
||||
PageLayoutType print_layout;
|
||||
|
||||
// --------------------------------------------------- : Special game stuff
|
||||
String apprentice_location;
|
||||
|
||||
// --------------------------------------------------- : Update checking
|
||||
#if USE_OLD_STYLE_UPDATE_CHECKER
|
||||
String updates_url;
|
||||
#endif
|
||||
String package_versions_url; ///< latest package versions
|
||||
String installer_list_url; ///< available installers
|
||||
CheckUpdates check_updates;
|
||||
bool check_updates_all; ///< Check updates of all packages, not just the program
|
||||
String website_url;
|
||||
|
||||
// --------------------------------------------------- : Installation settings
|
||||
InstallType install_type;
|
||||
|
||||
// --------------------------------------------------- : The io
|
||||
|
||||
/// Read the settings file from the standard location
|
||||
void read();
|
||||
/// Store the settings in the standard location
|
||||
void write();
|
||||
|
||||
private:
|
||||
/// Name of the settings file
|
||||
String settingsFile();
|
||||
/// Clear settings before reading them
|
||||
void clear();
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
/// Name of the settings file
|
||||
String settingsFile();
|
||||
/// Clear settings before reading them
|
||||
void clear();
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
/// The global settings object
|
||||
|
||||
+90
-90
@@ -20,120 +20,120 @@ extern ScriptValueP script_primary_choice;
|
||||
// ----------------------------------------------------------------------------- : Statistics dimension
|
||||
|
||||
StatsDimension::StatsDimension()
|
||||
: automatic (false)
|
||||
, position_hint(0)
|
||||
, numeric (false)
|
||||
, bin_size (0)
|
||||
, show_empty (false)
|
||||
, split_list (false)
|
||||
: automatic (false)
|
||||
, position_hint(0)
|
||||
, numeric (false)
|
||||
, bin_size (0)
|
||||
, show_empty (false)
|
||||
, split_list (false)
|
||||
{}
|
||||
|
||||
StatsDimension::StatsDimension(const Field& field)
|
||||
: automatic (true)
|
||||
, name (field.name)
|
||||
, description (field.description)
|
||||
, position_hint(field.position_hint)
|
||||
, icon_filename(field.icon_filename)
|
||||
, numeric (false)
|
||||
, show_empty (false)
|
||||
, split_list (false)
|
||||
: automatic (true)
|
||||
, name (field.name)
|
||||
, description (field.description)
|
||||
, position_hint(field.position_hint)
|
||||
, icon_filename(field.icon_filename)
|
||||
, numeric (false)
|
||||
, show_empty (false)
|
||||
, split_list (false)
|
||||
{
|
||||
// choice field?
|
||||
const ChoiceField* choice_field = dynamic_cast<const ChoiceField*>(&field);
|
||||
if (choice_field) {
|
||||
colors = choice_field->choice_colors;
|
||||
/*int count = choice_field->choices->lastId();
|
||||
for (int i = 0 ; i < count ; ++i) {
|
||||
groups.push_back(choice_field->choices->choiceName(i));
|
||||
}*/
|
||||
// only top level choices
|
||||
FOR_EACH_CONST(g, choice_field->choices->choices) {
|
||||
groups.push_back(g->name);
|
||||
}
|
||||
// initialize script: primary_choice(card.{field_name})
|
||||
Script& s = script.getMutableScript();
|
||||
s.addInstruction(I_PUSH_CONST, script_primary_choice);
|
||||
s.addInstruction(I_GET_VAR, SCRIPT_VAR_card);
|
||||
s.addInstruction(I_MEMBER_C, field.name);
|
||||
s.addInstruction(I_CALL, 1);
|
||||
s.addInstruction(I_NOP, SCRIPT_VAR_input);
|
||||
} else {
|
||||
// initialize script, card.{field_name}
|
||||
Script& s = script.getMutableScript();
|
||||
s.addInstruction(I_GET_VAR, SCRIPT_VAR_card);
|
||||
s.addInstruction(I_MEMBER_C, field.name);
|
||||
}
|
||||
// choice field?
|
||||
const ChoiceField* choice_field = dynamic_cast<const ChoiceField*>(&field);
|
||||
if (choice_field) {
|
||||
colors = choice_field->choice_colors;
|
||||
/*int count = choice_field->choices->lastId();
|
||||
for (int i = 0 ; i < count ; ++i) {
|
||||
groups.push_back(choice_field->choices->choiceName(i));
|
||||
}*/
|
||||
// only top level choices
|
||||
FOR_EACH_CONST(g, choice_field->choices->choices) {
|
||||
groups.push_back(g->name);
|
||||
}
|
||||
// initialize script: primary_choice(card.{field_name})
|
||||
Script& s = script.getMutableScript();
|
||||
s.addInstruction(I_PUSH_CONST, script_primary_choice);
|
||||
s.addInstruction(I_GET_VAR, SCRIPT_VAR_card);
|
||||
s.addInstruction(I_MEMBER_C, field.name);
|
||||
s.addInstruction(I_CALL, 1);
|
||||
s.addInstruction(I_NOP, SCRIPT_VAR_input);
|
||||
} else {
|
||||
// initialize script, card.{field_name}
|
||||
Script& s = script.getMutableScript();
|
||||
s.addInstruction(I_GET_VAR, SCRIPT_VAR_card);
|
||||
s.addInstruction(I_MEMBER_C, field.name);
|
||||
}
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION_NO_GET_MEMBER(StatsDimension) {
|
||||
if (!automatic) {
|
||||
REFLECT(name);
|
||||
REFLECT(description);
|
||||
REFLECT(position_hint);
|
||||
REFLECT_N("icon", icon_filename);
|
||||
REFLECT(script);
|
||||
REFLECT(numeric);
|
||||
REFLECT(bin_size);
|
||||
REFLECT(show_empty);
|
||||
REFLECT(split_list);
|
||||
REFLECT(colors);
|
||||
REFLECT(groups);
|
||||
}
|
||||
if (!automatic) {
|
||||
REFLECT(name);
|
||||
REFLECT(description);
|
||||
REFLECT(position_hint);
|
||||
REFLECT_N("icon", icon_filename);
|
||||
REFLECT(script);
|
||||
REFLECT(numeric);
|
||||
REFLECT(bin_size);
|
||||
REFLECT(show_empty);
|
||||
REFLECT(split_list);
|
||||
REFLECT(colors);
|
||||
REFLECT(groups);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Statistics category
|
||||
|
||||
StatsCategory::StatsCategory()
|
||||
: automatic(false)
|
||||
, position_hint(0)
|
||||
, type(GRAPH_TYPE_BAR)
|
||||
: automatic(false)
|
||||
, position_hint(0)
|
||||
, type(GRAPH_TYPE_BAR)
|
||||
{}
|
||||
|
||||
StatsCategory::StatsCategory(const StatsDimensionP& dim)
|
||||
: automatic(true)
|
||||
, name (dim->name)
|
||||
, description (dim->description)
|
||||
, position_hint(dim->position_hint)
|
||||
, icon_filename(dim->icon_filename)
|
||||
, dimensions(1, dim)
|
||||
, type(GRAPH_TYPE_BAR)
|
||||
: automatic(true)
|
||||
, name (dim->name)
|
||||
, description (dim->description)
|
||||
, position_hint(dim->position_hint)
|
||||
, icon_filename(dim->icon_filename)
|
||||
, dimensions(1, dim)
|
||||
, type(GRAPH_TYPE_BAR)
|
||||
{}
|
||||
|
||||
IMPLEMENT_REFLECTION_NO_GET_MEMBER(StatsCategory) {
|
||||
if (!automatic) {
|
||||
REFLECT(name);
|
||||
REFLECT(description);
|
||||
REFLECT(position_hint);
|
||||
REFLECT_N("icon", icon_filename);
|
||||
REFLECT(type);
|
||||
REFLECT_N("dimensions", dimension_names);
|
||||
}
|
||||
if (!automatic) {
|
||||
REFLECT(name);
|
||||
REFLECT(description);
|
||||
REFLECT(position_hint);
|
||||
REFLECT_N("icon", icon_filename);
|
||||
REFLECT(type);
|
||||
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",n));
|
||||
} else {
|
||||
dimensions.push_back(dim);
|
||||
}
|
||||
}
|
||||
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",n));
|
||||
} else {
|
||||
dimensions.push_back(dim);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : GraphType (from graph_type.hpp)
|
||||
|
||||
IMPLEMENT_REFLECTION_ENUM(GraphType) {
|
||||
VALUE_N("bar", GRAPH_TYPE_BAR);
|
||||
VALUE_N("pie", GRAPH_TYPE_PIE);
|
||||
VALUE_N("stack", GRAPH_TYPE_STACK);
|
||||
VALUE_N("scatter", GRAPH_TYPE_SCATTER);
|
||||
VALUE_N("scatter pie", GRAPH_TYPE_SCATTER_PIE);
|
||||
VALUE_N("bar", GRAPH_TYPE_BAR);
|
||||
VALUE_N("pie", GRAPH_TYPE_PIE);
|
||||
VALUE_N("stack", GRAPH_TYPE_STACK);
|
||||
VALUE_N("scatter", GRAPH_TYPE_SCATTER);
|
||||
VALUE_N("scatter pie", GRAPH_TYPE_SCATTER_PIE);
|
||||
}
|
||||
|
||||
+35
-35
@@ -24,24 +24,24 @@ DECLARE_POINTER_TYPE(StatsCategory);
|
||||
/** Dimensions can be generated automatically based on card fields */
|
||||
class StatsDimension : public IntrusivePtrBase<StatsDimension> {
|
||||
public:
|
||||
StatsDimension();
|
||||
StatsDimension(const Field&);
|
||||
|
||||
const bool automatic; ///< Based on a card field?
|
||||
String name; ///< Name of this dimension
|
||||
String description; ///< Description, used in status bar
|
||||
int position_hint; ///< Hint for the ordering
|
||||
String icon_filename; ///< Icon for lists
|
||||
Bitmap icon; ///< The loaded icon (optional of course)
|
||||
OptionalScript script; ///< Script that determines the value(s)
|
||||
bool numeric; ///< Are the values numeric? If so, they require special sorting
|
||||
double bin_size; ///< Bin adjecent numbers?
|
||||
bool show_empty; ///< Should "" be shown?
|
||||
bool split_list; ///< Split values into multiple ones separated by commas
|
||||
map<String,Color> colors; ///< Colors for the categories
|
||||
vector<String> groups; ///< Order of the items
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
StatsDimension();
|
||||
StatsDimension(const Field&);
|
||||
|
||||
const bool automatic; ///< Based on a card field?
|
||||
String name; ///< Name of this dimension
|
||||
String description; ///< Description, used in status bar
|
||||
int position_hint; ///< Hint for the ordering
|
||||
String icon_filename; ///< Icon for lists
|
||||
Bitmap icon; ///< The loaded icon (optional of course)
|
||||
OptionalScript script; ///< Script that determines the value(s)
|
||||
bool numeric; ///< Are the values numeric? If so, they require special sorting
|
||||
double bin_size; ///< Bin adjecent numbers?
|
||||
bool show_empty; ///< Should "" be shown?
|
||||
bool split_list; ///< Split values into multiple ones separated by commas
|
||||
map<String,Color> colors; ///< Colors for the categories
|
||||
vector<String> groups; ///< Order of the items
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Statistics category
|
||||
@@ -50,23 +50,23 @@ class StatsDimension : public IntrusivePtrBase<StatsDimension> {
|
||||
/** Can be generated automatically based on a dimension */
|
||||
class StatsCategory : public IntrusivePtrBase<StatsCategory> {
|
||||
public:
|
||||
StatsCategory();
|
||||
StatsCategory(const StatsDimensionP&);
|
||||
|
||||
const bool automatic; ///< Automatically generated?
|
||||
String name; ///< Name/label
|
||||
String description; ///< Description, used in status bar
|
||||
int position_hint; ///< Hint for the ordering
|
||||
String icon_filename; ///< Icon for lists
|
||||
Bitmap icon; ///< The loaded icon (optional of course)
|
||||
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();
|
||||
StatsCategory();
|
||||
StatsCategory(const StatsDimensionP&);
|
||||
|
||||
const bool automatic; ///< Automatically generated?
|
||||
String name; ///< Name/label
|
||||
String description; ///< Description, used in status bar
|
||||
int position_hint; ///< Hint for the ordering
|
||||
String icon_filename; ///< Icon for lists
|
||||
Bitmap icon; ///< The loaded icon (optional of course)
|
||||
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();
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
+95
-95
@@ -21,44 +21,44 @@ DECLARE_TYPEOF_COLLECTION(FieldP);
|
||||
IMPLEMENT_DYNAMIC_ARG(StyleSheet*, stylesheet_for_reading, nullptr);
|
||||
|
||||
StyleSheet::StyleSheet()
|
||||
: card_width(100), card_height(100)
|
||||
, card_dpi(96), card_background(*wxWHITE)
|
||||
, dependencies_initialized(false)
|
||||
: card_width(100), card_height(100)
|
||||
, card_dpi(96), card_background(*wxWHITE)
|
||||
, dependencies_initialized(false)
|
||||
{}
|
||||
|
||||
StyleSheetP StyleSheet::byGameAndName(const Game& game, const String& name) {
|
||||
/// Alternative stylesheets for game
|
||||
static map<String, String> stylesheet_alternatives;
|
||||
String full_name = game.name() + _("-") + name + _(".mse-style");
|
||||
try {
|
||||
map<String, String>::const_iterator it = stylesheet_alternatives.find(full_name);
|
||||
if (it != stylesheet_alternatives.end()) {
|
||||
return package_manager.open<StyleSheet>(it->second);
|
||||
} else {
|
||||
return package_manager.open<StyleSheet>(full_name);
|
||||
}
|
||||
} catch (PackageNotFoundError& e) {
|
||||
if (stylesheet_for_reading()) {
|
||||
// we already have a stylesheet higher up, so just return a null pointer
|
||||
return StyleSheetP();
|
||||
}
|
||||
// load an alternative stylesheet
|
||||
StyleSheetP ss = select_stylesheet(game, name);
|
||||
if (ss) {
|
||||
stylesheet_alternatives[full_name] = ss->relativeFilename();
|
||||
return ss;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
/// Alternative stylesheets for game
|
||||
static map<String, String> stylesheet_alternatives;
|
||||
String full_name = game.name() + _("-") + name + _(".mse-style");
|
||||
try {
|
||||
map<String, String>::const_iterator it = stylesheet_alternatives.find(full_name);
|
||||
if (it != stylesheet_alternatives.end()) {
|
||||
return package_manager.open<StyleSheet>(it->second);
|
||||
} else {
|
||||
return package_manager.open<StyleSheet>(full_name);
|
||||
}
|
||||
} catch (PackageNotFoundError& e) {
|
||||
if (stylesheet_for_reading()) {
|
||||
// we already have a stylesheet higher up, so just return a null pointer
|
||||
return StyleSheetP();
|
||||
}
|
||||
// load an alternative stylesheet
|
||||
StyleSheetP ss = select_stylesheet(game, name);
|
||||
if (ss) {
|
||||
stylesheet_alternatives[full_name] = ss->relativeFilename();
|
||||
return ss;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
String StyleSheet::stylesheetName() const {
|
||||
String sn = name(), gn = game->name();
|
||||
if (sn.size() + 1 > gn.size()) {
|
||||
return sn.substr(gn.size() + 1); // remove "gamename-"
|
||||
} else {
|
||||
return sn;
|
||||
}
|
||||
String sn = name(), gn = game->name();
|
||||
if (sn.size() + 1 > gn.size()) {
|
||||
return sn.substr(gn.size() + 1); // remove "gamename-"
|
||||
} else {
|
||||
return sn;
|
||||
}
|
||||
}
|
||||
|
||||
String StyleSheet::typeNameStatic() { return _("style"); }
|
||||
@@ -66,86 +66,86 @@ String StyleSheet::typeName() const { return _("style"); }
|
||||
Version StyleSheet::fileVersion() const { return file_version_stylesheet; }
|
||||
|
||||
void StyleSheet::validate(Version ver) {
|
||||
Packaged::validate(ver);
|
||||
if (!game) {
|
||||
throw Error(_ERROR_1_("no game specified",_TYPE_("stylesheet")));
|
||||
}
|
||||
// a stylsheet depends on the game it is made for
|
||||
requireDependency(game.get());
|
||||
Packaged::validate(ver);
|
||||
if (!game) {
|
||||
throw Error(_ERROR_1_("no game specified",_TYPE_("stylesheet")));
|
||||
}
|
||||
// a stylsheet depends on the game it is made for
|
||||
requireDependency(game.get());
|
||||
}
|
||||
|
||||
|
||||
StyleP StyleSheet::styleFor(const FieldP& field) {
|
||||
if (card_style.containsKey(field)) {
|
||||
return card_style[field];
|
||||
} else if (set_info_style.containsKey(field)) {
|
||||
return set_info_style[field];
|
||||
} else if (styling_style.containsKey(field)) {
|
||||
return styling_style[field];
|
||||
} else {
|
||||
throw InternalError(_("Can not find styling for field '")+field->name+_("'in stylesheet"));
|
||||
}
|
||||
if (card_style.containsKey(field)) {
|
||||
return card_style[field];
|
||||
} else if (set_info_style.containsKey(field)) {
|
||||
return set_info_style[field];
|
||||
} else if (styling_style.containsKey(field)) {
|
||||
return styling_style[field];
|
||||
} else {
|
||||
throw InternalError(_("Can not find styling for field '")+field->name+_("'in stylesheet"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void mark_dependency_value(const StyleSheet& stylesheet, const Dependency& dep) {
|
||||
stylesheet.game->dependent_scripts_stylesheet.add(dep);
|
||||
stylesheet.game->dependent_scripts_stylesheet.add(dep);
|
||||
}
|
||||
|
||||
|
||||
IMPLEMENT_REFLECTION(StyleSheet) {
|
||||
// < 0.3.0 didn't use card_ prefix
|
||||
REFLECT_ALIAS(300, "width", "card width");
|
||||
REFLECT_ALIAS(300, "height", "card height");
|
||||
REFLECT_ALIAS(300, "dpi", "card dpi");
|
||||
REFLECT_ALIAS(300, "background", "card background");
|
||||
REFLECT_ALIAS(300, "info style", "set info style");
|
||||
REFLECT_ALIAS(300, "align", "alignment");
|
||||
REFLECT_ALIAS(300, "extra field", "styling field");
|
||||
REFLECT_ALIAS(300, "extra style", "styling style");
|
||||
|
||||
REFLECT(game);
|
||||
REFLECT_BASE(Packaged);
|
||||
REFLECT(card_width);
|
||||
REFLECT(card_height);
|
||||
REFLECT(card_dpi);
|
||||
REFLECT(card_background);
|
||||
REFLECT(init_script);
|
||||
// styling
|
||||
REFLECT(styling_fields);
|
||||
REFLECT_IF_READING styling_style.init(styling_fields);
|
||||
REFLECT(styling_style);
|
||||
// style of game fields
|
||||
if (game) {
|
||||
REFLECT_IF_READING {
|
||||
card_style.init(game->card_fields);
|
||||
set_info_style.cloneFrom(game->default_set_style);
|
||||
}
|
||||
REFLECT(set_info_style);
|
||||
REFLECT(card_style);
|
||||
}
|
||||
// extra card fields
|
||||
REFLECT(extra_card_fields);
|
||||
REFLECT_IF_READING {
|
||||
if (extra_card_style.init(extra_card_fields)) {
|
||||
// if a value is not editable, don't save it
|
||||
FOR_EACH(f, extra_card_fields) {
|
||||
if (!f->editable) f->save_value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
REFLECT(extra_card_style);
|
||||
// < 0.3.0 didn't use card_ prefix
|
||||
REFLECT_ALIAS(300, "width", "card width");
|
||||
REFLECT_ALIAS(300, "height", "card height");
|
||||
REFLECT_ALIAS(300, "dpi", "card dpi");
|
||||
REFLECT_ALIAS(300, "background", "card background");
|
||||
REFLECT_ALIAS(300, "info style", "set info style");
|
||||
REFLECT_ALIAS(300, "align", "alignment");
|
||||
REFLECT_ALIAS(300, "extra field", "styling field");
|
||||
REFLECT_ALIAS(300, "extra style", "styling style");
|
||||
|
||||
REFLECT(game);
|
||||
REFLECT_BASE(Packaged);
|
||||
REFLECT(card_width);
|
||||
REFLECT(card_height);
|
||||
REFLECT(card_dpi);
|
||||
REFLECT(card_background);
|
||||
REFLECT(init_script);
|
||||
// styling
|
||||
REFLECT(styling_fields);
|
||||
REFLECT_IF_READING styling_style.init(styling_fields);
|
||||
REFLECT(styling_style);
|
||||
// style of game fields
|
||||
if (game) {
|
||||
REFLECT_IF_READING {
|
||||
card_style.init(game->card_fields);
|
||||
set_info_style.cloneFrom(game->default_set_style);
|
||||
}
|
||||
REFLECT(set_info_style);
|
||||
REFLECT(card_style);
|
||||
}
|
||||
// extra card fields
|
||||
REFLECT(extra_card_fields);
|
||||
REFLECT_IF_READING {
|
||||
if (extra_card_style.init(extra_card_fields)) {
|
||||
// if a value is not editable, don't save it
|
||||
FOR_EACH(f, extra_card_fields) {
|
||||
if (!f->editable) f->save_value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
REFLECT(extra_card_style);
|
||||
}
|
||||
|
||||
|
||||
// special behaviour of reading/writing StyleSheetPs: only read/write the name
|
||||
|
||||
void Reader::handle(StyleSheetP& stylesheet) {
|
||||
if (!game_for_reading()) {
|
||||
throw InternalError(_("game_for_reading not set"));
|
||||
}
|
||||
stylesheet = StyleSheet::byGameAndName(*game_for_reading(), getValue());
|
||||
if (!game_for_reading()) {
|
||||
throw InternalError(_("game_for_reading not set"));
|
||||
}
|
||||
stylesheet = StyleSheet::byGameAndName(*game_for_reading(), getValue());
|
||||
}
|
||||
void Writer::handle(const StyleSheetP& stylesheet) {
|
||||
if (stylesheet) handle(stylesheet->stylesheetName());
|
||||
if (stylesheet) handle(stylesheet->stylesheetName());
|
||||
}
|
||||
|
||||
+44
-44
@@ -27,54 +27,54 @@ DECLARE_DYNAMIC_ARG(StyleSheet*, stylesheet_for_reading);
|
||||
/// A collection of style information for card and set fields
|
||||
class StyleSheet : public Packaged {
|
||||
public:
|
||||
StyleSheet();
|
||||
|
||||
GameP game; ///< The game this stylesheet is made for
|
||||
OptionalScript init_script; ///< Script of variables available to other scripts in this stylesheet
|
||||
double card_width; ///< The width of a card in pixels
|
||||
double card_height; ///< The height of a card in pixels
|
||||
double card_dpi; ///< The resolution of a card in dots per inch
|
||||
Color card_background; ///< The background color of cards
|
||||
/// The styling for card fields
|
||||
/** The indices should correspond to the card_fields in the Game */
|
||||
IndexMap<FieldP, StyleP> card_style;
|
||||
/// The styling for set info fields
|
||||
/** The indices should correspond to the set_fields in the Game */
|
||||
IndexMap<FieldP, StyleP> set_info_style;
|
||||
/// Extra card fields for boxes and borders
|
||||
vector<FieldP> extra_card_fields;
|
||||
IndexMap<FieldP, StyleP> extra_card_style;
|
||||
/// Extra fields for styling options
|
||||
vector<FieldP> styling_fields;
|
||||
/// The styling for the extra set fields
|
||||
/** The indices should correspond to the styling_fields */
|
||||
IndexMap<FieldP, StyleP> styling_style;
|
||||
|
||||
bool dependencies_initialized; ///< are the script dependencies comming from this stylesheet all initialized?
|
||||
|
||||
inline RealRect getCardRect() const { return RealRect(0, 0, card_width, card_height); }
|
||||
|
||||
/// Return the style for a given field, it is not specified what type of field this is.
|
||||
StyleP styleFor(const FieldP& field);
|
||||
|
||||
/// Load a StyleSheet, given a Game and the name of the StyleSheet
|
||||
static StyleSheetP byGameAndName(const Game& game, const String& name);
|
||||
/// name of the package without the game name
|
||||
String stylesheetName() const;
|
||||
|
||||
static String typeNameStatic();
|
||||
virtual String typeName() const;
|
||||
Version fileVersion() const;
|
||||
/// Validate the stylesheet
|
||||
virtual void validate(Version = app_version);
|
||||
|
||||
StyleSheet();
|
||||
|
||||
GameP game; ///< The game this stylesheet is made for
|
||||
OptionalScript init_script; ///< Script of variables available to other scripts in this stylesheet
|
||||
double card_width; ///< The width of a card in pixels
|
||||
double card_height; ///< The height of a card in pixels
|
||||
double card_dpi; ///< The resolution of a card in dots per inch
|
||||
Color card_background; ///< The background color of cards
|
||||
/// The styling for card fields
|
||||
/** The indices should correspond to the card_fields in the Game */
|
||||
IndexMap<FieldP, StyleP> card_style;
|
||||
/// The styling for set info fields
|
||||
/** The indices should correspond to the set_fields in the Game */
|
||||
IndexMap<FieldP, StyleP> set_info_style;
|
||||
/// Extra card fields for boxes and borders
|
||||
vector<FieldP> extra_card_fields;
|
||||
IndexMap<FieldP, StyleP> extra_card_style;
|
||||
/// Extra fields for styling options
|
||||
vector<FieldP> styling_fields;
|
||||
/// The styling for the extra set fields
|
||||
/** The indices should correspond to the styling_fields */
|
||||
IndexMap<FieldP, StyleP> styling_style;
|
||||
|
||||
bool dependencies_initialized; ///< are the script dependencies comming from this stylesheet all initialized?
|
||||
|
||||
inline RealRect getCardRect() const { return RealRect(0, 0, card_width, card_height); }
|
||||
|
||||
/// Return the style for a given field, it is not specified what type of field this is.
|
||||
StyleP styleFor(const FieldP& field);
|
||||
|
||||
/// Load a StyleSheet, given a Game and the name of the StyleSheet
|
||||
static StyleSheetP byGameAndName(const Game& game, const String& name);
|
||||
/// name of the package without the game name
|
||||
String stylesheetName() const;
|
||||
|
||||
static String typeNameStatic();
|
||||
virtual String typeName() const;
|
||||
Version fileVersion() const;
|
||||
/// Validate the stylesheet
|
||||
virtual void validate(Version = app_version);
|
||||
|
||||
protected:
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
inline String type_name(const StyleSheet&) {
|
||||
return _TYPE_("stylesheet");
|
||||
return _TYPE_("stylesheet");
|
||||
}
|
||||
|
||||
void mark_dependency_value(const StyleSheet& value, const Dependency& dep);
|
||||
|
||||
+222
-222
@@ -18,359 +18,359 @@ DECLARE_TYPEOF_COLLECTION(SymbolPartP);
|
||||
// ----------------------------------------------------------------------------- : ControlPoint
|
||||
|
||||
IMPLEMENT_REFLECTION_ENUM(LockMode) {
|
||||
VALUE_N("free", LOCK_FREE);
|
||||
VALUE_N("direction", LOCK_DIR);
|
||||
VALUE_N("size", LOCK_SIZE);
|
||||
VALUE_N("free", LOCK_FREE);
|
||||
VALUE_N("direction", LOCK_DIR);
|
||||
VALUE_N("size", LOCK_SIZE);
|
||||
}
|
||||
IMPLEMENT_REFLECTION_ENUM(SegmentMode) {
|
||||
VALUE_N("line", SEGMENT_LINE);
|
||||
VALUE_N("curve", SEGMENT_CURVE);
|
||||
VALUE_N("line", SEGMENT_LINE);
|
||||
VALUE_N("curve", SEGMENT_CURVE);
|
||||
}
|
||||
IMPLEMENT_REFLECTION(ControlPoint) {
|
||||
REFLECT_N("position", pos);
|
||||
REFLECT_N("lock", lock);
|
||||
REFLECT_N("line_after", segment_after);
|
||||
if (tag.reading() || segment_before == SEGMENT_CURVE) {
|
||||
REFLECT_N("handle_before", delta_before);
|
||||
}
|
||||
if (tag.reading() || segment_after == SEGMENT_CURVE) {
|
||||
REFLECT_N("handle_after", delta_after);
|
||||
}
|
||||
REFLECT_N("position", pos);
|
||||
REFLECT_N("lock", lock);
|
||||
REFLECT_N("line_after", segment_after);
|
||||
if (tag.reading() || segment_before == SEGMENT_CURVE) {
|
||||
REFLECT_N("handle_before", delta_before);
|
||||
}
|
||||
if (tag.reading() || segment_after == SEGMENT_CURVE) {
|
||||
REFLECT_N("handle_after", delta_after);
|
||||
}
|
||||
}
|
||||
|
||||
ControlPoint::ControlPoint()
|
||||
: segment_before(SEGMENT_LINE), segment_after(SEGMENT_LINE)
|
||||
, lock(LOCK_FREE)
|
||||
: segment_before(SEGMENT_LINE), segment_after(SEGMENT_LINE)
|
||||
, lock(LOCK_FREE)
|
||||
{}
|
||||
ControlPoint::ControlPoint(double x, double y)
|
||||
: pos(x,y)
|
||||
, segment_before(SEGMENT_LINE), segment_after(SEGMENT_LINE)
|
||||
, lock(LOCK_FREE)
|
||||
: pos(x,y)
|
||||
, segment_before(SEGMENT_LINE), segment_after(SEGMENT_LINE)
|
||||
, lock(LOCK_FREE)
|
||||
{}
|
||||
ControlPoint::ControlPoint(double x, double y, double xb, double yb, double xa, double ya, LockMode lock)
|
||||
: pos(x,y)
|
||||
, delta_before(xb,yb)
|
||||
, delta_after(xa,ya)
|
||||
, segment_before(SEGMENT_CURVE), segment_after(SEGMENT_CURVE)
|
||||
, lock(lock)
|
||||
: pos(x,y)
|
||||
, delta_before(xb,yb)
|
||||
, delta_after(xa,ya)
|
||||
, segment_before(SEGMENT_CURVE), segment_after(SEGMENT_CURVE)
|
||||
, lock(lock)
|
||||
{}
|
||||
|
||||
void ControlPoint::onUpdateHandle(WhichHandle wh) {
|
||||
// One handle has changed, update only the other one
|
||||
if (lock == LOCK_DIR) {
|
||||
getOther(wh) = -getHandle(wh) * getOther(wh).length() / getHandle(wh).length();
|
||||
} else if (lock == LOCK_SIZE) {
|
||||
getOther(wh) = -getHandle(wh);
|
||||
}
|
||||
// One handle has changed, update only the other one
|
||||
if (lock == LOCK_DIR) {
|
||||
getOther(wh) = -getHandle(wh) * getOther(wh).length() / getHandle(wh).length();
|
||||
} else if (lock == LOCK_SIZE) {
|
||||
getOther(wh) = -getHandle(wh);
|
||||
}
|
||||
}
|
||||
void ControlPoint::onUpdateLock() {
|
||||
// The lock has changed, avarage the handle values
|
||||
if (lock == LOCK_DIR) {
|
||||
// delta_before = x * delta_after
|
||||
Vector2D dir = (delta_before - delta_after).normalized();
|
||||
delta_before = dir * delta_before.length();
|
||||
delta_after = dir * -delta_after.length();
|
||||
} else if (lock == LOCK_SIZE) {
|
||||
// delta_before = -delta_after
|
||||
delta_before = (delta_before - delta_after) * 0.5;
|
||||
delta_after = -delta_before;
|
||||
}
|
||||
// The lock has changed, avarage the handle values
|
||||
if (lock == LOCK_DIR) {
|
||||
// delta_before = x * delta_after
|
||||
Vector2D dir = (delta_before - delta_after).normalized();
|
||||
delta_before = dir * delta_before.length();
|
||||
delta_after = dir * -delta_after.length();
|
||||
} else if (lock == LOCK_SIZE) {
|
||||
// delta_before = -delta_after
|
||||
delta_before = (delta_before - delta_after) * 0.5;
|
||||
delta_after = -delta_before;
|
||||
}
|
||||
}
|
||||
|
||||
Vector2D& ControlPoint::getHandle(WhichHandle wh) {
|
||||
if (wh == HANDLE_BEFORE) {
|
||||
return delta_before;
|
||||
} else {
|
||||
assert(wh == HANDLE_AFTER);
|
||||
return delta_after;
|
||||
}
|
||||
if (wh == HANDLE_BEFORE) {
|
||||
return delta_before;
|
||||
} else {
|
||||
assert(wh == HANDLE_AFTER);
|
||||
return delta_after;
|
||||
}
|
||||
}
|
||||
Vector2D& ControlPoint::getOther(WhichHandle wh) {
|
||||
if (wh == HANDLE_BEFORE) {
|
||||
return delta_after;
|
||||
} else {
|
||||
assert(wh == HANDLE_AFTER);
|
||||
return delta_before;
|
||||
}
|
||||
if (wh == HANDLE_BEFORE) {
|
||||
return delta_after;
|
||||
} else {
|
||||
assert(wh == HANDLE_AFTER);
|
||||
return delta_before;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Bounds
|
||||
|
||||
void Bounds::update(const Vector2D& p) {
|
||||
min = piecewise_min(min, p);
|
||||
max = piecewise_max(max, p);
|
||||
min = piecewise_min(min, p);
|
||||
max = piecewise_max(max, p);
|
||||
}
|
||||
void Bounds::update(const Bounds& b) {
|
||||
min = piecewise_min(min, b.min);
|
||||
max = piecewise_max(max, b.max);
|
||||
min = piecewise_min(min, b.min);
|
||||
max = piecewise_max(max, b.max);
|
||||
}
|
||||
|
||||
bool Bounds::contains(const Vector2D& p) const {
|
||||
return p.x >= min.x && p.y >= min.y &&
|
||||
p.x <= max.x && p.y <= max.y;
|
||||
return p.x >= min.x && p.y >= min.y &&
|
||||
p.x <= max.x && p.y <= max.y;
|
||||
}
|
||||
bool Bounds::contains(const Bounds& b) const {
|
||||
return b.min.x >= min.x && b.min.y >= min.y &&
|
||||
b.max.x <= max.x && b.max.y <= max.y;
|
||||
return b.min.x >= min.x && b.min.y >= min.y &&
|
||||
b.max.x <= max.x && b.max.y <= max.y;
|
||||
}
|
||||
|
||||
Vector2D Bounds::corner(int dx, int dy) const {
|
||||
return Vector2D(
|
||||
0.5 * (min.x + max.x + dx * (max.x - min.x)),
|
||||
0.5 * (min.y + max.y + dy * (max.y - min.y)));
|
||||
return Vector2D(
|
||||
0.5 * (min.x + max.x + dx * (max.x - min.x)),
|
||||
0.5 * (min.y + max.y + dy * (max.y - min.y)));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : SymbolPart
|
||||
|
||||
void SymbolPart::updateBounds() {
|
||||
calculateBounds(Vector2D(), Matrix2D(), true);
|
||||
calculateBounds(Vector2D(), Matrix2D(), true);
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION(SymbolPart) {
|
||||
REFLECT_IF_NOT_READING {
|
||||
String type = typeName();
|
||||
REFLECT(type);
|
||||
}
|
||||
REFLECT(name);
|
||||
REFLECT_IF_NOT_READING {
|
||||
String type = typeName();
|
||||
REFLECT(type);
|
||||
}
|
||||
REFLECT(name);
|
||||
}
|
||||
|
||||
template <>
|
||||
SymbolPartP read_new<SymbolPart>(Reader& reader) {
|
||||
// there must be a type specified
|
||||
String type;
|
||||
reader.handle(_("type"), type);
|
||||
if (type == _("shape") || type.empty()) return intrusive(new SymbolShape);
|
||||
else if (type == _("symmetry")) return intrusive(new SymbolSymmetry);
|
||||
else if (type == _("group")) return intrusive(new SymbolGroup);
|
||||
else {
|
||||
throw ParseError(_("Unsupported symbol part type: '") + type + _("'"));
|
||||
}
|
||||
// there must be a type specified
|
||||
String type;
|
||||
reader.handle(_("type"), type);
|
||||
if (type == _("shape") || type.empty()) return intrusive(new SymbolShape);
|
||||
else if (type == _("symmetry")) return intrusive(new SymbolSymmetry);
|
||||
else if (type == _("group")) return intrusive(new SymbolGroup);
|
||||
else {
|
||||
throw ParseError(_("Unsupported symbol part type: '") + type + _("'"));
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : SymbolShape
|
||||
|
||||
IMPLEMENT_REFLECTION_ENUM(SymbolShapeCombine) {
|
||||
VALUE_N("overlap", SYMBOL_COMBINE_OVERLAP);
|
||||
VALUE_N("merge", SYMBOL_COMBINE_MERGE);
|
||||
VALUE_N("subtract", SYMBOL_COMBINE_SUBTRACT);
|
||||
VALUE_N("intersection", SYMBOL_COMBINE_INTERSECTION);
|
||||
VALUE_N("difference", SYMBOL_COMBINE_DIFFERENCE);
|
||||
VALUE_N("border", SYMBOL_COMBINE_BORDER);
|
||||
VALUE_N("overlap", SYMBOL_COMBINE_OVERLAP);
|
||||
VALUE_N("merge", SYMBOL_COMBINE_MERGE);
|
||||
VALUE_N("subtract", SYMBOL_COMBINE_SUBTRACT);
|
||||
VALUE_N("intersection", SYMBOL_COMBINE_INTERSECTION);
|
||||
VALUE_N("difference", SYMBOL_COMBINE_DIFFERENCE);
|
||||
VALUE_N("border", SYMBOL_COMBINE_BORDER);
|
||||
}
|
||||
|
||||
|
||||
template<typename T> void fix(const T&,SymbolShape&) {}
|
||||
void fix(const Reader& reader, SymbolShape& shape) {
|
||||
if (reader.file_app_version != Version()) return;
|
||||
shape.updateBounds();
|
||||
if (shape.bounds.max.x < 100 || shape.bounds.max.y < 100) return;
|
||||
// this is a <= 0.1.2 symbol, points range [0...500] instead of [0...1]
|
||||
// adjust it
|
||||
FOR_EACH(p, shape.points) {
|
||||
p->pos /= 500.0;
|
||||
p->delta_before /= 500.0;
|
||||
p->delta_after /= 500.0;
|
||||
}
|
||||
if (shape.name.empty()) shape.name = _("Shape");
|
||||
shape.updateBounds();
|
||||
if (reader.file_app_version != Version()) return;
|
||||
shape.updateBounds();
|
||||
if (shape.bounds.max.x < 100 || shape.bounds.max.y < 100) return;
|
||||
// this is a <= 0.1.2 symbol, points range [0...500] instead of [0...1]
|
||||
// adjust it
|
||||
FOR_EACH(p, shape.points) {
|
||||
p->pos /= 500.0;
|
||||
p->delta_before /= 500.0;
|
||||
p->delta_after /= 500.0;
|
||||
}
|
||||
if (shape.name.empty()) shape.name = _("Shape");
|
||||
shape.updateBounds();
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION(SymbolShape) {
|
||||
REFLECT_BASE(SymbolPart);
|
||||
REFLECT(combine);
|
||||
REFLECT(points);
|
||||
// Fixes after reading
|
||||
REFLECT_IF_READING {
|
||||
// enforce constraints
|
||||
enforceConstraints();
|
||||
fix(tag,*this);
|
||||
}
|
||||
REFLECT_BASE(SymbolPart);
|
||||
REFLECT(combine);
|
||||
REFLECT(points);
|
||||
// Fixes after reading
|
||||
REFLECT_IF_READING {
|
||||
// enforce constraints
|
||||
enforceConstraints();
|
||||
fix(tag,*this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SymbolShape::SymbolShape()
|
||||
: combine(SYMBOL_COMBINE_OVERLAP), rotation_center(.5, .5)
|
||||
: combine(SYMBOL_COMBINE_OVERLAP), rotation_center(.5, .5)
|
||||
{}
|
||||
|
||||
String SymbolShape::typeName() const {
|
||||
return _("shape");
|
||||
return _("shape");
|
||||
}
|
||||
|
||||
SymbolPartP SymbolShape::clone() const {
|
||||
SymbolShapeP part(new SymbolShape(*this));
|
||||
// also clone the control points
|
||||
FOR_EACH(p, part->points) {
|
||||
p = intrusive(new ControlPoint(*p));
|
||||
}
|
||||
return part;
|
||||
SymbolShapeP part(new SymbolShape(*this));
|
||||
// also clone the control points
|
||||
FOR_EACH(p, part->points) {
|
||||
p = intrusive(new ControlPoint(*p));
|
||||
}
|
||||
return part;
|
||||
}
|
||||
|
||||
void SymbolShape::enforceConstraints() {
|
||||
for (int i = 0 ; i < (int)points.size() ; ++i) {
|
||||
ControlPointP p1 = getPoint(i);
|
||||
ControlPointP p2 = getPoint(i + 1);
|
||||
p2->segment_before = p1->segment_after;
|
||||
p1->onUpdateLock();
|
||||
}
|
||||
for (int i = 0 ; i < (int)points.size() ; ++i) {
|
||||
ControlPointP p1 = getPoint(i);
|
||||
ControlPointP p2 = getPoint(i + 1);
|
||||
p2->segment_before = p1->segment_after;
|
||||
p1->onUpdateLock();
|
||||
}
|
||||
}
|
||||
|
||||
Bounds SymbolShape::calculateBounds(const Vector2D& origin, const Matrix2D& m, bool is_identity) {
|
||||
Bounds bounds;
|
||||
for (int i = 0 ; i < (int)points.size() ; ++i) {
|
||||
bounds.update(segment_bounds(origin, m, *getPoint(i), *getPoint(i + 1)));
|
||||
}
|
||||
if (is_identity) this->bounds = bounds;
|
||||
return bounds;
|
||||
Bounds bounds;
|
||||
for (int i = 0 ; i < (int)points.size() ; ++i) {
|
||||
bounds.update(segment_bounds(origin, m, *getPoint(i), *getPoint(i + 1)));
|
||||
}
|
||||
if (is_identity) this->bounds = bounds;
|
||||
return bounds;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : SymbolSymmetry
|
||||
|
||||
IMPLEMENT_REFLECTION_ENUM(SymbolSymmetryType) {
|
||||
VALUE_N("rotation", SYMMETRY_ROTATION);
|
||||
VALUE_N("reflection", SYMMETRY_REFLECTION);
|
||||
VALUE_N("rotation", SYMMETRY_ROTATION);
|
||||
VALUE_N("reflection", SYMMETRY_REFLECTION);
|
||||
}
|
||||
|
||||
SymbolSymmetry::SymbolSymmetry()
|
||||
: kind(SYMMETRY_ROTATION), copies(2)
|
||||
: kind(SYMMETRY_ROTATION), copies(2)
|
||||
{}
|
||||
|
||||
String SymbolSymmetry::typeName() const {
|
||||
return _("symmetry");
|
||||
return _("symmetry");
|
||||
}
|
||||
|
||||
SymbolPartP SymbolSymmetry::clone() const {
|
||||
SymbolSymmetryP part(new SymbolSymmetry(*this));
|
||||
// also clone the parts inside
|
||||
FOR_EACH(p, part->parts) {
|
||||
p = p->clone();
|
||||
}
|
||||
return part;
|
||||
SymbolSymmetryP part(new SymbolSymmetry(*this));
|
||||
// also clone the parts inside
|
||||
FOR_EACH(p, part->parts) {
|
||||
p = p->clone();
|
||||
}
|
||||
return part;
|
||||
}
|
||||
|
||||
String SymbolSymmetry::expectedName() const {
|
||||
return capitalize(kind == SYMMETRY_ROTATION ? _TYPE_("rotation") : _TYPE_("reflection"))
|
||||
+ String::Format(_(" (%d)"), copies);
|
||||
return capitalize(kind == SYMMETRY_ROTATION ? _TYPE_("rotation") : _TYPE_("reflection"))
|
||||
+ String::Format(_(" (%d)"), copies);
|
||||
}
|
||||
|
||||
Bounds SymbolSymmetry::calculateBounds(const Vector2D& origin, const Matrix2D& m, bool is_identity) {
|
||||
Bounds bounds;
|
||||
// See SymbolViewer::draw
|
||||
Radians b = 2 * handle.angle();
|
||||
int copies = kind == SYMMETRY_REFLECTION ? this->copies & ~1 : this->copies;
|
||||
FOR_EACH_CONST(p, parts) {
|
||||
for (int i = 0 ; i < copies ; ++i) {
|
||||
double a = i * 2 * M_PI / copies;
|
||||
if (kind == SYMMETRY_ROTATION || i % 2 == 0) {
|
||||
Matrix2D rot(cos(a),-sin(a), sin(a),cos(a));
|
||||
bounds.update(
|
||||
p->calculateBounds(origin + (center - center*rot) * m, rot * m, is_identity && i == 0)
|
||||
);
|
||||
} else {
|
||||
Matrix2D rot(cos(a+b),sin(a+b), sin(a+b),-cos(a+b));
|
||||
bounds.update(
|
||||
p->calculateBounds(origin + (center - center*rot) * m, rot * m, is_identity && i == 0)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
// done
|
||||
if (is_identity) this->bounds = bounds;
|
||||
return bounds;
|
||||
Bounds bounds;
|
||||
// See SymbolViewer::draw
|
||||
Radians b = 2 * handle.angle();
|
||||
int copies = kind == SYMMETRY_REFLECTION ? this->copies & ~1 : this->copies;
|
||||
FOR_EACH_CONST(p, parts) {
|
||||
for (int i = 0 ; i < copies ; ++i) {
|
||||
double a = i * 2 * M_PI / copies;
|
||||
if (kind == SYMMETRY_ROTATION || i % 2 == 0) {
|
||||
Matrix2D rot(cos(a),-sin(a), sin(a),cos(a));
|
||||
bounds.update(
|
||||
p->calculateBounds(origin + (center - center*rot) * m, rot * m, is_identity && i == 0)
|
||||
);
|
||||
} else {
|
||||
Matrix2D rot(cos(a+b),sin(a+b), sin(a+b),-cos(a+b));
|
||||
bounds.update(
|
||||
p->calculateBounds(origin + (center - center*rot) * m, rot * m, is_identity && i == 0)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
// done
|
||||
if (is_identity) this->bounds = bounds;
|
||||
return bounds;
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION(SymbolSymmetry) {
|
||||
REFLECT_BASE(SymbolPart);
|
||||
REFLECT(kind);
|
||||
REFLECT(copies);
|
||||
REFLECT(center);
|
||||
REFLECT(handle);
|
||||
REFLECT(parts);
|
||||
REFLECT_BASE(SymbolPart);
|
||||
REFLECT(kind);
|
||||
REFLECT(copies);
|
||||
REFLECT(center);
|
||||
REFLECT(handle);
|
||||
REFLECT(parts);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : SymbolGroup
|
||||
|
||||
SymbolGroup::SymbolGroup() {
|
||||
name = capitalize(_TYPE_("group"));
|
||||
name = capitalize(_TYPE_("group"));
|
||||
}
|
||||
|
||||
String SymbolGroup::typeName() const {
|
||||
return _("group");
|
||||
return _("group");
|
||||
}
|
||||
|
||||
SymbolPartP SymbolGroup::clone() const {
|
||||
SymbolGroupP part(new SymbolGroup(*this));
|
||||
// also clone the parts inside
|
||||
FOR_EACH(p, part->parts) {
|
||||
p = p->clone();
|
||||
}
|
||||
return part;
|
||||
SymbolGroupP part(new SymbolGroup(*this));
|
||||
// also clone the parts inside
|
||||
FOR_EACH(p, part->parts) {
|
||||
p = p->clone();
|
||||
}
|
||||
return part;
|
||||
}
|
||||
|
||||
bool SymbolGroup::isAncestor(const SymbolPart& that) const {
|
||||
if (this == &that) return true;
|
||||
FOR_EACH_CONST(p, parts) {
|
||||
if (p->isAncestor(that)) return true;
|
||||
}
|
||||
return false;
|
||||
if (this == &that) return true;
|
||||
FOR_EACH_CONST(p, parts) {
|
||||
if (p->isAncestor(that)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Bounds SymbolGroup::calculateBounds(const Vector2D& origin, const Matrix2D& m, bool is_identity) {
|
||||
Bounds bounds;
|
||||
FOR_EACH(p, parts) {
|
||||
bounds.update(p->calculateBounds(origin, m, is_identity));
|
||||
}
|
||||
if (is_identity) this->bounds = bounds;
|
||||
return bounds;
|
||||
Bounds bounds;
|
||||
FOR_EACH(p, parts) {
|
||||
bounds.update(p->calculateBounds(origin, m, is_identity));
|
||||
}
|
||||
if (is_identity) this->bounds = bounds;
|
||||
return bounds;
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION(SymbolGroup) {
|
||||
REFLECT_BASE(SymbolPart);
|
||||
REFLECT(parts);
|
||||
REFLECT_BASE(SymbolPart);
|
||||
REFLECT(parts);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Symbol
|
||||
|
||||
IMPLEMENT_REFLECTION(Symbol) {
|
||||
REFLECT(parts);
|
||||
REFLECT_IF_READING updateBounds();
|
||||
REFLECT(parts);
|
||||
REFLECT_IF_READING updateBounds();
|
||||
}
|
||||
|
||||
double Symbol::aspectRatio() const {
|
||||
// Margin between the edges and the symbol.
|
||||
// In each direction take the lowest one
|
||||
// This is at most 0.5 (if the symbol is just a line in the middle)
|
||||
// Multiply by 2 (below) to give something in the range [0...1] i.e. [touches the edge...only in the middle]
|
||||
double margin_x = min(0.4999, max(0., min(bounds.min.x, 1-bounds.max.x)));
|
||||
double margin_y = min(0.4999, max(0., min(bounds.min.y, 1-bounds.max.y)));
|
||||
// The difference between these two,
|
||||
// e.g. if the vertical margin is more then the horizontal one, the symbol is 'flat'
|
||||
double delta = 2 * (margin_y - margin_x);
|
||||
// The aspect ratio, i.e. width/height
|
||||
if (delta > 0) {
|
||||
return 1 / (1 - delta);
|
||||
} else {
|
||||
return 1 + delta;
|
||||
}
|
||||
// Margin between the edges and the symbol.
|
||||
// In each direction take the lowest one
|
||||
// This is at most 0.5 (if the symbol is just a line in the middle)
|
||||
// Multiply by 2 (below) to give something in the range [0...1] i.e. [touches the edge...only in the middle]
|
||||
double margin_x = min(0.4999, max(0., min(bounds.min.x, 1-bounds.max.x)));
|
||||
double margin_y = min(0.4999, max(0., min(bounds.min.y, 1-bounds.max.y)));
|
||||
// The difference between these two,
|
||||
// e.g. if the vertical margin is more then the horizontal one, the symbol is 'flat'
|
||||
double delta = 2 * (margin_y - margin_x);
|
||||
// The aspect ratio, i.e. width/height
|
||||
if (delta > 0) {
|
||||
return 1 / (1 - delta);
|
||||
} else {
|
||||
return 1 + delta;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Default symbol
|
||||
|
||||
// A default symbol part, a square, moved by d
|
||||
SymbolShapeP default_symbol_part(double d) {
|
||||
SymbolShapeP part = intrusive(new SymbolShape);
|
||||
part->points.push_back(intrusive(new ControlPoint(d + .2, d + .2)));
|
||||
part->points.push_back(intrusive(new ControlPoint(d + .2, d + .8)));
|
||||
part->points.push_back(intrusive(new ControlPoint(d + .8, d + .8)));
|
||||
part->points.push_back(intrusive(new ControlPoint(d + .8, d + .2)));
|
||||
part->name = _("Square");
|
||||
return part;
|
||||
SymbolShapeP part = intrusive(new SymbolShape);
|
||||
part->points.push_back(intrusive(new ControlPoint(d + .2, d + .2)));
|
||||
part->points.push_back(intrusive(new ControlPoint(d + .2, d + .8)));
|
||||
part->points.push_back(intrusive(new ControlPoint(d + .8, d + .8)));
|
||||
part->points.push_back(intrusive(new ControlPoint(d + .8, d + .2)));
|
||||
part->name = _("Square");
|
||||
return part;
|
||||
}
|
||||
|
||||
// A default symbol, a square
|
||||
SymbolP default_symbol() {
|
||||
SymbolP symbol = intrusive(new Symbol);
|
||||
symbol->parts.push_back(default_symbol_part(0));
|
||||
return symbol;
|
||||
SymbolP symbol = intrusive(new Symbol);
|
||||
symbol->parts.push_back(default_symbol_part(0));
|
||||
return symbol;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : SymbolView
|
||||
@@ -378,14 +378,14 @@ SymbolP default_symbol() {
|
||||
SymbolView::SymbolView() {}
|
||||
|
||||
SymbolView::~SymbolView() {
|
||||
if (symbol) symbol->actions.removeListener(this);
|
||||
if (symbol) symbol->actions.removeListener(this);
|
||||
}
|
||||
|
||||
void SymbolView::setSymbol(const SymbolP& newSymbol) {
|
||||
// no longer listening to old symbol
|
||||
if (symbol) symbol->actions.removeListener(this);
|
||||
symbol = newSymbol;
|
||||
// start listening to new symbol
|
||||
if (symbol) symbol->actions.addListener(this);
|
||||
onChangeSymbol();
|
||||
// no longer listening to old symbol
|
||||
if (symbol) symbol->actions.removeListener(this);
|
||||
symbol = newSymbol;
|
||||
// start listening to new symbol
|
||||
if (symbol) symbol->actions.addListener(this);
|
||||
onChangeSymbol();
|
||||
}
|
||||
|
||||
+194
-194
@@ -28,52 +28,52 @@ DECLARE_POINTER_TYPE(Symbol);
|
||||
/** Specificly: the relation between delta_before and delta_after
|
||||
*/
|
||||
enum LockMode
|
||||
{ LOCK_FREE ///< no locking
|
||||
, LOCK_DIR ///< delta_before = x * delta_after
|
||||
, LOCK_SIZE ///< delta_before = -delta_after
|
||||
{ LOCK_FREE ///< no locking
|
||||
, LOCK_DIR ///< delta_before = x * delta_after
|
||||
, LOCK_SIZE ///< delta_before = -delta_after
|
||||
};
|
||||
|
||||
/// Is the segment between two ControlPoints a line or a curve?
|
||||
enum SegmentMode
|
||||
{ SEGMENT_LINE
|
||||
, SEGMENT_CURVE
|
||||
{ SEGMENT_LINE
|
||||
, SEGMENT_CURVE
|
||||
};
|
||||
|
||||
/// To refer to a specific handle of a control point
|
||||
enum WhichHandle
|
||||
{ HANDLE_NONE = 0 ///< point is not selected
|
||||
, HANDLE_MAIN
|
||||
, HANDLE_BEFORE
|
||||
, HANDLE_AFTER
|
||||
{ HANDLE_NONE = 0 ///< point is not selected
|
||||
, HANDLE_MAIN
|
||||
, HANDLE_BEFORE
|
||||
, HANDLE_AFTER
|
||||
};
|
||||
|
||||
/// A control point (corner) of a SymbolShape (polygon/bezier-gon)
|
||||
class ControlPoint : public IntrusivePtrBase<ControlPoint> {
|
||||
public:
|
||||
Vector2D pos; ///< position of the control point itself
|
||||
Vector2D delta_before; ///< delta to bezier control point, for curve before point
|
||||
Vector2D delta_after; ///< delta to bezier control point, for curve after point
|
||||
SegmentMode segment_before, segment_after;
|
||||
LockMode lock;
|
||||
|
||||
/// Default constructor
|
||||
ControlPoint();
|
||||
/// Constructor for straight lines, takes only the position
|
||||
ControlPoint(double x, double y);
|
||||
/// Constructor for curves lines, takes postions, delta_before, delta_after and lock mode
|
||||
ControlPoint(double x, double y, double xb, double yb, double xa, double ya, LockMode lock = LOCK_FREE);
|
||||
|
||||
/// Must be called after delta_before/delta_after has changed, enforces lock constraints
|
||||
void onUpdateHandle(WhichHandle wh);
|
||||
/// Must be called after lock has changed, enforces lock constraints
|
||||
void onUpdateLock();
|
||||
Vector2D pos; ///< position of the control point itself
|
||||
Vector2D delta_before; ///< delta to bezier control point, for curve before point
|
||||
Vector2D delta_after; ///< delta to bezier control point, for curve after point
|
||||
SegmentMode segment_before, segment_after;
|
||||
LockMode lock;
|
||||
|
||||
/// Default constructor
|
||||
ControlPoint();
|
||||
/// Constructor for straight lines, takes only the position
|
||||
ControlPoint(double x, double y);
|
||||
/// Constructor for curves lines, takes postions, delta_before, delta_after and lock mode
|
||||
ControlPoint(double x, double y, double xb, double yb, double xa, double ya, LockMode lock = LOCK_FREE);
|
||||
|
||||
/// Must be called after delta_before/delta_after has changed, enforces lock constraints
|
||||
void onUpdateHandle(WhichHandle wh);
|
||||
/// Must be called after lock has changed, enforces lock constraints
|
||||
void onUpdateLock();
|
||||
|
||||
/// Get a handle of this control point
|
||||
Vector2D& getHandle(WhichHandle wh);
|
||||
/// Get a handle of this control point that is oposite wh
|
||||
Vector2D& getOther(WhichHandle wh);
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
/// Get a handle of this control point
|
||||
Vector2D& getHandle(WhichHandle wh);
|
||||
/// Get a handle of this control point that is oposite wh
|
||||
Vector2D& getOther(WhichHandle wh);
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
|
||||
@@ -82,27 +82,27 @@ class ControlPoint : public IntrusivePtrBase<ControlPoint> {
|
||||
/// A specific handle of a ControlPoint
|
||||
class SelectedHandle {
|
||||
public:
|
||||
ControlPointP point; ///< the selected point
|
||||
WhichHandle handle; ///< the selected handle of the point
|
||||
|
||||
// SelectedHandle
|
||||
SelectedHandle() : handle(HANDLE_NONE) {}
|
||||
SelectedHandle(const WhichHandle& handle) : handle(handle) { assert (handle == HANDLE_NONE); }
|
||||
SelectedHandle(const ControlPointP& point, const WhichHandle& handle) : point(point), handle(handle) {}
|
||||
|
||||
inline Vector2D& getHandle() const { return point->getHandle(handle); }
|
||||
inline Vector2D& getOther() const { return point->getOther (handle); }
|
||||
inline void onUpdateHandle() const { return point->onUpdateHandle(handle); }
|
||||
|
||||
/*
|
||||
bool operator == (const ControlPointP pnt) const;
|
||||
|
||||
bool SelectedHandle::operator == (const ControlPointP pnt) const { return point == pnt; }
|
||||
bool operator == (const WhichHandle& wh) const;
|
||||
bool SelectedHandle::operator == (const WhichHandle& wh) const { return handle == wh; }
|
||||
bool operator ! () const;
|
||||
bool SelectedHandle::operator ! () const { return handle == handleNone; }
|
||||
*/
|
||||
ControlPointP point; ///< the selected point
|
||||
WhichHandle handle; ///< the selected handle of the point
|
||||
|
||||
// SelectedHandle
|
||||
SelectedHandle() : handle(HANDLE_NONE) {}
|
||||
SelectedHandle(const WhichHandle& handle) : handle(handle) { assert (handle == HANDLE_NONE); }
|
||||
SelectedHandle(const ControlPointP& point, const WhichHandle& handle) : point(point), handle(handle) {}
|
||||
|
||||
inline Vector2D& getHandle() const { return point->getHandle(handle); }
|
||||
inline Vector2D& getOther() const { return point->getOther (handle); }
|
||||
inline void onUpdateHandle() const { return point->onUpdateHandle(handle); }
|
||||
|
||||
/*
|
||||
bool operator == (const ControlPointP pnt) const;
|
||||
|
||||
bool SelectedHandle::operator == (const ControlPointP pnt) const { return point == pnt; }
|
||||
bool operator == (const WhichHandle& wh) const;
|
||||
bool SelectedHandle::operator == (const WhichHandle& wh) const { return handle == wh; }
|
||||
bool operator ! () const;
|
||||
bool SelectedHandle::operator ! () const { return handle == handleNone; }
|
||||
*/
|
||||
};
|
||||
|
||||
|
||||
@@ -111,25 +111,25 @@ class SelectedHandle {
|
||||
/// Bounding box of a symbol part
|
||||
class Bounds {
|
||||
public:
|
||||
inline Bounds() : min(Vector2D::infinity()), max(-Vector2D::infinity()) {}
|
||||
inline explicit Bounds(const Vector2D& p) : min(p), max(p) {}
|
||||
inline Bounds(const Vector2D& min, const Vector2D& max) : min(min), max(max) {}
|
||||
|
||||
/// Combine with another bounding box
|
||||
void update(const Bounds& b);
|
||||
void update(const Vector2D& p);
|
||||
|
||||
/// Does this box contain the given point?
|
||||
bool contains(const Vector2D& p) const;
|
||||
/// Does this box contain the given rectangle?
|
||||
bool contains(const Bounds& b) const;
|
||||
|
||||
/// Corner or center of this bounding box, dx,dy in <-1, 0, 1>
|
||||
Vector2D corner(int dx, int dy) const;
|
||||
|
||||
Vector2D min, max;
|
||||
|
||||
inline operator RealRect () const { return RealRect(min, RealSize(max - min)); }
|
||||
inline Bounds() : min(Vector2D::infinity()), max(-Vector2D::infinity()) {}
|
||||
inline explicit Bounds(const Vector2D& p) : min(p), max(p) {}
|
||||
inline Bounds(const Vector2D& min, const Vector2D& max) : min(min), max(max) {}
|
||||
|
||||
/// Combine with another bounding box
|
||||
void update(const Bounds& b);
|
||||
void update(const Vector2D& p);
|
||||
|
||||
/// Does this box contain the given point?
|
||||
bool contains(const Vector2D& p) const;
|
||||
/// Does this box contain the given rectangle?
|
||||
bool contains(const Bounds& b) const;
|
||||
|
||||
/// Corner or center of this bounding box, dx,dy in <-1, 0, 1>
|
||||
Vector2D corner(int dx, int dy) const;
|
||||
|
||||
Vector2D min, max;
|
||||
|
||||
inline operator RealRect () const { return RealRect(min, RealSize(max - min)); }
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : SymbolPart
|
||||
@@ -137,39 +137,39 @@ class Bounds {
|
||||
/// A part of a symbol, not necesserly a shape
|
||||
class SymbolPart : public IntrusivePtrVirtualBase {
|
||||
public:
|
||||
/// Name/label for this part
|
||||
String name;
|
||||
/// Position and size of the part.
|
||||
/** this is the smallest axis aligned bounding box that fits around the part */
|
||||
Bounds bounds;
|
||||
|
||||
/// Type of this part
|
||||
virtual String typeName() const = 0;
|
||||
/// Create a clone of this symbol part
|
||||
virtual SymbolPartP clone() const = 0;
|
||||
/// Icon for this part
|
||||
virtual int icon() const = 0;
|
||||
|
||||
/// Convert to SymbolShape?
|
||||
virtual SymbolShape* isSymbolShape() { return nullptr; }
|
||||
virtual const SymbolShape* isSymbolShape() const { return nullptr; }
|
||||
/// Convert to SymbolSymmetry?
|
||||
virtual SymbolSymmetry* isSymbolSymmetry() { return nullptr; }
|
||||
virtual const SymbolSymmetry* isSymbolSymmetry() const { return nullptr; }
|
||||
/// Convert to SymbolGroup?
|
||||
virtual SymbolGroup* isSymbolGroup() { return nullptr; }
|
||||
virtual const SymbolGroup* isSymbolGroup() const { return nullptr; }
|
||||
|
||||
/// Does this part contain another?
|
||||
/** also true if this==that*/
|
||||
virtual bool isAncestor(const SymbolPart& that) const { return this == &that; }
|
||||
|
||||
/// Calculate the position and size of the part (bounds)
|
||||
virtual void updateBounds();
|
||||
/// Calculate the position and size of the part using the given rotation matrix
|
||||
virtual Bounds calculateBounds(const Vector2D& origin, const Matrix2D& m, bool is_identity) = 0;
|
||||
|
||||
DECLARE_REFLECTION_VIRTUAL();
|
||||
/// Name/label for this part
|
||||
String name;
|
||||
/// Position and size of the part.
|
||||
/** this is the smallest axis aligned bounding box that fits around the part */
|
||||
Bounds bounds;
|
||||
|
||||
/// Type of this part
|
||||
virtual String typeName() const = 0;
|
||||
/// Create a clone of this symbol part
|
||||
virtual SymbolPartP clone() const = 0;
|
||||
/// Icon for this part
|
||||
virtual int icon() const = 0;
|
||||
|
||||
/// Convert to SymbolShape?
|
||||
virtual SymbolShape* isSymbolShape() { return nullptr; }
|
||||
virtual const SymbolShape* isSymbolShape() const { return nullptr; }
|
||||
/// Convert to SymbolSymmetry?
|
||||
virtual SymbolSymmetry* isSymbolSymmetry() { return nullptr; }
|
||||
virtual const SymbolSymmetry* isSymbolSymmetry() const { return nullptr; }
|
||||
/// Convert to SymbolGroup?
|
||||
virtual SymbolGroup* isSymbolGroup() { return nullptr; }
|
||||
virtual const SymbolGroup* isSymbolGroup() const { return nullptr; }
|
||||
|
||||
/// Does this part contain another?
|
||||
/** also true if this==that*/
|
||||
virtual bool isAncestor(const SymbolPart& that) const { return this == &that; }
|
||||
|
||||
/// Calculate the position and size of the part (bounds)
|
||||
virtual void updateBounds();
|
||||
/// Calculate the position and size of the part using the given rotation matrix
|
||||
virtual Bounds calculateBounds(const Vector2D& origin, const Matrix2D& m, bool is_identity) = 0;
|
||||
|
||||
DECLARE_REFLECTION_VIRTUAL();
|
||||
};
|
||||
|
||||
template <> SymbolPartP read_new<SymbolPart>(Reader& reader);
|
||||
@@ -178,50 +178,50 @@ template <> SymbolPartP read_new<SymbolPart>(Reader& reader);
|
||||
|
||||
/// How are symbol parts combined with parts below it?
|
||||
enum SymbolShapeCombine
|
||||
{ SYMBOL_COMBINE_MERGE
|
||||
, SYMBOL_COMBINE_SUBTRACT
|
||||
, SYMBOL_COMBINE_INTERSECTION
|
||||
, SYMBOL_COMBINE_DIFFERENCE
|
||||
, SYMBOL_COMBINE_OVERLAP
|
||||
, SYMBOL_COMBINE_BORDER
|
||||
{ SYMBOL_COMBINE_MERGE
|
||||
, SYMBOL_COMBINE_SUBTRACT
|
||||
, SYMBOL_COMBINE_INTERSECTION
|
||||
, SYMBOL_COMBINE_DIFFERENCE
|
||||
, SYMBOL_COMBINE_OVERLAP
|
||||
, SYMBOL_COMBINE_BORDER
|
||||
};
|
||||
|
||||
/// A sane mod function, always returns a result in the range [0..size)
|
||||
inline size_t mod(int a, size_t size) {
|
||||
int m = a % (int)size;
|
||||
return m >= 0 ? m : m + size;
|
||||
int m = a % (int)size;
|
||||
return m >= 0 ? m : m + size;
|
||||
}
|
||||
|
||||
/// A single shape (polygon/bezier-gon) in a Symbol
|
||||
class SymbolShape : public SymbolPart {
|
||||
public:
|
||||
/// The points of this polygon
|
||||
vector<ControlPointP> points;
|
||||
/// How is this part combined with parts below it?
|
||||
SymbolShapeCombine combine;
|
||||
// Center of rotation, relative to the part, when the part is scaled to [0..1]
|
||||
Vector2D rotation_center;
|
||||
|
||||
SymbolShape();
|
||||
|
||||
virtual String typeName() const;
|
||||
virtual SymbolPartP clone() const;
|
||||
virtual int icon() const { return combine; }
|
||||
virtual SymbolShape* isSymbolShape() { return this; }
|
||||
virtual const SymbolShape* isSymbolShape() const { return this; }
|
||||
|
||||
/// Get a control point, wraps around
|
||||
inline ControlPointP getPoint(int id) const {
|
||||
return points[mod(id, points.size())];
|
||||
}
|
||||
|
||||
/// Enforce lock constraints
|
||||
void enforceConstraints();
|
||||
|
||||
/// Calculate the position and size of the part using the given rotation matrix
|
||||
virtual Bounds calculateBounds(const Vector2D& origin, const Matrix2D& m, bool is_identity);
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
/// The points of this polygon
|
||||
vector<ControlPointP> points;
|
||||
/// How is this part combined with parts below it?
|
||||
SymbolShapeCombine combine;
|
||||
// Center of rotation, relative to the part, when the part is scaled to [0..1]
|
||||
Vector2D rotation_center;
|
||||
|
||||
SymbolShape();
|
||||
|
||||
virtual String typeName() const;
|
||||
virtual SymbolPartP clone() const;
|
||||
virtual int icon() const { return combine; }
|
||||
virtual SymbolShape* isSymbolShape() { return this; }
|
||||
virtual const SymbolShape* isSymbolShape() const { return this; }
|
||||
|
||||
/// Get a control point, wraps around
|
||||
inline ControlPointP getPoint(int id) const {
|
||||
return points[mod(id, points.size())];
|
||||
}
|
||||
|
||||
/// Enforce lock constraints
|
||||
void enforceConstraints();
|
||||
|
||||
/// Calculate the position and size of the part using the given rotation matrix
|
||||
virtual Bounds calculateBounds(const Vector2D& origin, const Matrix2D& m, bool is_identity);
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : SymbolGroup
|
||||
@@ -229,52 +229,52 @@ class SymbolShape : public SymbolPart {
|
||||
/// A group of symbol parts
|
||||
class SymbolGroup : public SymbolPart {
|
||||
public:
|
||||
vector<SymbolPartP> parts; ///< The parts in this group, first item is on top
|
||||
|
||||
SymbolGroup();
|
||||
|
||||
virtual String typeName() const;
|
||||
virtual SymbolPartP clone() const;
|
||||
virtual int icon() const { return SYMBOL_COMBINE_BORDER + 3; }
|
||||
virtual SymbolGroup* isSymbolGroup() { return this; }
|
||||
virtual const SymbolGroup* isSymbolGroup() const { return this; }
|
||||
|
||||
virtual bool isAncestor(const SymbolPart& that) const;
|
||||
|
||||
virtual Bounds calculateBounds(const Vector2D& origin, const Matrix2D& m, bool is_identity);
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
vector<SymbolPartP> parts; ///< The parts in this group, first item is on top
|
||||
|
||||
SymbolGroup();
|
||||
|
||||
virtual String typeName() const;
|
||||
virtual SymbolPartP clone() const;
|
||||
virtual int icon() const { return SYMBOL_COMBINE_BORDER + 3; }
|
||||
virtual SymbolGroup* isSymbolGroup() { return this; }
|
||||
virtual const SymbolGroup* isSymbolGroup() const { return this; }
|
||||
|
||||
virtual bool isAncestor(const SymbolPart& that) const;
|
||||
|
||||
virtual Bounds calculateBounds(const Vector2D& origin, const Matrix2D& m, bool is_identity);
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : SymbolSymmetry
|
||||
|
||||
enum SymbolSymmetryType
|
||||
{ SYMMETRY_ROTATION
|
||||
, SYMMETRY_REFLECTION
|
||||
{ SYMMETRY_ROTATION
|
||||
, SYMMETRY_REFLECTION
|
||||
};
|
||||
|
||||
/// A mirror, reflecting the part of the symbol in the group
|
||||
/** Can handle rotation symmetry with any number of reflections */
|
||||
class SymbolSymmetry : public SymbolGroup {
|
||||
public:
|
||||
SymbolSymmetryType kind; ///< What kind of symmetry
|
||||
int copies; ///< How many times is the orignal reflected (including the original itself)
|
||||
bool clip; ///< Clip the orignal so it doesn't intersect the mirror(s)
|
||||
Vector2D center; ///< Center point of the mirror
|
||||
Vector2D handle; ///< A handle pointing in the direction of the original, relative to the center
|
||||
|
||||
SymbolSymmetry();
|
||||
|
||||
virtual String typeName() const;
|
||||
virtual SymbolPartP clone() const;
|
||||
virtual int icon() const { return kind + SYMBOL_COMBINE_BORDER + 1; }
|
||||
virtual SymbolSymmetry* isSymbolSymmetry() { return this; }
|
||||
virtual const SymbolSymmetry* isSymbolSymmetry() const { return this; }
|
||||
|
||||
String expectedName() const;
|
||||
virtual Bounds calculateBounds(const Vector2D& origin, const Matrix2D& m, bool is_identity);
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
SymbolSymmetryType kind; ///< What kind of symmetry
|
||||
int copies; ///< How many times is the orignal reflected (including the original itself)
|
||||
bool clip; ///< Clip the orignal so it doesn't intersect the mirror(s)
|
||||
Vector2D center; ///< Center point of the mirror
|
||||
Vector2D handle; ///< A handle pointing in the direction of the original, relative to the center
|
||||
|
||||
SymbolSymmetry();
|
||||
|
||||
virtual String typeName() const;
|
||||
virtual SymbolPartP clone() const;
|
||||
virtual int icon() const { return kind + SYMBOL_COMBINE_BORDER + 1; }
|
||||
virtual SymbolSymmetry* isSymbolSymmetry() { return this; }
|
||||
virtual const SymbolSymmetry* isSymbolSymmetry() const { return this; }
|
||||
|
||||
String expectedName() const;
|
||||
virtual Bounds calculateBounds(const Vector2D& origin, const Matrix2D& m, bool is_identity);
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Symbol
|
||||
@@ -282,13 +282,13 @@ class SymbolSymmetry : public SymbolGroup {
|
||||
/// An editable symbol, consists of any number of SymbolParts
|
||||
class Symbol : public SymbolGroup {
|
||||
public:
|
||||
/// Actions performed on this symbol and the parts in it
|
||||
ActionStack actions;
|
||||
|
||||
/// Determine the aspect ratio best suited for this symbol
|
||||
double aspectRatio() const;
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
/// Actions performed on this symbol and the parts in it
|
||||
ActionStack actions;
|
||||
|
||||
/// Determine the aspect ratio best suited for this symbol
|
||||
double aspectRatio() const;
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
/// A default symbol: a square
|
||||
@@ -301,20 +301,20 @@ SymbolP default_symbol();
|
||||
*/
|
||||
class SymbolView : public ActionListener {
|
||||
public:
|
||||
SymbolView();
|
||||
~SymbolView();
|
||||
SymbolView();
|
||||
~SymbolView();
|
||||
|
||||
/// Get the symbol that is currently being viewed
|
||||
inline SymbolP getSymbol() { return symbol; }
|
||||
/// Change the symbol that is being viewed
|
||||
void setSymbol(const SymbolP& symbol);
|
||||
|
||||
/// Get the symbol that is currently being viewed
|
||||
inline SymbolP getSymbol() { return symbol; }
|
||||
/// Change the symbol that is being viewed
|
||||
void setSymbol(const SymbolP& symbol);
|
||||
|
||||
protected:
|
||||
/// The symbol that is currently being viewed, should not be modified directly!
|
||||
SymbolP symbol;
|
||||
|
||||
/// Called when another symbol is being viewn (using setSymbol)
|
||||
virtual void onChangeSymbol() {}
|
||||
/// The symbol that is currently being viewed, should not be modified directly!
|
||||
SymbolP symbol;
|
||||
|
||||
/// Called when another symbol is being viewn (using setSymbol)
|
||||
virtual void onChangeSymbol() {}
|
||||
};
|
||||
|
||||
|
||||
|
||||
+438
-438
File diff suppressed because it is too large
Load Diff
+118
-118
@@ -27,117 +27,117 @@ struct CharInfo;
|
||||
/// A font that is drawn using images
|
||||
class SymbolFont : public Packaged {
|
||||
public:
|
||||
SymbolFont();
|
||||
~SymbolFont();
|
||||
|
||||
/// Loads the symbol font with a given name, for example "magic-mana-large"
|
||||
static SymbolFontP byName(const String& name);
|
||||
|
||||
// Script update
|
||||
void update(Context& ctx) const;
|
||||
|
||||
/// A symbol to be drawn
|
||||
class DrawableSymbol {
|
||||
public:
|
||||
inline DrawableSymbol(const String& text, const String& draw_text, SymbolInFont& symbol)
|
||||
: text(text), draw_text(draw_text), symbol(&symbol)
|
||||
{}
|
||||
|
||||
String text; ///< Original text
|
||||
String draw_text;///< Text to draw (extracted from the regex to avoid performance costs)
|
||||
SymbolInFont* symbol; ///< Symbol to draw
|
||||
};
|
||||
typedef vector<DrawableSymbol> SplitSymbols;
|
||||
|
||||
/// Split a string into separate symbols for drawing and for determining their size
|
||||
void split(const String& text, SplitSymbols& out) const;
|
||||
|
||||
/// How many consecutive characters of the text, starting at start can be rendered with this symbol font?
|
||||
size_t recognizePrefix(const String& text, size_t start) const;
|
||||
|
||||
/// Draw a piece of text
|
||||
void draw(RotatedDC& dc, Context& ctx, const RealRect& rect, double font_size, const Alignment& align, const String& text);
|
||||
/// Get information on characters in a string
|
||||
void getCharInfo(RotatedDC& dc, Context& ctx, double font_size, const String& text, vector<CharInfo>& out);
|
||||
|
||||
/// Draw a piece of text prepared using split
|
||||
void draw(RotatedDC& dc, RealRect rect, double font_size, const Alignment& align, const SplitSymbols& text);
|
||||
/// Get information on characters in a string
|
||||
void getCharInfo(RotatedDC& dc, double font_size, const SplitSymbols& text, vector<CharInfo>& out);
|
||||
|
||||
/// Get the image for a symbol
|
||||
Image getImage(double font_size, const DrawableSymbol& symbol);
|
||||
|
||||
static String typeNameStatic();
|
||||
virtual String typeName() const;
|
||||
Version fileVersion() const;
|
||||
|
||||
/// Generate a 'insert symbol' menu.
|
||||
/** This class owns the menu!
|
||||
* All ids used will be in the range ID_INSERT_SYMBOL_MENU_MIN...ID_INSERT_SYMBOL_MENU_MAX.
|
||||
* If there is no insert symbol menu, returns nullptr.
|
||||
*/
|
||||
wxMenu* insertSymbolMenu(Context& ctx);
|
||||
/// Process a choice from the insert symbol menu
|
||||
/** Return the code representing the symbol */
|
||||
String insertSymbolCode(int menu_id) const;
|
||||
|
||||
SymbolFont();
|
||||
~SymbolFont();
|
||||
|
||||
/// Loads the symbol font with a given name, for example "magic-mana-large"
|
||||
static SymbolFontP byName(const String& name);
|
||||
|
||||
// Script update
|
||||
void update(Context& ctx) const;
|
||||
|
||||
/// A symbol to be drawn
|
||||
class DrawableSymbol {
|
||||
public:
|
||||
inline DrawableSymbol(const String& text, const String& draw_text, SymbolInFont& symbol)
|
||||
: text(text), draw_text(draw_text), symbol(&symbol)
|
||||
{}
|
||||
|
||||
String text; ///< Original text
|
||||
String draw_text;///< Text to draw (extracted from the regex to avoid performance costs)
|
||||
SymbolInFont* symbol; ///< Symbol to draw
|
||||
};
|
||||
typedef vector<DrawableSymbol> SplitSymbols;
|
||||
|
||||
/// Split a string into separate symbols for drawing and for determining their size
|
||||
void split(const String& text, SplitSymbols& out) const;
|
||||
|
||||
/// How many consecutive characters of the text, starting at start can be rendered with this symbol font?
|
||||
size_t recognizePrefix(const String& text, size_t start) const;
|
||||
|
||||
/// Draw a piece of text
|
||||
void draw(RotatedDC& dc, Context& ctx, const RealRect& rect, double font_size, const Alignment& align, const String& text);
|
||||
/// Get information on characters in a string
|
||||
void getCharInfo(RotatedDC& dc, Context& ctx, double font_size, const String& text, vector<CharInfo>& out);
|
||||
|
||||
/// Draw a piece of text prepared using split
|
||||
void draw(RotatedDC& dc, RealRect rect, double font_size, const Alignment& align, const SplitSymbols& text);
|
||||
/// Get information on characters in a string
|
||||
void getCharInfo(RotatedDC& dc, double font_size, const SplitSymbols& text, vector<CharInfo>& out);
|
||||
|
||||
/// Get the image for a symbol
|
||||
Image getImage(double font_size, const DrawableSymbol& symbol);
|
||||
|
||||
static String typeNameStatic();
|
||||
virtual String typeName() const;
|
||||
Version fileVersion() const;
|
||||
|
||||
/// Generate a 'insert symbol' menu.
|
||||
/** This class owns the menu!
|
||||
* All ids used will be in the range ID_INSERT_SYMBOL_MENU_MIN...ID_INSERT_SYMBOL_MENU_MAX.
|
||||
* If there is no insert symbol menu, returns nullptr.
|
||||
*/
|
||||
wxMenu* insertSymbolMenu(Context& ctx);
|
||||
/// Process a choice from the insert symbol menu
|
||||
/** Return the code representing the symbol */
|
||||
String insertSymbolCode(int menu_id) const;
|
||||
|
||||
private:
|
||||
double img_size; ///< Font size that the images use
|
||||
RealSize spacing; ///< Spacing between sybmols (for the default font size)
|
||||
// writing text
|
||||
bool scale_text; ///< Should text be scaled down to fit in a symbol?
|
||||
InsertSymbolMenuP insert_symbol_menu;
|
||||
wxMenu* processed_insert_symbol_menu;
|
||||
|
||||
friend class SymbolInFont;
|
||||
friend class InsertSymbolMenu;
|
||||
vector<SymbolInFontP> symbols; ///< The individual symbols
|
||||
|
||||
/// Find the default symbol
|
||||
/** may return nullptr */
|
||||
SymbolInFont* defaultSymbol() const;
|
||||
|
||||
/// Draws a single symbol inside the given rectangle
|
||||
void drawSymbol (RotatedDC& dc, RealRect sym_rect, double font_size, const Alignment& align, SymbolInFont& sym, const String& text);
|
||||
|
||||
/// Size of a single symbol, including spacing
|
||||
RealSize symbolSize (double font_size, const DrawableSymbol& sym);
|
||||
double img_size; ///< Font size that the images use
|
||||
RealSize spacing; ///< Spacing between sybmols (for the default font size)
|
||||
// writing text
|
||||
bool scale_text; ///< Should text be scaled down to fit in a symbol?
|
||||
InsertSymbolMenuP insert_symbol_menu;
|
||||
wxMenu* processed_insert_symbol_menu;
|
||||
|
||||
friend class SymbolInFont;
|
||||
friend class InsertSymbolMenu;
|
||||
vector<SymbolInFontP> symbols; ///< The individual symbols
|
||||
|
||||
/// Find the default symbol
|
||||
/** may return nullptr */
|
||||
SymbolInFont* defaultSymbol() const;
|
||||
|
||||
/// Draws a single symbol inside the given rectangle
|
||||
void drawSymbol (RotatedDC& dc, RealRect sym_rect, double font_size, const Alignment& align, SymbolInFont& sym, const String& text);
|
||||
|
||||
/// Size of a single symbol, including spacing
|
||||
RealSize symbolSize (double font_size, const DrawableSymbol& sym);
|
||||
public:
|
||||
/// The default size of symbols, including spacing
|
||||
RealSize defaultSymbolSize(double font_size);
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
/// The default size of symbols, including spacing
|
||||
RealSize defaultSymbolSize(double font_size);
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : InsertSymbolMenu
|
||||
|
||||
enum MenuItemType
|
||||
{ ITEM_CODE ///< Name gives the code to insert
|
||||
, ITEM_CUSTOM ///< Use a dialog box
|
||||
, ITEM_LINE ///< A menu separator
|
||||
, ITEM_SUBMENU ///< A submenu
|
||||
{ ITEM_CODE ///< Name gives the code to insert
|
||||
, ITEM_CUSTOM ///< Use a dialog box
|
||||
, ITEM_LINE ///< A menu separator
|
||||
, ITEM_SUBMENU ///< A submenu
|
||||
};
|
||||
|
||||
/// Description of a menu to insert symbols from a symbol font into the text
|
||||
class InsertSymbolMenu : public IntrusivePtrBase<InsertSymbolMenu> {
|
||||
public:
|
||||
InsertSymbolMenu();
|
||||
|
||||
MenuItemType type;
|
||||
String name;
|
||||
vector<InsertSymbolMenuP> items;
|
||||
|
||||
/// Number of ids used (recursive)
|
||||
int size() const;
|
||||
/// Get the code for an item, id relative to the start of this menu
|
||||
String getCode(int id, const SymbolFont& font) const;
|
||||
/// Make an actual menu
|
||||
wxMenu* makeMenu(int first_id, SymbolFont& font) const;
|
||||
/// Make an actual menu item
|
||||
wxMenuItem* makeMenuItem(wxMenu* parent, int first_id, SymbolFont& font) const;
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
InsertSymbolMenu();
|
||||
|
||||
MenuItemType type;
|
||||
String name;
|
||||
vector<InsertSymbolMenuP> items;
|
||||
|
||||
/// Number of ids used (recursive)
|
||||
int size() const;
|
||||
/// Get the code for an item, id relative to the start of this menu
|
||||
String getCode(int id, const SymbolFont& font) const;
|
||||
/// Make an actual menu
|
||||
wxMenu* makeMenu(int first_id, SymbolFont& font) const;
|
||||
/// Make an actual menu item
|
||||
wxMenuItem* makeMenuItem(wxMenu* parent, int first_id, SymbolFont& font) const;
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : SymbolFontRef
|
||||
@@ -145,26 +145,26 @@ class InsertSymbolMenu : public IntrusivePtrBase<InsertSymbolMenu> {
|
||||
/// A reference to an actual symbol font
|
||||
class SymbolFontRef {
|
||||
public:
|
||||
SymbolFontRef();
|
||||
|
||||
// Script update
|
||||
bool update(Context& ctx);
|
||||
void initDependencies(Context&, const Dependency&) const;
|
||||
|
||||
/// Is a font loaded?
|
||||
bool valid() const;
|
||||
|
||||
Scriptable<String> name; ///< Font package name, can be changed with script
|
||||
Scriptable<double> size; ///< Size of the font
|
||||
double scale_down_to; ///< Mimumum size of the font
|
||||
Scriptable<Alignment> alignment; ///< Alignment of symbols in a line of text
|
||||
SymbolFontP font; ///< The font, if it is loaded
|
||||
|
||||
SymbolFontRef();
|
||||
|
||||
// Script update
|
||||
bool update(Context& ctx);
|
||||
void initDependencies(Context&, const Dependency&) const;
|
||||
|
||||
/// Is a font loaded?
|
||||
bool valid() const;
|
||||
|
||||
Scriptable<String> name; ///< Font package name, can be changed with script
|
||||
Scriptable<double> size; ///< Size of the font
|
||||
double scale_down_to; ///< Mimumum size of the font
|
||||
Scriptable<Alignment> alignment; ///< Alignment of symbols in a line of text
|
||||
SymbolFontP font; ///< The font, if it is loaded
|
||||
|
||||
private:
|
||||
DECLARE_REFLECTION();
|
||||
|
||||
/// (re)load the symbol font based on name
|
||||
void loadFont(Context& ctx);
|
||||
DECLARE_REFLECTION();
|
||||
|
||||
/// (re)load the symbol font based on name
|
||||
void loadFont(Context& ctx);
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
+21
-21
@@ -12,40 +12,40 @@
|
||||
// ----------------------------------------------------------------------------- : WordList
|
||||
|
||||
WordListWord::WordListWord()
|
||||
: line_below(false)
|
||||
, is_prefix(false)
|
||||
: line_below(false)
|
||||
, is_prefix(false)
|
||||
{}
|
||||
|
||||
IMPLEMENT_REFLECTION_NO_SCRIPT(WordListWord) {
|
||||
if (line_below || is_prefix || isGroup() || script || (tag.reading() && tag.isComplex())) {
|
||||
// complex value
|
||||
REFLECT(name);
|
||||
REFLECT(line_below);
|
||||
REFLECT(is_prefix);
|
||||
REFLECT(words);
|
||||
REFLECT(script);
|
||||
} else {
|
||||
REFLECT_NAMELESS(name);
|
||||
}
|
||||
if (line_below || is_prefix || isGroup() || script || (tag.reading() && tag.isComplex())) {
|
||||
// complex value
|
||||
REFLECT(name);
|
||||
REFLECT(line_below);
|
||||
REFLECT(is_prefix);
|
||||
REFLECT(words);
|
||||
REFLECT(script);
|
||||
} else {
|
||||
REFLECT_NAMELESS(name);
|
||||
}
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION_NO_SCRIPT(WordList) {
|
||||
REFLECT(name);
|
||||
REFLECT(words);
|
||||
REFLECT(name);
|
||||
REFLECT(words);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------- : Auto replace words
|
||||
|
||||
AutoReplace::AutoReplace()
|
||||
: enabled(true)
|
||||
, whole_word(true)
|
||||
, custom(true)
|
||||
: enabled(true)
|
||||
, whole_word(true)
|
||||
, custom(true)
|
||||
{}
|
||||
|
||||
IMPLEMENT_REFLECTION_NO_SCRIPT(AutoReplace) {
|
||||
REFLECT(enabled);
|
||||
REFLECT(whole_word);
|
||||
REFLECT(match);
|
||||
REFLECT(replace);
|
||||
REFLECT(enabled);
|
||||
REFLECT(whole_word);
|
||||
REFLECT(match);
|
||||
REFLECT(replace);
|
||||
}
|
||||
|
||||
+17
-17
@@ -22,23 +22,23 @@ DECLARE_POINTER_TYPE(AutoReplace);
|
||||
/// A word in a WordList
|
||||
class WordListWord : public IntrusivePtrBase<WordListWord> {
|
||||
public:
|
||||
WordListWord();
|
||||
WordListWord();
|
||||
|
||||
String name; ///< Name of the list / the word
|
||||
bool line_below; ///< Line below in the list?
|
||||
bool is_prefix; ///< Is this a prefix before other words?
|
||||
vector<WordListWordP> words; ///< Sublist
|
||||
OptionalScript script; ///< Generate words using a script
|
||||
String name; ///< Name of the list / the word
|
||||
bool line_below; ///< Line below in the list?
|
||||
bool is_prefix; ///< Is this a prefix before other words?
|
||||
vector<WordListWordP> words; ///< Sublist
|
||||
OptionalScript script; ///< Generate words using a script
|
||||
|
||||
inline bool isGroup() const { return !words.empty(); }
|
||||
inline bool isGroup() const { return !words.empty(); }
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
/// A list of words for a drop down box
|
||||
class WordList : public WordListWord {
|
||||
public:
|
||||
DECLARE_REFLECTION();
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Auto replace words
|
||||
@@ -46,17 +46,17 @@ class WordList : public WordListWord {
|
||||
/// Autoreplace specific shortcut words
|
||||
class AutoReplace : public IntrusivePtrVirtualBase {
|
||||
public:
|
||||
AutoReplace();
|
||||
AutoReplace();
|
||||
|
||||
bool enabled;
|
||||
bool whole_word;
|
||||
bool custom; ///< Is this a custom auto replace?
|
||||
String match;
|
||||
String replace;
|
||||
bool enabled;
|
||||
bool whole_word;
|
||||
bool custom; ///< Is this a custom auto replace?
|
||||
String match;
|
||||
String replace;
|
||||
|
||||
inline AutoReplaceP clone() const { return intrusive(new AutoReplace(*this)); }
|
||||
inline AutoReplaceP clone() const { return intrusive(new AutoReplace(*this)); }
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
Reference in New Issue
Block a user