diff --git a/src/data/action/value.cpp b/src/data/action/value.cpp index 5be3361b..31d410ce 100644 --- a/src/data/action/value.cpp +++ b/src/data/action/value.cpp @@ -26,11 +26,14 @@ String ValueAction::getName(bool to_undo) const { /// Swap the value in a Value object with a new one inline void swap_value(ChoiceValue& a, ChoiceValue ::ValueType& b) { swap(a.value, b); } -inline void swap_value(MultipleChoiceValue& a, MultipleChoiceValue::ValueType& b) { swap(a.value, b); } inline void swap_value(ColorValue& a, ColorValue ::ValueType& b) { swap(a.value, b); } inline void swap_value(ImageValue& a, ImageValue ::ValueType& b) { swap(a.filename, b); a.last_update.update(); } inline void swap_value(SymbolValue& a, SymbolValue ::ValueType& b) { swap(a.filename, b); a.last_update.update(); } inline void swap_value(TextValue& a, TextValue ::ValueType& b) { swap(a.value, b); a.last_update.update(); } +inline void swap_value(MultipleChoiceValue& a, MultipleChoiceValue::ValueType& b) { + swap(a.value, b.value); + swap(a.last_change, b.last_change); +} /// A ValueAction that swaps between old and new values template @@ -62,10 +65,13 @@ class SimpleValueAction : public ValueAction { }; ValueAction* value_action(const ChoiceValueP& value, const Defaultable& new_value) { return new SimpleValueAction (value, new_value); } -ValueAction* value_action(const MultipleChoiceValueP& value, const Defaultable& new_value) { return new SimpleValueAction(value, new_value); } ValueAction* value_action(const ColorValueP& value, const Defaultable& new_value) { return new SimpleValueAction (value, new_value); } ValueAction* value_action(const ImageValueP& value, const FileName& new_value) { return new SimpleValueAction(value, new_value); } ValueAction* value_action(const SymbolValueP& value, const FileName& new_value) { return new SimpleValueAction(value, new_value); } +ValueAction* value_action(const MultipleChoiceValueP& value, const Defaultable& new_value, const String& last_change) { + MultipleChoiceValue::ValueType v = { new_value, last_change }; + return new SimpleValueAction(value, v); +} // ----------------------------------------------------------------------------- : Text diff --git a/src/data/action/value.hpp b/src/data/action/value.hpp index a85c838c..f1bc8a5b 100644 --- a/src/data/action/value.hpp +++ b/src/data/action/value.hpp @@ -45,7 +45,7 @@ class ValueAction : public Action { /// Action that updates a Value to a new value ValueAction* value_action(const ChoiceValueP& value, const Defaultable& new_value); -ValueAction* value_action(const MultipleChoiceValueP& value, const Defaultable& new_value); +ValueAction* value_action(const MultipleChoiceValueP& value, const Defaultable& new_value, const String& last_change); ValueAction* value_action(const ColorValueP& value, const Defaultable& new_value); ValueAction* value_action(const ImageValueP& value, const FileName& new_value); ValueAction* value_action(const SymbolValueP& value, const FileName& new_value); diff --git a/src/data/field/choice.cpp b/src/data/field/choice.cpp index a750da86..8a284404 100644 --- a/src/data/field/choice.cpp +++ b/src/data/field/choice.cpp @@ -179,6 +179,35 @@ ChoiceStyle::~ChoiceStyle() { 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 + // RET + intrusive_ptr lookup(new ScriptCustomCollection()); + FOR_EACH(ci, choice_images) { + lookup->key_value[ci.first] = ci.second.getScriptP(); + } + Script& script = image.getScript(); + script.addInstruction(I_PUSH_CONST, lookup); + script.addInstruction(I_GET_VAR, string_to_variable(_("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); + script.addInstruction(I_RET); +} bool ChoiceStyle::update(Context& ctx) { // Don't update the choice images, leave that to invalidate() @@ -256,6 +285,7 @@ IMPLEMENT_REFLECTION(ChoiceStyle) { REFLECT(alignment); REFLECT(angle); REFLECT(font); + REFLECT(image); REFLECT(choice_images); } diff --git a/src/data/field/choice.hpp b/src/data/field/choice.hpp index c2ff29fe..abf4f62d 100644 --- a/src/data/field/choice.hpp +++ b/src/data/field/choice.hpp @@ -138,6 +138,7 @@ class ChoiceStyle : public Style { ChoicePopupStyle popup_style; ///< Style of popups/menus ChoiceRenderStyle render_style; ///< Style of rendering Font font; ///< Font for drawing text (when RENDER_TEXT) + ScriptableImage image; ///< Image to draw (when RENDER_IMAGE) map choice_images; ///< Images for the various choices (when RENDER_IMAGE) bool choice_images_initialized; Scriptable mask_filename; ///< Filename of an additional mask over the images @@ -150,6 +151,8 @@ class ChoiceStyle : public Style { /// Load the mask image, if it's not already done void loadMask(Package& pkg); + /// Initialize image from choice_images + void initImage(); virtual bool update(Context&); virtual void initDependencies(Context&, const Dependency&) const; diff --git a/src/data/field/multiple_choice.cpp b/src/data/field/multiple_choice.cpp index 73a0d280..4c9a5ce7 100644 --- a/src/data/field/multiple_choice.cpp +++ b/src/data/field/multiple_choice.cpp @@ -48,6 +48,14 @@ IMPLEMENT_REFLECTION_NAMELESS(MultipleChoiceValue) { 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; +} + void MultipleChoiceValue::get(vector& out) const { // split the value out.clear(); @@ -66,3 +74,38 @@ void MultipleChoiceValue::get(vector& out) const { } } } + +void MultipleChoiceValue::normalForm() { + String& val = value.mutateDontChangeDefault(); + // which choices are active? + vector 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; + } +} diff --git a/src/data/field/multiple_choice.hpp b/src/data/field/multiple_choice.hpp index 08451f27..e7c3efc7 100644 --- a/src/data/field/multiple_choice.hpp +++ b/src/data/field/multiple_choice.hpp @@ -57,13 +57,24 @@ class MultipleChoiceValue : public ChoiceValue { inline MultipleChoiceValue(const MultipleChoiceFieldP& field) : ChoiceValue(field, true) {} DECLARE_HAS_FIELD(MultipleChoice); - String last_choice; ///< Which of the choices was selected/deselected last? + 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& out) const; + virtual bool update(Context&); + private: DECLARE_REFLECTION(); + + /// Put the value in normal form (all choices ordered, empty_name + void normalForm(); }; // ----------------------------------------------------------------------------- : Utilities diff --git a/src/gui/value/multiple_choice.cpp b/src/gui/value/multiple_choice.cpp index 580c9116..eabbafa1 100644 --- a/src/gui/value/multiple_choice.cpp +++ b/src/gui/value/multiple_choice.cpp @@ -35,8 +35,7 @@ DropDownMultipleChoiceList::DropDownMultipleChoiceList void DropDownMultipleChoiceList::select(size_t item) { MultipleChoiceValueEditor& mcve = dynamic_cast(cve); if (isFieldDefault(item)) { - // make default - mcve.getSet().actions.add(value_action(mcve.valueP(), Defaultable())); + mcve.toggleDefault(); } else { ChoiceField::ChoiceP choice = getChoice(item); mcve.toggle(choice->first_id); @@ -143,6 +142,7 @@ void MultipleChoiceValueEditor::onValueChange() { void MultipleChoiceValueEditor::toggle(int id) { String new_value; + String toggled_choice; // old selection vector selected; value().get(selected); @@ -157,7 +157,12 @@ void MultipleChoiceValueEditor::toggle(int id) { if (!new_value.empty()) new_value += _(", "); new_value += choice; } + if (i == id) toggled_choice = choice; } // store value - getSet().actions.add(value_action(valueP(), new_value)); + getSet().actions.add(value_action(valueP(), new_value, toggled_choice)); +} + +void MultipleChoiceValueEditor::toggleDefault() { + getSet().actions.add(value_action(valueP(), Defaultable(value().value(), !value().value.isDefault()), _(""))); } diff --git a/src/gui/value/multiple_choice.hpp b/src/gui/value/multiple_choice.hpp index f5a7609d..5a295d9e 100644 --- a/src/gui/value/multiple_choice.hpp +++ b/src/gui/value/multiple_choice.hpp @@ -38,6 +38,8 @@ class MultipleChoiceValueEditor : public MultipleChoiceValueViewer, public Value DropDownList& initDropDown(); /// Toggle a choice or on or off void toggle(int id); + /// Toggle defaultness or on or off + void toggleDefault(); }; // ----------------------------------------------------------------------------- : EOF diff --git a/src/render/value/choice.cpp b/src/render/value/choice.cpp index 7ee34dd5..1d008d6f 100644 --- a/src/render/value/choice.cpp +++ b/src/render/value/choice.cpp @@ -19,9 +19,15 @@ void ChoiceValueViewer::draw(RotatedDC& dc) { double margin = 0; if (style().render_style & RENDER_IMAGE) { // draw image - map::iterator it = style().choice_images.find(cannocial_name_form(value().value())); - if (it != style().choice_images.end() && it->second.isReady()) { - ScriptableImage& img = it->second; +// map::iterator it = style().choice_images.find(cannocial_name_form(value().value())); +// if (it != style().choice_images.end() && it->second.isReady()) { +// ScriptableImage& img = it->second; + style().initImage(); + ScriptableImage& img = style().image; + Context& ctx = viewer.getContext(); + ctx.setVariable(_("input"), to_script(value().value())); + img.update(ctx); + if (img.isReady()) { GeneratedImage::Options img_options(0,0, viewer.stylesheet.get(), &getSet()); if (nativeLook()) { img_options.width = img_options.height = 16; diff --git a/src/render/value/multiple_choice.cpp b/src/render/value/multiple_choice.cpp index 4bfd86e2..e4f88b58 100644 --- a/src/render/value/multiple_choice.cpp +++ b/src/render/value/multiple_choice.cpp @@ -43,9 +43,12 @@ void MultipleChoiceValueViewer::draw(RotatedDC& dc) { double margin = 0; if (style().render_style & RENDER_IMAGE) { // draw image - map::iterator it = style().choice_images.find(cannocial_name_form(value().value())); - if (it != style().choice_images.end() && it->second.isReady()) { - ScriptableImage& img = it->second; + style().initImage(); + ScriptableImage& img = style().image; + Context& ctx = viewer.getContext(); + ctx.setVariable(_("input"), to_script(value().value())); + img.update(ctx); + if (img.isReady()) { GeneratedImage::Options img_options(0,0, viewer.stylesheet.get(), &getSet()); if (nativeLook()) { img_options.width = img_options.height = 16; diff --git a/src/script/functions/basic.cpp b/src/script/functions/basic.cpp index b93233bb..91456ff7 100644 --- a/src/script/functions/basic.cpp +++ b/src/script/functions/basic.cpp @@ -35,6 +35,13 @@ SCRIPT_FUNCTION(to_title) { SCRIPT_RETURN(capitalize(input)); } +// reverse a string +SCRIPT_FUNCTION(reverse) { + SCRIPT_PARAM(String, input); + reverse(input.begin(), input.end()); + SCRIPT_RETURN(input); +} + // extract a substring SCRIPT_FUNCTION(substring) { SCRIPT_PARAM(String, input); @@ -360,13 +367,19 @@ class ScriptFilterRule : public ScriptValue { size_t start, len; bool ok = regex.GetMatch(&start, &len, 0); assert(ok); - ret += input.substr(start, len); // the match - input = input.substr(start + len); // everything after the match + String inside = input.substr(start, len); // the match + String next_input = input.substr(start + len); // everything after the match + if (!context.IsValid() || context.Matches(input.substr(0,start) + _("") + next_input)) { + // no context or context match + ret += inside; + } + input = next_input; } SCRIPT_RETURN(ret); } - wxRegEx regex; ///< Regex to match + wxRegEx regex; ///< Regex to match + wxRegEx context; ///< Match only in a given context, optional }; // Create a regular expression rule for filtering strings @@ -377,6 +390,12 @@ ScriptValueP filter_rule(Context& ctx) { if (!ret->regex.Compile(match, wxRE_ADVANCED)) { throw ScriptError(_("Error while compiling regular expression: '")+match+_("'")); } + // in_context + SCRIPT_OPTIONAL_PARAM_N(String, _("in context"), in_context) { + if (!ret->context.Compile(in_context, wxRE_ADVANCED)) { + throw ScriptError(_("Error while compiling regular expression: '")+in_context+_("'")); + } + } return ret; } @@ -592,6 +611,7 @@ void init_script_basic_functions(Context& ctx) { ctx.setVariable(_("to upper"), script_to_upper); ctx.setVariable(_("to lower"), script_to_lower); ctx.setVariable(_("to title"), script_to_title); + ctx.setVariable(_("reverse"), script_reverse); ctx.setVariable(_("substring"), script_substring); ctx.setVariable(_("contains"), script_contains); ctx.setVariable(_("format"), script_format); diff --git a/src/script/functions/editor.cpp b/src/script/functions/editor.cpp index 76d4a4c4..87d3f5de 100644 --- a/src/script/functions/editor.cpp +++ b/src/script/functions/editor.cpp @@ -157,10 +157,8 @@ SCRIPT_FUNCTION(primary_choice) { // ----------------------------------------------------------------------------- : Multiple choice values -// is the given choice active? -SCRIPT_FUNCTION(chosen) { - SCRIPT_PARAM(String,choice); - SCRIPT_PARAM(String,input); +/// Is the given choice active? +bool chosen(const String& choice, const String& input) { for (size_t pos = 0 ; pos < input.size() ; ) { if (input.GetChar(pos) == _(' ')) { ++pos; // ingore whitespace @@ -169,32 +167,147 @@ SCRIPT_FUNCTION(chosen) { size_t end = input.find_first_of(_(','), pos); if (end == String::npos) end = input.size(); if (end - pos == choice.size() && is_substr(input, pos, choice)) { - SCRIPT_RETURN(true); + return true; } pos = end + 1; } } - SCRIPT_RETURN(false); + return false; +} + +// is the given choice active? +SCRIPT_FUNCTION(chosen) { + SCRIPT_PARAM(String,choice); + SCRIPT_PARAM(String,input); + SCRIPT_RETURN(chosen(choice, input)); +} + +/// Filter the choices +/** Keep at most max elements of choices, + * and at least min. Use prefered as choice to add/keep in case of conflicts. + */ +String filter_choices(const String& input, const vector& choices, int min, int max, String prefered) { + if (choices.empty()) return input; // do nothing, shouldn't happen, but better make sure + String ret; + int count = 0; + vector seen(choices.size()); // which choices have we seen? + for (size_t pos = 0 ; pos < input.size() ; ) { + if (input.GetChar(pos) == _(' ')) { + ++pos; // ingore whitespace + } else { + // does this choice match the one asked about? + size_t end = input.find_first_of(_(','), pos); + if (end == String::npos) end = input.size(); + // is this choice in choices + bool in_choices = false; + for (size_t i = 0 ; i < choices.size() ; ++i) { + if (!seen[i] && is_substr(input, pos, choices[i])) { + seen[i] = true; ++count; + in_choices = true; + break; + } + } + // is this choice unaffected? + if (!in_choices) { + if (!ret.empty()) ret += _(", "); + ret += input.substr(pos, end - pos); + } + pos = end + 1; + } + } + // keep more choices + if (count < min) { + // add prefered choice + for (size_t i = 0 ; i < choices.size() ; ++i) { + if (choices[i] == prefered) { + if (!seen[i]) { + seen[i] = true; ++count; + } + break; + } + } + // add more choices + for (size_t i = 0 ; i < choices.size() ; ++i) { + if (count >= min) break; + if (!seen[i]) { + seen[i] = true; ++count; + } + } + } + // keep less choices + if (count > max) { + for (size_t i = choices.size() - 1 ; i >= 0 ; --i) { + if (count <= max) break; + if (seen[i]) { + if (max > 0 && choices[i] == prefered) continue; // we would rather not remove prefered choice + seen[i] = false; --count; + } + } + } + // add the 'seen' choices to ret + for (size_t i = 0 ; i < choices.size() ; ++i) { + if (seen[i]) { + if (!ret.empty()) ret += _(", "); + ret += choices[i]; + } + } + return ret; +} + +// read 'choice#' arguments +void read_choices_param(Context& ctx, vector& choices) { + for (int i = 0 ; ; ++i) { + String name = _("choice"); if (i > 0) name = name << i; + SCRIPT_OPTIONAL_PARAM_N(String, name, choice) { + choices.push_back(choice); + } else if (i > 0) break; + } } // add the given choice if it is not already active SCRIPT_FUNCTION(require_choice) { - SCRIPT_PARAM(ValueP,input); - MultipleChoiceValueP value = dynamic_pointer_cast(input); - if (!value) { - throw ScriptError(_("Argument 'input' to 'require_choice' should be a multiple choice value")); - } - SCRIPT_PARAM(String,choice); - // TODO - SCRIPT_RETURN(input); + SCRIPT_PARAM(String,input); + SCRIPT_OPTIONAL_PARAM_N_(String,_("last change"),last_change); + vector choices; + read_choices_param(ctx, choices); + SCRIPT_RETURN(filter_choices(input, choices, 1, (int)choices.size(), last_change)); +} + +// make sure at most one of the choices is active +SCRIPT_FUNCTION(exclusive_choice) { + SCRIPT_PARAM(String,input); + SCRIPT_OPTIONAL_PARAM_N_(String,_("last change"),last_change); + vector choices; + read_choices_param(ctx, choices); + SCRIPT_RETURN(filter_choices(input, choices, 0, 1, last_change)); +} + +// make sure exactly one of the choices is active +SCRIPT_FUNCTION(require_exclusive_choice) { + SCRIPT_PARAM(String,input); + SCRIPT_OPTIONAL_PARAM_N_(String,_("last change"),last_change); + vector choices; + read_choices_param(ctx, choices); + SCRIPT_RETURN(filter_choices(input, choices, 1, 1, last_change)); +} + +// make sure none of the choices are active +SCRIPT_FUNCTION(remove_choice) { + SCRIPT_PARAM(String,input); + vector choices; + read_choices_param(ctx, choices); + SCRIPT_RETURN(filter_choices(input, choices, 0, 0, _(""))); } // ----------------------------------------------------------------------------- : Init void init_script_editor_functions(Context& ctx) { - ctx.setVariable(_("forward editor"), script_combined_editor); // combatability - ctx.setVariable(_("combined editor"), script_combined_editor); - ctx.setVariable(_("primary choice"), script_primary_choice); - ctx.setVariable(_("chosen"), script_chosen); - ctx.setVariable(_("require choice"), script_require_choice); + ctx.setVariable(_("forward editor"), script_combined_editor); // compatability + ctx.setVariable(_("combined editor"), script_combined_editor); + ctx.setVariable(_("primary choice"), script_primary_choice); + ctx.setVariable(_("chosen"), script_chosen); + ctx.setVariable(_("require choice"), script_require_choice); + ctx.setVariable(_("exclusive choice"), script_exclusive_choice); + ctx.setVariable(_("require exclusive choice"), script_require_exclusive_choice); + ctx.setVariable(_("remove choice"), script_remove_choice); } diff --git a/src/script/script.cpp b/src/script/script.cpp index a9bdf6d1..5de6d521 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -61,15 +61,15 @@ ScriptValueP Script::dependencies(Context& ctx, const Dependency& dep) const { } void Script::addInstruction(InstructionType t) { - //if (t == I_MEMBER_V && !instructions.empty() && instructions.back().instr == I_PUSH_CONST) { - // // optimize: push x ; member_v --> member x - // instructions.back().instr = I_MEMBER; - //} else { Instruction i = {t, {0}}; instructions.push_back(i); - //} } void Script::addInstruction(InstructionType t, unsigned int d) { + if (t == I_BINARY && d == I_MEMBER && !instructions.empty() && instructions.back().instr == I_PUSH_CONST) { + // optimize: push x ; member --> member_c x + instructions.back().instr = I_MEMBER_C; + return; + } Instruction i = {t, {d}}; instructions.push_back(i); } diff --git a/src/util/defaultable.hpp b/src/util/defaultable.hpp index a4d78653..6efbcf38 100644 --- a/src/util/defaultable.hpp +++ b/src/util/defaultable.hpp @@ -46,13 +46,13 @@ class Defaultable { is_default = false; return value; } + /// Get access to the value, for changing it, don't change the defaultness + inline T& mutateDontChangeDefault() { return value; } /// Is this value in the default state? inline bool isDefault() const { return is_default; } - /// Set the defaultness to true - inline void makeDefault() { is_default = true; } - /// Set the defaultness to false - inline void unsetDefault() { is_default = false; } + /// Set the defaultness to d + inline void makeDefault(bool d = true) { is_default = d; } /// Compare the values, ignore defaultness /** used by scriptable to check for changes */ diff --git a/src/util/string.cpp b/src/util/string.cpp index 715e002d..98dd9da2 100644 --- a/src/util/string.cpp +++ b/src/util/string.cpp @@ -41,6 +41,30 @@ void writeUTF8(wxTextOutputStream& stream, const String& str) { #endif } +// ----------------------------------------------------------------------------- : Char functions + +#ifdef CHAR_FUNCTIONS_ARE_SLOW + +Char toLower(Char c) { + if (c <= 128) { + if (c >= _('A') && c <= _('Z')) return c + (_('a') - _('A')); + else return c; + } else { + return IF_UNICODE( towlower(c) , tolower(c) ); + } +} + +Char toUpper(Char c) { + if (c <= 128) { + if (c >= _('a') && c <= _('z')) return c + (_('A') - _('a')); + else return c; + } else { + return IF_UNICODE( towupper(c) , toupper(c) ); + } +} + +#endif + // ----------------------------------------------------------------------------- : String utilities String trim(const String& s){ @@ -180,7 +204,6 @@ bool smart_less(const String& as, const String& bs) { bool eq = true; // so far is everything equal? FOR_EACH_2_CONST(a, as, b, bs) { bool na = isDigit(a), nb = isDigit(b); - Char la = toLower(a), lb = toLower(b); if (na && nb) { // compare numbers in_num = true; @@ -198,8 +221,9 @@ bool smart_less(const String& as, const String& bs) { return lt; } else { // compare characters - if (la < lb) return true; - if (la > lb) return false; + Char la = toLower(a), lb = toLower(b); + if (la < lb) return true; + if (la > lb) return false; } in_num = na && nb; } diff --git a/src/util/string.hpp b/src/util/string.hpp index 73668d1a..7f59963e 100644 --- a/src/util/string.hpp +++ b/src/util/string.hpp @@ -88,8 +88,18 @@ inline bool isUpper(Char c) { return IF_UNICODE( iswupper(c) , isupper(c) ); } inline bool isLower(Char c) { return IF_UNICODE( iswlower(c) , islower(c) ); } inline bool isPunct(Char c) { return IF_UNICODE( iswpunct(c) , ispunct(c) ); } // Character conversions -inline Char toUpper(Char c) { return IF_UNICODE( towupper(c) , toupper(c) ); } -inline Char toLower(Char c) { return IF_UNICODE( towlower(c) , tolower(c) ); } +#ifdef _MSC_VER + #define CHAR_FUNCTIONS_ARE_SLOW +#endif +#ifdef CHAR_FUNCTIONS_ARE_SLOW + // These functions are slow as hell on msvc. + // If also in other compilers, they can also use these routines. + Char toLower(Char c); + Char toUpper(Char c); +#else + inline Char toLower(Char c) { return IF_UNICODE( towlower(c) , tolower(c) ); } + inline Char toUpper(Char c) { return IF_UNICODE( towupper(c) , toupper(c) ); } +#endif // ----------------------------------------------------------------------------- : String utilities