Commit 71fd64f9 authored by twanvl's avatar twanvl

Added support for scripts to determine word lists;

Added 'trim' and 'remove_tags' script functions;
Simplified safety improvements of locale checker;
Added 'is_targeted' function to magic game to replace the contains(..) calls
parent 74641b82
...@@ -237,8 +237,7 @@ init script: ...@@ -237,8 +237,7 @@ init script:
# replaces — correctly # replaces — correctly
add := "" # default is nothing add := "" # default is nothing
separate_words := replace_rule(match:" ", replace: " and/or ")+ separate_words := replace_rule(match:" ", replace: " and/or ")+ remove_tags
tag_remove_rule(tag: "<word-list")
for_mana_costs := format_cost := { for_mana_costs := format_cost := {
if input.separator_before == "—" and contains(input.param, match: " ") then ( if input.separator_before == "—" and contains(input.param, match: " ") then (
if contains(input.param, match:",") then ( if contains(input.param, match:",") then (
...@@ -259,6 +258,8 @@ init script: ...@@ -259,6 +258,8 @@ init script:
# Utilities for keywords # Utilities for keywords
has_cc := { card.casting_cost != "" } has_cc := { card.casting_cost != "" }
has_pt := { card.pt != "" } has_pt := { card.pt != "" }
contains_target := match_rule(match:"(?i)([^a-z]|^)target([^a-z]|$)")
is_targeted := { contains_target(card.rule_text) }
############################################################## The text box ############################################################## The text box
...@@ -334,17 +335,6 @@ init script: ...@@ -334,17 +335,6 @@ init script:
replace_rule( replace_rule(
match: "\\[[STXYZWUBRG0-9/|]+\\]", match: "\\[[STXYZWUBRG0-9/|]+\\]",
replace: {"<sym>" + mana_filter_t() + "</sym>"} ) + replace: {"<sym>" + mana_filter_t() + "</sym>"} ) +
# step 6 : longdash
replace_rule(
match: "--",
replace: "—") +
replace_rule(
match: " - ",
replace: " — ") +
# step 6b : Æ replacement
replace_rule(
match: "AE",
replace: "Æ" ) +
# step 7 : italic reminder text # step 7 : italic reminder text
replace_rule( replace_rule(
match: "[(][^)\n]*[)]?", match: "[(][^)\n]*[)]?",
...@@ -354,84 +344,43 @@ init script: ...@@ -354,84 +344,43 @@ init script:
replace_rule( replace_rule(
match: "[a-z]", match: "[a-z]",
in_context: "[(](<param-[a-z]*>)?<match>|[ ]*: <param-cost><match>|—<match>| — <match>", in_context: "[(](<param-[a-z]*>)?<match>|[ ]*: <param-cost><match>|—<match>| — <match>",
replace: { to_upper() }) + replace: to_upper) +
curly_quotes curly_quotes
############################################################## Other boxes ############################################################## Other boxes
#character filter for title line
name_filter :=
# step 1 : Æ replacement rule
replace_rule(
match: "AE",
replace: "Æ")
#character filter for copyright line
copyright_filter :=
# step 1 : Æ replacement rule
replace_rule(
match: "AE",
replace: "Æ") +
# step 2 : longdash for keywords
replace_rule(
match: "--",
replace: "—") +
replace_rule(
match: " - ",
replace: " — ") +
# step 3 : trademark symbol
replace_rule(
match: "TM",
replace: "™") +
# step 4 : copyright symbol
replace_rule(
match: "CR|\\(C\\)",
replace: "©")
# the flavor text filter # the flavor text filter
# - makes all text italic # - makes all text italic
flavor_text_filter := flavor_text_filter :=
# step 1 : Æ replacement rule # step 1 : remove italic tags
replace_rule(
match: "AE",
replace: "Æ") +
# step 1.5 : longdash
replace_rule(
match: "--",
replace: "—") +
replace_rule(
match: " - ",
replace: " — ") +
# step 2 : remove italic tags
tag_remove_rule(tag: "<i-flavor>") + tag_remove_rule(tag: "<i-flavor>") +
# step 3 : surround by <i> tags # step 2 : surround by <i> tags
{ "<i-flavor>" + input + "</i-flavor>" } + { "<i-flavor>" + input + "</i-flavor>" } +
# curly quotes # curly quotes
curly_quotes curly_quotes
# Used in FPM and Future Sight
artist_line_filter :=
replace_rule(
match: "AE",
replace: "Æ") +
replace_rule(
match: "--",
replace: "—") +
replace_rule(
match: " - ",
replace: " — ");
# Move the cursor past the separator in the p/t and type boxes # Move the cursor past the separator in the p/t and type boxes
type_over_pt := replace_rule(match:"/$", replace:"") type_over_pt := replace_rule(match:"/$", replace:"")
type_over_type := replace_rule(match:" ?-$", replace:"") type_over_type := replace_rule(match:" ?-$", replace:"")
super_type_filter := super_type_filter :=
type_over_type +
tag_remove_rule(tag: "<word-list-") + tag_remove_rule(tag: "<word-list-") +
type_over_type +
{ "<word-list-type>{input}</word-list-type>" } { "<word-list-type>{input}</word-list-type>" }
space_to_wlclass := replace_rule(match:" +", replace:"</word-list-class> <word-list-class>")
sub_type_filter := sub_type_filter :=
tag_remove_rule(tag: "<word-list-") + tag_remove_rule(tag: "<word-list-") +
{ if is_creature(type) then "<word-list-creature>{ input}</word-list-creature>" { if is_creature(type) then (
if input == "" then "<word-list-race></word-list-race>"
else (
first := only_first(input);
next := trim(only_next(input));
if next != "" then next := next + " ";
"<word-list-race>{first}</word-list-race> " +
"<word-list-class>" + space_to_wlclass(next) + "</word-list-class>"
)
)
else if is_land(type) then "<word-list-land>{ input}</word-list-land>" else if is_land(type) then "<word-list-land>{ input}</word-list-land>"
else if is_artifact(type) then "<word-list-artifact>{ input}</word-list-artifact>" else if is_artifact(type) then "<word-list-artifact>{ input}</word-list-artifact>"
else if is_enchantment(type) then "<word-list-enchantment>{input}</word-list-enchantment>" else if is_enchantment(type) then "<word-list-enchantment>{input}</word-list-enchantment>"
...@@ -439,6 +388,25 @@ init script: ...@@ -439,6 +388,25 @@ init script:
else input else input
} }
# all sub types, for word list
space_to_comma := replace_rule(match:" ", replace:",")
only_first := replace_rule(match:" .*", replace:"")
only_next := replace_rule(match:"^[^ ]* ?", replace:"")
all_sub_types := {
for each card in set do
if contains(card.super_type) then "," + space_to_comma(to_text(card.sub_type))
}
all_races := {
for each card in set do
if contains(card.super_type, match:"Creature") then
"," + only_first(to_text(card.sub_type))
}
all_classes := {
for each card in set do
if contains(card.super_type, match:"Creature") then
"," + space_to_comma(only_next(to_text(card.sub_type)))
}
# Shape of cards, can be changed in style files # Shape of cards, can be changed in style files
card_shape := { "normal" } card_shape := { "normal" }
...@@ -497,7 +465,6 @@ set field: ...@@ -497,7 +465,6 @@ set field:
set field: set field:
type: text type: text
name: copyright name: copyright
script: copyright_filter(value)
set field: set field:
type: symbol type: symbol
name: symbol name: symbol
...@@ -709,7 +676,6 @@ card field: ...@@ -709,7 +676,6 @@ card field:
type: text type: text
name: name name: name
card list visible: false card list visible: false
script: name_filter(value)
identifying: true identifying: true
show statistics: false show statistics: false
card field: card field:
...@@ -862,14 +828,12 @@ card field: ...@@ -862,14 +828,12 @@ card field:
card field: card field:
type: text type: text
name: illustrator name: illustrator
script: artist_line_filter(value)
icon: stats/illustrator.png icon: stats/illustrator.png
default: set.artist default: set.artist
description: Illustrator of this card, the default value can be changed on the 'set info' tab description: Illustrator of this card, the default value can be changed on the 'set info' tab
card field: card field:
type: text type: text
name: copyright name: copyright
script: copyright_filter(value)
default: set.copyright default: set.copyright
editable: false editable: false
show statistics: false show statistics: false
...@@ -934,7 +898,6 @@ card field: ...@@ -934,7 +898,6 @@ card field:
card field: card field:
type: text type: text
name: name 2 name: name 2
script: name_filter(value)
identifying: true identifying: true
show statistics: false show statistics: false
card field: card field:
...@@ -1053,14 +1016,12 @@ card field: ...@@ -1053,14 +1016,12 @@ card field:
card field: card field:
type: text type: text
name: illustrator 2 name: illustrator 2
script: artist_line_filter(value)
icon: stats/illustrator.png icon: stats/illustrator.png
default: set.artist default: set.artist
show statistics: false show statistics: false
card field: card field:
type: text type: text
name: copyright 2 name: copyright 2
script: copyright_filter(value)
default: set.copyright default: set.copyright
editable: false editable: false
show statistics: false show statistics: false
...@@ -1254,6 +1215,9 @@ word list: ...@@ -1254,6 +1215,9 @@ word list:
word: word:
name: Tribal name: Tribal
is prefix: true is prefix: true
word:
name: Snow
is prefix: true
line below: true line below: true
word: Creature word: Creature
word: Artifact word: Artifact
...@@ -1264,12 +1228,36 @@ word list: ...@@ -1264,12 +1228,36 @@ word list:
word: Land word: Land
word list: word list:
name: creature name: race
word: Goblin word:
script: all_races()
line below: true
word: Beast
word: Bird
word: Elemental
word: Elf word: Elf
word: Wizard word: Goblin
word: Human word: Human
# TODO: lots more word: Merfolk
word: Zombie
# TODO: lots more?
word list:
name: class
word:
script: all_classes()
line below: true
word: Cleric
word: Lord
word: Soldier
word: Warrior
word:
name: Wizard
line below: true
word:
name: All types
word: A
word: B
# TODO: Add all types here
word list: word list:
name: artifact name: artifact
...@@ -1281,7 +1269,7 @@ word list: ...@@ -1281,7 +1269,7 @@ word list:
word list: word list:
name: land name: land
word: word:
name: script: all_sub_types(match: "Land")
line below: true line below: true
word: Plains word: Plains
word: Island word: Island
...@@ -1304,6 +1292,38 @@ word list: ...@@ -1304,6 +1292,38 @@ word list:
word: Arcane word: Arcane
############################################################## Auto replace
# Do we need categories?
#auto replace category: text box
#auto replace category: copyright
#auto replace category: everywhere
auto replace:
match: (C)
replace: ©
auto replace:
match: AE
replace: Æ
whole word: false
auto replace:
match: TM
replace: ™
whole word: false
auto replace:
match: --
replace: —
auto replace:
# note the spaces
match:
-
replace:
auto replace:
match: CIP
replace: comes into play
auto replace:
match: AAA
replace: as an additional cost to play
############################################################## Add multiple cards ############################################################## Add multiple cards
...@@ -1379,7 +1399,7 @@ pack type: ...@@ -1379,7 +1399,7 @@ pack type:
has keywords: true has keywords: true
keyword match script: name_filter(value) #keyword match script: name_filter(value)
#keyword preview: {keyword} <i>({reminder})</i> #keyword preview: {keyword} <i>({reminder})</i>
keyword mode: keyword mode:
...@@ -1651,7 +1671,7 @@ keyword: ...@@ -1651,7 +1671,7 @@ keyword:
keyword: Storm keyword: Storm
match: Storm match: Storm
mode: expert mode: expert
reminder: When you play this spell, copy it for each spell played before it this turn.{ if contains(card.rule_text, match:"target") or contains(card.rule_text, match:"Target") then " You may choose new targets for the copies." else "" } reminder: When you play this spell, copy it for each spell played before it this turn.{ if is_targeted() then " You may choose new targets for the copies." }
keyword: keyword:
keyword: Affinity for keyword: Affinity for
match: Affinity for <atom-param>name</atom-param> match: Affinity for <atom-param>name</atom-param>
...@@ -1740,7 +1760,7 @@ keyword: ...@@ -1740,7 +1760,7 @@ keyword:
keyword: Epic keyword: Epic
match: Epic match: Epic
mode: expert mode: expert
reminder: For the rest of the game, you can’t play spells. At the beginning of each of your upkeeps, copy this spell except for its epic ability.{ if contains(card.rule_text, match:"target") or contains(card.rule_text, match:"Target") then " You may choose a new target for the copy." else "" } reminder: For the rest of the game, you can’t play spells. At the beginning of each of your upkeeps, copy this spell except for its epic ability.{ if is_targeted() then " You may choose a new target for the copy." }
keyword: keyword:
keyword: Channel keyword: Channel
match: Channel match: Channel
...@@ -1785,7 +1805,7 @@ keyword: ...@@ -1785,7 +1805,7 @@ keyword:
keyword: Replicate keyword: Replicate
match: Replicate <atom-param>cost</atom-param> match: Replicate <atom-param>cost</atom-param>
mode: expert mode: expert
reminder: When you play this spell, copy it for each time you paid its replicate cost.{ if contains(card.rule_text, match:"target") or contains(card.rule_text, match:"Target") then " You may choose new targets for the copies." else "" } reminder: When you play this spell, copy it for each time you paid its replicate cost.{ if is_targeted() then " You may choose new targets for the copies." }
keyword: keyword:
keyword: Graft keyword: Graft
match: Graft <atom-param>number</atom-param> match: Graft <atom-param>number</atom-param>
...@@ -1825,7 +1845,7 @@ keyword: ...@@ -1825,7 +1845,7 @@ keyword:
keyword: Suspend keyword: Suspend
match: Suspend <atom-param>number</atom-param>—<atom-param>cost</atom-param> match: Suspend <atom-param>number</atom-param>—<atom-param>cost</atom-param>
mode: expert mode: expert
reminder: Rather than play this card from your hand,{if has_cc() then " you may" else ""} {for_mana_costs(add:"pay ", param2)} and remove it from the game with {english_number_a(param1)} time counter(s) on it. At the beginning of your upkeep, remove a time counter. When the last is removed, play it without paying its mana cost.{if has_pt() then " It has haste." else ""} reminder: Rather than play this card from your hand,{if has_cc() then " you may" else ""} {for_mana_costs(add:"pay ", param2)} and remove it from the game with {english_number_a(param1)} time counter(s) on it. At the beginning of your upkeep, remove a time counter. When the last is removed, play it without paying its mana cost.{if has_pt() then " It has haste." }
keyword: keyword:
keyword: Vanishing keyword: Vanishing
match: Vanishing <atom-param>number</atom-param> match: Vanishing <atom-param>number</atom-param>
...@@ -1845,7 +1865,7 @@ keyword: ...@@ -1845,7 +1865,7 @@ keyword:
keyword: Gravestorm keyword: Gravestorm
match: Gravestorm match: Gravestorm
mode: expert mode: expert
reminder: When you play this spell, copy it for each permanent put into a graveyard this turn.{ if contains(card.rule_text, match:"target") or contains(card.rule_text, match:"Target") then " You may choose new targets for the copies." else "" } reminder: When you play this spell, copy it for each permanent put into a graveyard this turn.{ if is_targeted() then " You may choose new targets for the copies." }
keyword: keyword:
keyword: Lifelink keyword: Lifelink
match: Lifelink match: Lifelink
......
...@@ -210,26 +210,16 @@ void Locale::validate(Version ver) { ...@@ -210,26 +210,16 @@ void Locale::validate(Version ver) {
r.handle_greedy(v); r.handle_greedy(v);
// validate // validate
String errors; String errors;
// For efficiency, this needs to be parallel to LocaleCategory's values. errors += translations[LOCALE_CAT_MENU ].validate(_("menu"), v.sublocales[_("menu") ]);
String sublocales[10] = { errors += translations[LOCALE_CAT_HELP ].validate(_("help"), v.sublocales[_("help") ]);
_("menu"), errors += translations[LOCALE_CAT_TOOL ].validate(_("tool"), v.sublocales[_("tool") ]);
_("help"), errors += translations[LOCALE_CAT_TOOLTIP].validate(_("tooltip"), v.sublocales[_("tooltip")]);
_("tool"), errors += translations[LOCALE_CAT_LABEL ].validate(_("label"), v.sublocales[_("label") ]);
_("tooltip"), errors += translations[LOCALE_CAT_BUTTON ].validate(_("button"), v.sublocales[_("button") ]);
_("label"), errors += translations[LOCALE_CAT_TITLE ].validate(_("title"), v.sublocales[_("title") ]);
_("button"), errors += translations[LOCALE_CAT_ACTION ].validate(_("action"), v.sublocales[_("action") ]);
_("title"), errors += translations[LOCALE_CAT_ERROR ].validate(_("error"), v.sublocales[_("error") ]);
_("type"), errors += translations[LOCALE_CAT_TYPE ].validate(_("type"), v.sublocales[_("type") ]);
_("action"),
_("error")
};
for (String * current = sublocales; current < sublocales + 10; ++current) {
if (v.sublocales[*current])
errors += translations[current - sublocales].validate(*current, *v.sublocales[*current]);
else
errors += _("\nError validating local file: expected keys file missing \"") + *current + _("\" section.");
}
// errors? // errors?
if (!errors.empty()) { if (!errors.empty()) {
if (ver != app_version) { if (ver != app_version) {
...@@ -242,10 +232,13 @@ void Locale::validate(Version ver) { ...@@ -242,10 +232,13 @@ void Locale::validate(Version ver) {
} }
} }
String SubLocale::validate(const String& name, const SubLocaleValidator& v) const { 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; String errors;
// 1. keys in v but not in this, check arg count // 1. keys in v but not in this, check arg count
FOR_EACH_CONST(kc, v.keys) { FOR_EACH_CONST(kc, v->keys) {
map<String,String>::const_iterator it = translations.find(kc.first); map<String,String>::const_iterator it = translations.find(kc.first);
if (it == translations.end()) { if (it == translations.end()) {
if (!kc.second.optional) { if (!kc.second.optional) {
...@@ -258,8 +251,8 @@ String SubLocale::validate(const String& name, const SubLocaleValidator& v) cons ...@@ -258,8 +251,8 @@ String SubLocale::validate(const String& name, const SubLocaleValidator& v) cons
} }
// 2. keys in this but not in v // 2. keys in this but not in v
FOR_EACH_CONST(kv, translations) { FOR_EACH_CONST(kv, translations) {
map<String,KeyValidator>::const_iterator it = v.keys.find(kv.first); map<String,KeyValidator>::const_iterator it = v->keys.find(kv.first);
if (it == v.keys.end() && !kv.second.empty()) { if (it == v->keys.end() && !kv.second.empty()) {
// allow extra keys with empty values as a kind of documentation // allow extra keys with empty values as a kind of documentation
// for example in the help stirngs: // for example in the help stirngs:
// help: // help:
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
DECLARE_POINTER_TYPE(Locale); DECLARE_POINTER_TYPE(Locale);
DECLARE_POINTER_TYPE(SubLocale); DECLARE_POINTER_TYPE(SubLocale);
class SubLocaleValidator; DECLARE_POINTER_TYPE(SubLocaleValidator);
// ----------------------------------------------------------------------------- : Locale class // ----------------------------------------------------------------------------- : Locale class
...@@ -31,7 +31,7 @@ class SubLocale : public IntrusivePtrBase<SubLocale> { ...@@ -31,7 +31,7 @@ class SubLocale : public IntrusivePtrBase<SubLocale> {
String tr(const String& key, const String& def); String tr(const String& key, const String& def);
/// Is this a valid sublocale? Returns errors /// Is this a valid sublocale? Returns errors
String validate(const String& name, const SubLocaleValidator&) const; String validate(const String& name, const SubLocaleValidatorP&) const;
DECLARE_REFLECTION(); DECLARE_REFLECTION();
}; };
......
...@@ -16,12 +16,13 @@ WordListWord::WordListWord() ...@@ -16,12 +16,13 @@ WordListWord::WordListWord()
{} {}
IMPLEMENT_REFLECTION_NO_SCRIPT(WordListWord) { IMPLEMENT_REFLECTION_NO_SCRIPT(WordListWord) {
if (line_below || is_prefix || isGroup() || (tag.reading() && tag.isComplex())) { if (line_below || is_prefix || isGroup() || script || (tag.reading() && tag.isComplex())) {
// complex value // complex value
REFLECT(name); REFLECT(name);
REFLECT(line_below); REFLECT(line_below);
REFLECT(is_prefix); REFLECT(is_prefix);
REFLECT(words); REFLECT(words);
REFLECT(script);
} else { } else {
REFLECT_NAMELESS(name); REFLECT_NAMELESS(name);
} }
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include <util/prec.hpp> #include <util/prec.hpp>
#include <util/reflect.hpp> #include <util/reflect.hpp>
#include <script/scriptable.hpp>
DECLARE_POINTER_TYPE(WordListWord); DECLARE_POINTER_TYPE(WordListWord);
DECLARE_POINTER_TYPE(WordList); DECLARE_POINTER_TYPE(WordList);
...@@ -27,6 +28,7 @@ class WordListWord : public IntrusivePtrBase<WordListWord> { ...@@ -27,6 +28,7 @@ class WordListWord : public IntrusivePtrBase<WordListWord> {
bool line_below; ///< Line below in the list? bool line_below; ///< Line below in the list?
bool is_prefix; ///< Is this a prefix before other words? bool is_prefix; ///< Is this a prefix before other words?
vector<WordListWordP> words; ///< Sublist 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(); }
......
...@@ -25,6 +25,7 @@ DataEditor::DataEditor(Window* parent, int id, long style) ...@@ -25,6 +25,7 @@ DataEditor::DataEditor(Window* parent, int id, long style)
: CardViewer(parent, id, style) : CardViewer(parent, id, style)
, current_viewer(nullptr) , current_viewer(nullptr)
, current_editor(nullptr) , current_editor(nullptr)
, hovered_viewer(nullptr)
{ {
// Create a caret // Create a caret
SetCaret(new wxCaret(this,1,1)); SetCaret(new wxCaret(this,1,1));
...@@ -134,6 +135,7 @@ void DataEditor::onInit() { ...@@ -134,6 +135,7 @@ void DataEditor::onInit() {
createTabIndex(); createTabIndex();
current_viewer = nullptr; current_viewer = nullptr;
current_editor = nullptr; current_editor = nullptr;
hovered_viewer = nullptr;
} }
// ----------------------------------------------------------------------------- : Search / replace // ----------------------------------------------------------------------------- : Search / replace
...@@ -213,24 +215,44 @@ void DataEditor::onMotion(wxMouseEvent& ev) { ...@@ -213,24 +215,44 @@ void DataEditor::onMotion(wxMouseEvent& ev) {
current_editor->onMotion(pos, ev); current_editor->onMotion(pos, ev);
} }
if (!HasCapture()) { if (!HasCapture()) {
// change cursor and set status text // find editor under mouse
wxFrame* frame = dynamic_cast<wxFrame*>( wxGetTopLevelParent(this) ); ValueViewer* new_hovered_viewer = nullptr;
FOR_EACH_EDITOR_REVERSE { // find high z index fields first FOR_EACH_EDITOR_REVERSE { // find high z index fields first
if (v->containsPoint(pos) && v->getField()->editable) { if (v->containsPoint(pos) && v->getField()->editable) {
wxCursor c = e->cursor(pos); new_hovered_viewer = v.get();
if (c.Ok()) SetCursor(c); break;
else SetCursor(wxCURSOR_ARROW);
if (frame) frame->SetStatusText(v->getField()->description);
return;
} }
} }
// no field under cursor if (hovered_viewer && hovered_viewer != new_hovered_viewer) {
SetCursor(wxCURSOR_ARROW); ValueEditor* e = hovered_viewer->getEditor();
if (frame) frame->SetStatusText(wxEmptyString); if (e) e->onMouseLeave(pos, ev);
}
hovered_viewer = new_hovered_viewer;
// change cursor and set status text
wxFrame* frame = dynamic_cast<wxFrame*>( wxGetTopLevelParent(this) );
if (hovered_viewer) {
ValueEditor* e = hovered_viewer->getEditor();
wxCursor c;
if (e) c = e->cursor(pos);
if (c.Ok()) SetCursor(c);
else SetCursor(wxCURSOR_ARROW);
if (frame) frame->SetStatusText(hovered_viewer->getField()->description);
} else {
// no field under cursor
SetCursor(wxCURSOR_ARROW);
if (frame) frame->SetStatusText(wxEmptyString);
}
} }
} }
void DataEditor::onMouseLeave(wxMouseEvent& ev) { void DataEditor::onMouseLeave(wxMouseEvent& ev) {
// on mouse leave for editor
if (hovered_viewer) {
ValueEditor* e = hovered_viewer->getEditor();
if (e) e->onMouseLeave(mousePoint(ev), ev);
hovered_viewer = nullptr;
}
// clear status text
wxFrame* frame = dynamic_cast<wxFrame*>( wxGetTopLevelParent(this) ); wxFrame* frame = dynamic_cast<wxFrame*>( wxGetTopLevelParent(this) );
if (frame) frame->SetStatusText(wxEmptyString); if (frame) frame->SetStatusText(wxEmptyString);
} }
......
...@@ -86,6 +86,7 @@ class DataEditor : public CardViewer { ...@@ -86,6 +86,7 @@ class DataEditor : public CardViewer {
// --------------------------------------------------- : Data // --------------------------------------------------- : Data
ValueViewer* current_viewer; ///< The currently selected viewer ValueViewer* current_viewer; ///< The currently selected viewer
ValueEditor* current_editor; ///< The currently selected editor, corresponding to the viewer ValueEditor* current_editor; ///< The currently selected editor, corresponding to the viewer
ValueViewer* hovered_viewer; ///< The editor under the mouse cursor
vector<ValueViewer*> by_tab_index; ///< The editable viewers, sorted by tab index vector<ValueViewer*> by_tab_index; ///< The editable viewers, sorted by tab index
private: private:
......
...@@ -291,7 +291,7 @@ void DropDownList::drawItem(DC& dc, int y, size_t item) { ...@@ -291,7 +291,7 @@ void DropDownList::drawItem(DC& dc, int y, size_t item) {
draw_menu_arrow(this, dc, RealRect(marginW, y, item_size.width, item_size.height), item == selected_item); draw_menu_arrow(this, dc, RealRect(marginW, y, item_size.width, item_size.height), item == selected_item);
} }
// draw line below // draw line below
if (lineBelow(item)) { if (lineBelow(item) && item != itemCount()) {
dc.SetPen(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); dc.SetPen(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
dc.DrawLine(marginW, y + (int)item_size.height, marginW + (int)item_size.width, y + (int)item_size.height); dc.DrawLine(marginW, y + (int)item_size.height, marginW + (int)item_size.width, y + (int)item_size.height);
} }
......
...@@ -47,6 +47,7 @@ class ValueEditor { ...@@ -47,6 +47,7 @@ class ValueEditor {
virtual bool onLeftDClick (const RealPoint& pos, wxMouseEvent& ev) { return false; } virtual bool onLeftDClick (const RealPoint& pos, wxMouseEvent& ev) { return false; }
virtual bool onRightDown (const RealPoint& pos, wxMouseEvent& ev) { return false; } virtual bool onRightDown (const RealPoint& pos, wxMouseEvent& ev) { return false; }
virtual bool onMotion (const RealPoint& pos, wxMouseEvent& ev) { return false; } virtual bool onMotion (const RealPoint& pos, wxMouseEvent& ev) { return false; }
virtual void onMouseLeave (const RealPoint& pos, wxMouseEvent& ev) {}
virtual bool onMouseWheel (const RealPoint& pos, wxMouseEvent& ev) { return false; } virtual bool onMouseWheel (const RealPoint& pos, wxMouseEvent& ev) { return false; }
/// Key events /// Key events
......
...@@ -20,11 +20,16 @@ ...@@ -20,11 +20,16 @@
#include <wx/clipbrd.h> #include <wx/clipbrd.h>
#include <wx/caret.h> #include <wx/caret.h>
#undef small // some evil windows header defines this
DECLARE_SHARED_POINTER_TYPE(DropDownList); DECLARE_SHARED_POINTER_TYPE(DropDownList);
DECLARE_TYPEOF_COLLECTION(WordListP); DECLARE_TYPEOF_COLLECTION(WordListP);
DECLARE_TYPEOF_COLLECTION(WordListWordP); DECLARE_TYPEOF_COLLECTION(WordListWordP);
DECLARE_TYPEOF_COLLECTION(WordListPosP); DECLARE_TYPEOF_COLLECTION(WordListPosP);
DECLARE_TYPEOF_COLLECTION(AutoReplaceP); DECLARE_TYPEOF_COLLECTION(AutoReplaceP);
struct DropDownWordListItem;
DECLARE_TYPEOF_COLLECTION(DropDownWordListItem);
DECLARE_TYPEOF_COLLECTION(String);
// ----------------------------------------------------------------------------- : TextValueEditorScrollBar // ----------------------------------------------------------------------------- : TextValueEditorScrollBar
...@@ -72,13 +77,40 @@ class WordListPos : public IntrusivePtrBase<WordListPos> { ...@@ -72,13 +77,40 @@ class WordListPos : public IntrusivePtrBase<WordListPos> {
: start(start), end(end) : start(start), end(end)
, rect(-1,-1,-1,-1) , rect(-1,-1,-1,-1)
, word_list(word_list) , word_list(word_list)
, active(false)
{} {}
const size_t start, end; ///< Start and ending indices const size_t start, end; ///< Start and ending indices
RealRect rect; ///< Rectangle around word list text RealRect rect; ///< Rectangle around word list text
WordListP word_list; ///< Word list to use WordListP word_list; ///< Word list to use
bool active; ///< Is the list dropped down right now? Bitmap behind; ///< Bitmap behind the button
};
enum WordListItemFlags
{ FLAG_ACTIVE = 0x01
, FLAG_SUBMENU = 0x02
, FLAG_LINE_BELOW = 0x04
};
struct DropDownWordListItem {
DropDownWordListItem() : word(nullptr), flags(0) {}
DropDownWordListItem(WordListWord* word, int flags = 0)
: word(word)
, name(word->name)
, flags(flags | (word->isGroup() * FLAG_SUBMENU)
| (word->line_below * FLAG_LINE_BELOW))
{}
DropDownWordListItem(WordListWord* word, const String& name, int flags = 0)
: word(word)
, name(name)
, flags(flags)
{}
WordListWord* word;
String name;
int flags;
DropDownListP submenu;
inline bool active() const { return flags & FLAG_ACTIVE; }
inline void setActive(bool a) { flags = flags & ~FLAG_ACTIVE | a * FLAG_ACTIVE; }
}; };
class DropDownWordList : public DropDownList { class DropDownWordList : public DropDownList {
...@@ -88,13 +120,13 @@ class DropDownWordList : public DropDownList { ...@@ -88,13 +120,13 @@ class DropDownWordList : public DropDownList {
void setWords(const WordListWordP& words2); void setWords(const WordListWordP& words2);
void setWords(const WordListPosP& pos2); void setWords(const WordListPosP& pos2);
inline WordListPosP getPos() const { return pos; }
protected: protected:
virtual void onShow();
virtual void onHide();
virtual void redrawArrowOnParent(); virtual void redrawArrowOnParent();
virtual size_t itemCount() const { return words->words.size(); } virtual size_t itemCount() const { return items.size(); }
virtual bool lineBelow(size_t item) const { return words->words[item]->line_below; } virtual bool lineBelow(size_t item) const { return items[item].flags & FLAG_LINE_BELOW; }
virtual String itemText(size_t item) const { return words->words[item]->name; } virtual String itemText(size_t item) const { return items[item].name; }
virtual void drawIcon(DC& dc, int x, int y, size_t item, bool selected) const; virtual void drawIcon(DC& dc, int x, int y, size_t item, bool selected) const;
virtual DropDownList* submenu(size_t item) const; virtual DropDownList* submenu(size_t item) const;
virtual size_t selection() const; virtual size_t selection() const;
...@@ -104,8 +136,9 @@ class DropDownWordList : public DropDownList { ...@@ -104,8 +136,9 @@ class DropDownWordList : public DropDownList {
WordListPosP pos; WordListPosP pos;
WordListWordP words; ///< The words we are listing WordListWordP words; ///< The words we are listing
bool has_checkboxes; ///< Do we need checkboxes? bool has_checkboxes; ///< Do we need checkboxes?
mutable vector<int> active; ///< Which items are checked? mutable vector <DropDownWordListItem> items;
mutable vector<DropDownListP> submenus;
void addWordsFromScript(const WordListWordP& w);
}; };
...@@ -124,27 +157,64 @@ void DropDownWordList::setWords(const WordListPosP& pos2) { ...@@ -124,27 +157,64 @@ void DropDownWordList::setWords(const WordListPosP& pos2) {
void DropDownWordList::setWords(const WordListWordP& words2) { void DropDownWordList::setWords(const WordListWordP& words2) {
if (words == words2) return; if (words == words2) return;
// switch to different list // switch to different list
submenus.clear(); items.clear();
words = words2; words = words2;
// init items; do we need checkboxes?
// do we need checkboxes? // do we need checkboxes?
has_checkboxes = false; has_checkboxes = false;
FOR_EACH(w, words->words) { FOR_EACH(w, words->words) {
if (w->is_prefix) { if (w->is_prefix) has_checkboxes = true;
has_checkboxes = true; if (w->script) {
break; addWordsFromScript(w);
} else {
// only if not duplicating
bool already_added = false;
FOR_EACH(i,items) {
if (i.name == w->name) {
already_added = true;
break;
}
}
if (!already_added || w->isGroup()) {
items.push_back(DropDownWordListItem(w.get()));
}
} }
} }
// size of items // size of items
icon_size.width = has_checkboxes ? 16 : 0; icon_size.width = has_checkboxes ? 16 : 0;
item_size.height = max(16., item_size.height); item_size.height = max(16., item_size.height);
} }
void DropDownWordList::addWordsFromScript(const WordListWordP& w) {
void DropDownWordList::onShow() { assert(w->script);
pos->active = true; // run script
} Context& ctx = tve.viewer.getContext();
void DropDownWordList::onHide() { String str = w->script.invoke(ctx)->toString();
if (isRoot()) { // collect items
pos->active = false; vector<String> strings;
{
size_t prev = 0;
size_t comma = str.find_first_of(_(','));
while (comma != String::npos) {
strings.push_back(str.substr(prev, comma - prev));
prev = comma + 1;
if (prev + 1 < str.size() && str.GetChar(prev + 1) == _(' ')) ++prev; // skip space after comma
comma = str.find_first_of(_(','), prev);
}
strings.push_back(str.substr(prev));
sort(strings.begin(), strings.end());
}
// add to menu
size_t prev = items.size();
FOR_EACH(s, strings) {
if (prev >= items.size() || s != items[prev].name) {
// no line below prev
if (prev < items.size() && !items[prev].name.empty()) {
items[prev].flags = 0;
}
// not a duplicate
prev = items.size();
items.push_back(DropDownWordListItem(w.get(), s, w->line_below * FLAG_LINE_BELOW));
}
} }
} }
...@@ -157,22 +227,25 @@ void DropDownWordList::drawIcon(DC& dc, int x, int y, size_t item, bool selected ...@@ -157,22 +227,25 @@ void DropDownWordList::drawIcon(DC& dc, int x, int y, size_t item, bool selected
dc.DrawRectangle(x,y,16,16); dc.DrawRectangle(x,y,16,16);
wxRect rect = RealRect(x+2,y+2,12,12); wxRect rect = RealRect(x+2,y+2,12,12);
if (radio) { if (radio) {
draw_radiobox(nullptr, dc, rect, active[item], itemEnabled(item)); draw_radiobox(nullptr, dc, rect, items[item].active(), itemEnabled(item));
} else { } else {
draw_checkbox(nullptr, dc, rect, active[item], itemEnabled(item)); draw_checkbox(nullptr, dc, rect, items[item].active(), itemEnabled(item));
} }
} }
} }
DropDownList* DropDownWordList::submenu(size_t item) const { DropDownList* DropDownWordList::submenu(size_t item) const {
if (item >= submenus.size()) submenus.resize(item + 1); DropDownWordListItem& i = items[item];
if (submenus[item]) return submenus[item].get(); if (i.submenu) return i.submenu.get();
WordListWordP word = words->words[item]; if (i.flags & FLAG_SUBMENU) {
if (word->isGroup()) { // create submenu?
// create submenu if (!i.submenu) {
submenus[item].reset(new DropDownWordList(const_cast<DropDownWordList*>(this), true, tve, pos, word)); i.submenu.reset(new DropDownWordList(const_cast<DropDownWordList*>(this), true, tve, pos, i.word));
}
return i.submenu.get();
} else {
return nullptr;
} }
return submenus[item].get();
} }
...@@ -182,24 +255,23 @@ size_t DropDownWordList::selection() const { ...@@ -182,24 +255,23 @@ size_t DropDownWordList::selection() const {
// find selection // find selection
size_t selected = NO_SELECTION; size_t selected = NO_SELECTION;
bool prefix_selected = true; bool prefix_selected = true;
size_t i = 0; size_t n = 0;
active.resize(words->words.size()); FOR_EACH(item, items) {
FOR_EACH(w, words->words) { if (item.word->is_prefix) {
if (w->is_prefix) { if (starts_with(current, item.name)) {
if (starts_with(current, w->name)) { item.setActive(true);
active[i] = true; current = current.substr(item.name.size());
current = current.substr(w->name.size());
} else { } else {
active[i] = false; item.setActive(false);
} }
} else { } else {
active[i] = (current == w->name); item.setActive((current == item.name));
} }
if (active[i] && (selected == NO_SELECTION || (prefix_selected && !w->is_prefix))) { if (item.active() && (selected == NO_SELECTION || (prefix_selected && !item.word->is_prefix))) {
selected = i; selected = n;
prefix_selected = w->is_prefix; prefix_selected = item.word->is_prefix;
} }
++i; ++n;
} }
return selected; return selected;
} }
...@@ -207,17 +279,20 @@ size_t DropDownWordList::selection() const { ...@@ -207,17 +279,20 @@ size_t DropDownWordList::selection() const {
void DropDownWordList::select(size_t item) { void DropDownWordList::select(size_t item) {
// determine new value // determine new value
String new_value; String new_value;
bool toggling_prefix = words->words[item]->is_prefix; bool toggling_prefix = items[item].word->is_prefix;
for (size_t i = 0 ; i < words->words.size() ; ++i) { for (size_t i = 0 ; i < words->words.size() ; ++i) {
const WordListWord& w = *words->words[i]; const DropDownWordListItem& it = items[i];
if (w.is_prefix && active[i] != (i == item)) { if (it.word->is_prefix) {
new_value += w.name; if (it.active() != (i == item)) {
} else if (!w.is_prefix && (i == item || (toggling_prefix && active[i]))) { new_value += it.name;
new_value += w.name; }
} else {
if (i == item || (toggling_prefix && items[i].active())) {
new_value += it.name;
}
} }
} }
// set value // set value
pos->active = false;
tve.selection_start_i = pos->start; tve.selection_start_i = pos->start;
tve.selection_end_i = pos->end; tve.selection_end_i = pos->end;
tve.fixSelection(TYPE_INDEX); tve.fixSelection(TYPE_INDEX);
...@@ -226,7 +301,7 @@ void DropDownWordList::select(size_t item) { ...@@ -226,7 +301,7 @@ void DropDownWordList::select(size_t item) {
} }
void DropDownWordList::redrawArrowOnParent() { void DropDownWordList::redrawArrowOnParent() {
tve.redrawSelection(tve.selection_start_i, tve.selection_end_i, !tve.dropDownShown()); tve.redrawWordListIndicators();
} }
// ----------------------------------------------------------------------------- : TextValueEditor // ----------------------------------------------------------------------------- : TextValueEditor
...@@ -236,6 +311,7 @@ IMPLEMENT_VALUE_EDITOR(Text) ...@@ -236,6 +311,7 @@ IMPLEMENT_VALUE_EDITOR(Text)
, selection_start_i(0), selection_end_i(0) , selection_start_i(0), selection_end_i(0)
, selecting(false), select_words(false) , selecting(false), select_words(false)
, scrollbar(nullptr), scroll_with_cursor(false) , scrollbar(nullptr), scroll_with_cursor(false)
, hovered_words(nullptr)
{ {
if (viewer.nativeLook() && field().multi_line) { if (viewer.nativeLook() && field().multi_line) {
scrollbar = new TextValueEditorScrollBar(*this); scrollbar = new TextValueEditorScrollBar(*this);
...@@ -291,6 +367,13 @@ bool TextValueEditor::onMotion(const RealPoint& pos, wxMouseEvent& ev) { ...@@ -291,6 +367,13 @@ bool TextValueEditor::onMotion(const RealPoint& pos, wxMouseEvent& ev) {
return true; return true;
} }
void TextValueEditor::onMouseLeave(const RealPoint& pos, wxMouseEvent& ev) {
if (hovered_words) {
hovered_words = nullptr;
redrawWordListIndicators();
}
}
bool TextValueEditor::onLeftDClick(const RealPoint& pos, wxMouseEvent& ev) { bool TextValueEditor::onLeftDClick(const RealPoint& pos, wxMouseEvent& ev) {
if (dropDownShown()) return false; if (dropDownShown()) return false;
select_words = true; select_words = true;
...@@ -538,7 +621,7 @@ void TextValueEditor::redrawSelection(size_t old_selection_start_i, size_t old_s ...@@ -538,7 +621,7 @@ void TextValueEditor::redrawSelection(size_t old_selection_start_i, size_t old_s
v.drawSelection(dc, style(), selection_start_i, selection_end_i); v.drawSelection(dc, style(), selection_start_i, selection_end_i);
} }
// redraw drop down indicators // redraw drop down indicators
drawWordListIndicators(dc); drawWordListIndicators(dc, true);
} }
} }
showCaret(); showCaret();
...@@ -550,18 +633,46 @@ wxCursor rotated_ibeam; ...@@ -550,18 +633,46 @@ wxCursor rotated_ibeam;
wxCursor TextValueEditor::cursor(const RealPoint& pos) const { wxCursor TextValueEditor::cursor(const RealPoint& pos) const {
RealPoint pos2 = style().getRotation().trInv(pos); RealPoint pos2 = style().getRotation().trInv(pos);
if (findWordList(pos2)) { WordListPosP p = findWordList(pos2);
return wxCursor(); if (p) {
} else if (viewer.getRotation().sideways() ^ style().getRotation().sideways()) { // 90 or 270 degrees if (hovered_words != p.get()) {
if (!rotated_ibeam.Ok()) { hovered_words = p.get();
rotated_ibeam = wxCursor(load_resource_cursor(_("rot_text"))); const_cast<TextValueEditor*>(this)->redrawWordListIndicators();
} }
return rotated_ibeam; return wxCursor();
} else { } else {
return wxCURSOR_IBEAM; p = findWordListBody(pos2);
if (hovered_words != p.get()) {
hovered_words = p.get();
const_cast<TextValueEditor*>(this)->redrawWordListIndicators();
}
if (viewer.getRotation().sideways() ^ style().getRotation().sideways()) { // 90 or 270 degrees
if (!rotated_ibeam.Ok()) {
rotated_ibeam = wxCursor(load_resource_cursor(_("rot_text")));
}
return rotated_ibeam;
} else {
return wxCURSOR_IBEAM;
}
} }
} }
bool TextValueEditor::containsPoint(const RealPoint& pos) const {
if (TextValueViewer::containsPoint(pos)) return true;
if (word_lists.empty()) return false;
RealPoint pos2 = style().getRotation().trInv(pos);
return findWordList(pos2);
}
RealRect TextValueEditor::boundingBox() const {
if (word_lists.empty()) return ValueViewer::boundingBox();
RealRect r = style().getRect().grow(1);
Rotation rot = style().getRotation();
FOR_EACH_CONST(wl, word_lists) {
r.width = max(r.width, rot.tr(wl->rect).right() + 9);
}
return r;
}
void TextValueEditor::onValueChange() { void TextValueEditor::onValueChange() {
TextValueViewer::onValueChange(); TextValueViewer::onValueChange();
selection_start = selection_end = 0; selection_start = selection_end = 0;
...@@ -1138,12 +1249,13 @@ void TextValueEditor::prepareDrawScrollbar(RotatedDC& dc) { ...@@ -1138,12 +1249,13 @@ void TextValueEditor::prepareDrawScrollbar(RotatedDC& dc) {
// ----------------------------------------------------------------------------- : Word lists // ----------------------------------------------------------------------------- : Word lists
bool TextValueEditor::dropDownShown() { bool TextValueEditor::dropDownShown() const {
return drop_down && drop_down->IsShown(); return drop_down && drop_down->IsShown();
} }
void TextValueEditor::findWordLists() { void TextValueEditor::findWordLists() {
word_lists.clear(); word_lists.clear();
hovered_words = nullptr;
// for each word list... // for each word list...
const String& str = value().value(); const String& str = value().value();
size_t pos = str.find(_("<word-list-")); size_t pos = str.find(_("<word-list-"));
...@@ -1170,60 +1282,112 @@ void TextValueEditor::findWordLists() { ...@@ -1170,60 +1282,112 @@ void TextValueEditor::findWordLists() {
} }
} }
void TextValueEditor::drawWordListIndicators(RotatedDC& dc) { void TextValueEditor::redrawWordListIndicators() {
if (isCurrent()) {
// Hide caret
wxCaret* caret = editor().GetCaret();
if (caret->IsVisible()) caret->Hide();
}
drawWordListIndicators(*editor().overdrawDC(), true);
if (isCurrent()) {
showCaret();
}
}
void TextValueEditor::drawWordListIndicators(RotatedDC& dc, bool redrawing) {
Rotater rot(dc, style().getRotation()); Rotater rot(dc, style().getRotation());
bool current = isCurrent(); bool current = isCurrent();
// Draw lines around fields
FOR_EACH(wl, word_lists) { FOR_EACH(wl, word_lists) {
RealRect& r = wl->rect; RealRect& r = wl->rect;
if (r.height < 0) { if (r.height < 0) {
// find the rectangle for this indicator // find the rectangle for this indicator
RealRect start = v.charRect(wl->start); RealRect start = v.charRect(wl->start);
RealRect end = v.charRect(wl->end); RealRect end = v.charRect(wl->end > wl->start ? wl->end - 1 : wl->start);
r.x = start.x; r.x = start.x;
r.y = start.y; r.y = start.y;
r.width = end.right() - start.left() + 0.5; r.width = end.right() - start.left() + 0.5;
r.height = end.bottom() - start.top(); r.height = end.bottom() - start.top();
} }
// draw background // color?
if (current && wl->active) { bool small = false;
if (current && drop_down && drop_down->IsShown() && drop_down->getPos() == wl) {
dc.SetPen (Color(0, 128,255)); dc.SetPen (Color(0, 128,255));
dc.SetBrush(Color(64, 160,255));
} else if (current && selection_end_i >= wl->start && selection_end_i <= wl->end && !dropDownShown()) { } else if (current && selection_end_i >= wl->start && selection_end_i <= wl->end && !dropDownShown()) {
dc.SetPen (Color(64, 160,255)); dc.SetPen (Color(64, 160,255));
dc.SetBrush(Color(160,208,255));
} else { } else {
dc.SetPen (Color(128,128,128)); dc.SetPen (Color(128,128,128));
dc.SetBrush(Color(192,192,192)); small = (wl.get() != hovered_words);
}
// capture background?
if (!redrawing) {
wl->behind = dc.GetBackground(RealRect(r.right(), r.top() - 1, 10, r.height + 3));
} else if (small && wl->behind.Ok()) {
// restore background
dc.DrawBitmap(wl->behind, r.topRight() + RealSize(0,-1));
} }
dc.DrawRectangle(RealRect(r.right(), r.top() - 1, 9, r.height + 2));
// draw rectangle around value // draw rectangle around value
dc.SetBrush(*wxTRANSPARENT_BRUSH); dc.SetBrush(*wxTRANSPARENT_BRUSH);
dc.DrawRectangle(r.move(-1,-1,2,2)); dc.DrawRectangle(r.move(-1,-1,2,2));
// draw foreground }
/* // Draw drop down arrows
dc.SetPen (*wxTRANSPARENT_PEN); FOR_EACH_REVERSE(wl, word_lists) {
dc.SetBrush(*wxBLACK_BRUSH); RealRect& r = wl->rect;
wxPoint poly[] = {dc.tr(RealPoint(0,0)), dc.tr(RealPoint(5,0)), dc.tr(RealPoint(3,2))}; // color
dc.getDC().DrawPolygon(3, poly, r.right() + 2, r.bottom() - 5); bool small = false;
*/ if (current && drop_down && drop_down->IsShown() && drop_down->getPos() == wl) {
dc.SetPen (*wxBLACK_PEN); dc.SetPen (Color(0, 128,255));
double x = r.right(), y = r.bottom() - 1; dc.SetBrush(Color(64, 160,255));
dc.DrawLine(RealPoint(x + 4, y - 3), RealPoint(x + 5, y - 3)); } else if (current && selection_end_i >= wl->start && selection_end_i <= wl->end && !dropDownShown()) {
dc.DrawLine(RealPoint(x + 3, y - 4), RealPoint(x + 6, y - 4)); dc.SetPen (Color(64, 160,255));
dc.DrawLine(RealPoint(x + 2, y - 5), RealPoint(x + 7, y - 5)); dc.SetBrush(Color(160,208,255));
} else {
dc.SetPen (Color(128,128,128));
dc.SetBrush(Color(192,192,192));
small = (wl.get() != hovered_words);
}
if (small) {
dc.DrawRectangle(RealRect(r.right(), r.top() - 1, 2, r.height + 2));
} else {
// draw background of drop down button
dc.DrawRectangle(RealRect(r.right(), r.top() - 1, 9, r.height + 2));
// draw foreground
/*
dc.SetPen (*wxTRANSPARENT_PEN);
dc.SetBrush(*wxBLACK_BRUSH);
wxPoint poly[] = {dc.tr(RealPoint(0,0)), dc.tr(RealPoint(5,0)), dc.tr(RealPoint(3,2))};
dc.getDC().DrawPolygon(3, poly, r.right() + 2, r.bottom() - 5);
*/
dc.SetPen (*wxBLACK_PEN);
double x = r.right(), y = r.bottom() - 1;
dc.DrawLine(RealPoint(x + 4, y - 3), RealPoint(x + 5, y - 3));
dc.DrawLine(RealPoint(x + 3, y - 4), RealPoint(x + 6, y - 4));
dc.DrawLine(RealPoint(x + 2, y - 5), RealPoint(x + 7, y - 5));
}
} }
} }
WordListPosP TextValueEditor::findWordList(const RealPoint& pos) const { WordListPosP TextValueEditor::findWordList(const RealPoint& pos) const {
FOR_EACH_CONST(wl, word_lists) { FOR_EACH_CONST(wl, word_lists) {
const RealRect& r = wl->rect; const RealRect& r = wl->rect;
if (pos.x >= r.right() && pos.x < r.right() + 9 && if (pos.x >= r.right() - 0.5 && pos.x < r.right() + 9 &&
pos.y >= r.top() && pos.y < r.bottom()) { pos.y >= r.top() && pos.y < r.bottom()) {
return wl; return wl;
} }
} }
return WordListPosP(); return WordListPosP();
} }
WordListPosP TextValueEditor::findWordListBody(const RealPoint& pos) const {
FOR_EACH_CONST(wl, word_lists) {
const RealRect& r = wl->rect;
if (pos.x >= r.left() && pos.x < r.right() &&
pos.y >= r.top() && pos.y < r.bottom()) {
return wl;
}
}
return WordListPosP();
}
WordListPosP TextValueEditor::findWordList(size_t index) const { WordListPosP TextValueEditor::findWordList(size_t index) const {
FOR_EACH_CONST(wl, word_lists) { FOR_EACH_CONST(wl, word_lists) {
if (index >= wl->start && index <= wl->end) { if (index >= wl->start && index <= wl->end) {
......
...@@ -43,7 +43,8 @@ class TextValueEditor : public TextValueViewer, public ValueEditor { ...@@ -43,7 +43,8 @@ class TextValueEditor : public TextValueViewer, public ValueEditor {
virtual bool onLeftDClick(const RealPoint& pos, wxMouseEvent&); virtual bool onLeftDClick(const RealPoint& pos, wxMouseEvent&);
virtual bool onRightDown (const RealPoint& pos, wxMouseEvent&); virtual bool onRightDown (const RealPoint& pos, wxMouseEvent&);
virtual bool onMotion (const RealPoint& pos, wxMouseEvent&); virtual bool onMotion (const RealPoint& pos, wxMouseEvent&);
virtual bool onMouseWheel(const RealPoint& pos, wxMouseEvent& ev); virtual void onMouseLeave(const RealPoint& pos, wxMouseEvent&);
virtual bool onMouseWheel(const RealPoint& pos, wxMouseEvent&);
virtual bool onContextMenu(IconMenu& m, wxContextMenuEvent&); virtual bool onContextMenu(IconMenu& m, wxContextMenuEvent&);
virtual wxMenu* getMenu(int type) const; virtual wxMenu* getMenu(int type) const;
...@@ -89,6 +90,8 @@ class TextValueEditor : public TextValueViewer, public ValueEditor { ...@@ -89,6 +90,8 @@ class TextValueEditor : public TextValueViewer, public ValueEditor {
virtual wxCursor cursor(const RealPoint& pos) const; virtual wxCursor cursor(const RealPoint& pos) const;
virtual void determineSize(bool force_fit = false); virtual void determineSize(bool force_fit = false);
virtual bool containsPoint(const RealPoint& p) const;
virtual RealRect boundingBox() const;
virtual void onShow(bool); virtual void onShow(bool);
virtual void draw(RotatedDC&); virtual void draw(RotatedDC&);
...@@ -162,14 +165,19 @@ class TextValueEditor : public TextValueViewer, public ValueEditor { ...@@ -162,14 +165,19 @@ class TextValueEditor : public TextValueViewer, public ValueEditor {
friend class DropDownWordList; friend class DropDownWordList;
DropDownWordListP drop_down; DropDownWordListP drop_down;
bool dropDownShown(); bool dropDownShown() const;
mutable WordListPos* hovered_words;
/// Find all word lists in the current value /// Find all word lists in the current value
void findWordLists(); void findWordLists();
/// Draw word list indicators /// Draw word list indicators
void drawWordListIndicators(RotatedDC& dc); void drawWordListIndicators(RotatedDC& dc, bool redrawing = false);
/// Re-draw word list indicators
void redrawWordListIndicators();
/// Find a WordListPos under the mouse cursor (if any), pos is in internal coordinates /// Find a WordListPos under the mouse cursor (if any), pos is in internal coordinates
WordListPosP findWordList(const RealPoint& pos) const; WordListPosP findWordList(const RealPoint& pos) const;
/// Find a WordListPos rectangle under the mouse cursor (if any), pos is in internal coordinates
WordListPosP findWordListBody(const RealPoint& pos) const;
/// Find a WordListPos for a index position /// Find a WordListPos for a index position
WordListPosP findWordList(size_t index) const; WordListPosP findWordList(size_t index) const;
/// Show a word list drop down menu, if wl /// Show a word list drop down menu, if wl
......
...@@ -54,6 +54,12 @@ SCRIPT_FUNCTION(reverse) { ...@@ -54,6 +54,12 @@ SCRIPT_FUNCTION(reverse) {
SCRIPT_RETURN(input); SCRIPT_RETURN(input);
} }
// remove leading and trailing whitespace from a string
SCRIPT_FUNCTION(trim) {
SCRIPT_PARAM(String, input);
SCRIPT_RETURN(trim(input));
}
// extract a substring // extract a substring
SCRIPT_FUNCTION(substring) { SCRIPT_FUNCTION(substring) {
SCRIPT_PARAM(String, input); SCRIPT_PARAM(String, input);
...@@ -156,6 +162,11 @@ SCRIPT_RULE_1(tag_remove, String, tag) { ...@@ -156,6 +162,11 @@ SCRIPT_RULE_1(tag_remove, String, tag) {
SCRIPT_RETURN(remove_tag(input, tag)); SCRIPT_RETURN(remove_tag(input, tag));
} }
SCRIPT_FUNCTION(remove_tags) {
SCRIPT_PARAM(String, input);
SCRIPT_RETURN(untag_no_escape(input));
}
// ----------------------------------------------------------------------------- : Collection stuff // ----------------------------------------------------------------------------- : Collection stuff
/// compare script values for equallity /// compare script values for equallity
...@@ -593,6 +604,7 @@ void init_script_basic_functions(Context& ctx) { ...@@ -593,6 +604,7 @@ void init_script_basic_functions(Context& ctx) {
ctx.setVariable(_("to lower"), script_to_lower); ctx.setVariable(_("to lower"), script_to_lower);
ctx.setVariable(_("to title"), script_to_title); ctx.setVariable(_("to title"), script_to_title);
ctx.setVariable(_("reverse"), script_reverse); ctx.setVariable(_("reverse"), script_reverse);
ctx.setVariable(_("trim"), script_trim);
ctx.setVariable(_("substring"), script_substring); ctx.setVariable(_("substring"), script_substring);
ctx.setVariable(_("contains"), script_contains); ctx.setVariable(_("contains"), script_contains);
ctx.setVariable(_("format"), script_format); ctx.setVariable(_("format"), script_format);
...@@ -602,6 +614,7 @@ void init_script_basic_functions(Context& ctx) { ...@@ -602,6 +614,7 @@ void init_script_basic_functions(Context& ctx) {
// tagged string // tagged string
ctx.setVariable(_("tag contents"), script_tag_contents); ctx.setVariable(_("tag contents"), script_tag_contents);
ctx.setVariable(_("remove tag"), script_tag_remove); ctx.setVariable(_("remove tag"), script_tag_remove);
ctx.setVariable(_("remove tags"), script_remove_tags);
ctx.setVariable(_("tag contents rule"), script_tag_contents_rule); ctx.setVariable(_("tag contents rule"), script_tag_contents_rule);
ctx.setVariable(_("tag remove rule"), script_tag_remove_rule); ctx.setVariable(_("tag remove rule"), script_tag_remove_rule);
// collection // collection
......
...@@ -327,22 +327,22 @@ enum Precedence ...@@ -327,22 +327,22 @@ enum Precedence
}; };
/// Parse an expression /// Parse an expression
/** @param input Read tokens from the input /** @param input Read tokens from the input
* @param scrip Add resulting instructions to the script * @param scrip Add resulting instructions to the script
* @param minPrec Minimum precedence level for operators * @param min_prec Minimum precedence level for operators
* NOTE: The net stack effect of an expression should be +1 * NOTE: The net stack effect of an expression should be +1
*/ */
void parseExpr(TokenIterator& input, Script& script, Precedence minPrec); void parseExpr(TokenIterator& input, Script& script, Precedence min_prec);
/// Parse an expression, possibly with operators applied. Optionally adds an instruction at the end. /// Parse an expression, possibly with operators applied. Optionally adds an instruction at the end.
/** @param input Read tokens from the input /** @param input Read tokens from the input
* @param script Add resulting instructions to the script * @param script Add resulting instructions to the script
* @param minPrec Minimum precedence level for operators * @param min_prec Minimum precedence level for operators
* @param closeWith Add this instruction at the end * @param close_with Add this instruction at the end
* @param closeWithData Data for the instruction at the end * @param close_with_data Data for the instruction at the end
* NOTE: The net stack effect of an expression should be +1 * NOTE: The net stack effect of an expression should be +1
*/ */
void parseOper(TokenIterator& input, Script& script, Precedence minPrec, InstructionType closeWith = I_NOP, int closeWithData = 0); void parseOper(TokenIterator& input, Script& script, Precedence min_prec, InstructionType close_with = I_NOP, int close_with_data = 0);
ScriptP parse(const String& s, bool string_mode, vector<ScriptParseError>& errors_out) { ScriptP parse(const String& s, bool string_mode, vector<ScriptParseError>& errors_out) {
...@@ -542,7 +542,9 @@ void parseExpr(TokenIterator& input, Script& script, Precedence minPrec) { ...@@ -542,7 +542,9 @@ void parseExpr(TokenIterator& input, Script& script, Precedence minPrec) {
} }
void parseOper(TokenIterator& input, Script& script, Precedence minPrec, InstructionType closeWith, int closeWithData) { void parseOper(TokenIterator& input, Script& script, Precedence minPrec, InstructionType closeWith, int closeWithData) {
size_t added = script.getInstructions().size(); // number of instructions added
parseExpr(input, script, minPrec); // first argument parseExpr(input, script, minPrec); // first argument
added -= script.getInstructions().size();
// read any operators after an expression // read any operators after an expression
// EBNF: expr = expr | expr oper expr // EBNF: expr = expr | expr oper expr
// without left recursion: expr = expr (oper expr)* // without left recursion: expr = expr (oper expr)*
...@@ -565,10 +567,8 @@ void parseOper(TokenIterator& input, Script& script, Precedence minPrec, Instruc ...@@ -565,10 +567,8 @@ void parseOper(TokenIterator& input, Script& script, Precedence minPrec, Instruc
} else if (minPrec <= PREC_SET && token==_(":=")) { } else if (minPrec <= PREC_SET && token==_(":=")) {
// We made a mistake, the part before the := should be a variable name, // We made a mistake, the part before the := should be a variable name,
// not an expression. Remove that instruction. // not an expression. Remove that instruction.
// TODO: There is a bug here: Instruction& instr = script.getInstructions().back();
// (if x then a else b) := c will use the 'b' as variable name if (added == 1 && instr.instr != I_GET_VAR) {
Instruction instr = script.getInstructions().back();
if (instr.instr != I_GET_VAR) {
input.add_error(_("Can only assign to variables")); input.add_error(_("Can only assign to variables"));
} }
script.getInstructions().pop_back(); script.getInstructions().pop_back();
...@@ -610,7 +610,18 @@ void parseOper(TokenIterator& input, Script& script, Precedence minPrec, Instruc ...@@ -610,7 +610,18 @@ void parseOper(TokenIterator& input, Script& script, Precedence minPrec, Instruc
input.expected(_("name")); input.expected(_("name"));
} }
} else if (minPrec <= PREC_FUN && token==_("[")) { // get member by expr } else if (minPrec <= PREC_FUN && token==_("[")) { // get member by expr
parseOper(input, script, PREC_ALL, I_BINARY, I_MEMBER); size_t before = script.getInstructions().size();
parseOper(input, script, PREC_ALL);
if (script.getInstructions().size() == before + 1 && script.getInstructions().back().instr == I_PUSH_CONST) {
// optimize:
// PUSH_CONST x
// MEMBER
// becomes
// MEMBER_CONST x
script.getInstructions().back().instr = I_MEMBER_C;
} else {
script.addInstruction(I_BINARY, I_MEMBER);
}
expectToken(input, _("]")); expectToken(input, _("]"));
} else if (minPrec <= PREC_FUN && token==_("(")) { } else if (minPrec <= PREC_FUN && token==_("(")) {
// function call, read arguments // function call, read arguments
......
...@@ -30,6 +30,7 @@ void ActionStack::add(Action* action, bool allow_merge) { ...@@ -30,6 +30,7 @@ void ActionStack::add(Action* action, bool allow_merge) {
action->perform(false); // TODO: delete action if perform throws action->perform(false); // TODO: delete action if perform throws
tellListeners(*action, false); tellListeners(*action, false);
// clear redo list // clear redo list
if (!redo_actions.empty()) allow_merge = false; // don't merge after undo
FOR_EACH(a, redo_actions) delete a; FOR_EACH(a, redo_actions) delete a;
redo_actions.clear(); redo_actions.clear();
// try to merge? // try to merge?
......
...@@ -286,3 +286,15 @@ void RotatedDC::SetClippingRegion(const RealRect& rect) { ...@@ -286,3 +286,15 @@ void RotatedDC::SetClippingRegion(const RealRect& rect) {
void RotatedDC::DestroyClippingRegion() { void RotatedDC::DestroyClippingRegion() {
dc.DestroyClippingRegion(); dc.DestroyClippingRegion();
} }
// ----------------------------------------------------------------------------- : Other
Bitmap RotatedDC::GetBackground(const RealRect& r) {
wxRect wr = trNoNeg(r);
Bitmap background(wr.width, wr.height);
wxMemoryDC mdc;
mdc.SelectObject(background);
mdc.Blit(0, 0, wr.width, wr.height, &dc, wr.x, wr.y);
mdc.SelectObject(wxNullBitmap);
return background;
}
...@@ -172,7 +172,7 @@ class RotatedDC : public Rotation { ...@@ -172,7 +172,7 @@ class RotatedDC : public Rotation {
// Fill the dc with the color of the current brush // Fill the dc with the color of the current brush
void Fill(); void Fill();
// --------------------------------------------------- : Forwarded properties // --------------------------------------------------- : Properties
/// Sets the pen for the dc, does not scale the line width /// Sets the pen for the dc, does not scale the line width
void SetPen(const wxPen&); void SetPen(const wxPen&);
...@@ -193,6 +193,11 @@ class RotatedDC : public Rotation { ...@@ -193,6 +193,11 @@ class RotatedDC : public Rotation {
void SetClippingRegion(const RealRect& rect); void SetClippingRegion(const RealRect& rect);
void DestroyClippingRegion(); void DestroyClippingRegion();
// --------------------------------------------------- : Other
/// Get the current contents of the given ractangle, for later restoring
Bitmap GetBackground(const RealRect& r);
inline wxDC& getDC() { return dc; } inline wxDC& getDC() { return dc; }
private: private:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment