Commit e023ad98 authored by twanvl's avatar twanvl

more script functions and text editor improvements

parent be2222ca
......@@ -12,7 +12,8 @@
#include <data/card.hpp>
#include <data/keyword.hpp>
#include <data/field.hpp>
#include <data/field/text.hpp> // for 0.2.7 fix
#include <data/field/text.hpp> // for 0.2.7 fix
#include <util/tagged_string.hpp> // for 0.2.7 fix
#include <script/value.hpp>
#include <script/script_manager.hpp>
#include <wx/sstream.h>
......@@ -62,9 +63,9 @@ String Set::typeName() const { return _("set"); }
void fix_value_207(const ValueP& value) {
if (TextValue* v = dynamic_cast<TextValue*>(value.get())) {
// text value -> fix it
// v->value.assign( // don't change defaultness
// fix_old_tags(v->value); // remove tags
// );
v->value.assignDontChangeDefault( // don't change defaultness
fix_old_tags(v->value()) // remove tags
);
}
}
......
......@@ -213,6 +213,11 @@ void TextValueEditor::onMenu(wxCommandEvent& ev) {
// ----------------------------------------------------------------------------- : Other overrides
void TextValueEditor::draw(RotatedDC& dc) {
TextValueViewer::draw(dc);
v.drawSelection(dc, style(), selection_start, selection_end);
}
wxCursor rotated_ibeam;
wxCursor TextValueEditor::cursor() const {
......@@ -237,6 +242,8 @@ void TextValueEditor::onAction(const ValueAction& action, bool undone) {
TYPE_CASE(action, TextValueAction) {
selection_start = action.selection_start;
selection_end = action.selection_end;
fixSelection();
if (isCurrent()) showCaret();
}
}
......@@ -471,7 +478,7 @@ void TextValueEditor::moveSelection(size_t new_end, bool also_move_start, Moveme
void TextValueEditor::moveSelectionNoRedraw(size_t new_end, bool also_move_start, Movement dir) {
selection_end = new_end;
if (also_move_start) selection_start = selection_end;
if (also_move_start) selection_start = selection_end;
fixSelection(dir);
}
......@@ -479,7 +486,7 @@ void TextValueEditor::fixSelection(Movement dir) {
const String& val = value().value();
// value may have become smaller because of undo/redo
// make sure the selection stays inside the text
size_t size;
size_t size = val.size();
selection_end = min(size, selection_end);
selection_start = min(size, selection_start);
// start and end must be on the same side of separators
......@@ -513,6 +520,8 @@ void TextValueEditor::fixSelection(Movement dir) {
atompos = val.find(_("<atom"), atompos + 1);
}
// start and end must not be inside or between tags
selection_start = v.firstVisibleChar(selection_start, dir == MOVE_LEFT ? -1 : +1);
selection_end = v.firstVisibleChar(selection_end, dir == MOVE_LEFT ? -1 : +1);
// TODO
}
......@@ -521,7 +530,7 @@ size_t TextValueEditor::prevCharBoundry(size_t pos) const {
return max(0, (int)pos - 1);
}
size_t TextValueEditor::nextCharBoundry(size_t pos) const {
return max(value().value().size(), pos + 1);
return min(value().value().size(), pos + 1);
}
size_t TextValueEditor::prevWordBoundry(size_t pos) const {
const String& val = value().value();
......
......@@ -79,6 +79,7 @@ class TextValueEditor : public TextValueViewer, public ValueEditor {
virtual wxCursor cursor() const;
virtual void determineSize();
virtual void onShow(bool);
virtual void draw(RotatedDC&);
// --------------------------------------------------- : Data
private:
......
......@@ -82,7 +82,7 @@ void TextViewer::draw(RotatedDC& dc, const String& text, const TextStyle& style,
void TextViewer::drawSelection(RotatedDC& dc, const TextStyle& style, size_t sel_start, size_t sel_end) {
Rotater r(dc, style.getRotation());
if (sel_start == sel_end) return;
if (sel_end < sel_start) swap(sel_start, sel_end);
if (sel_end < sel_start) swap(sel_start, sel_end);
dc.SetBrush(*wxBLACK_BRUSH);
dc.SetPen(*wxTRANSPARENT_PEN);
dc.SetLogicalFunction(wxINVERT);
......@@ -96,7 +96,7 @@ void TextViewer::Line::drawSelection(RotatedDC& dc, size_t sel_start, size_t sel
if (!visible(dc)) return;
if (sel_start < end() && sel_end > start) {
double x1 = positions[sel_start];
double x2 = positions[max(end(), sel_end)];
double x2 = positions[min(end(), sel_end)];
dc.DrawRectangle(RealRect(x1, top, x2 - x1, line_height));
}
}
......@@ -116,6 +116,7 @@ const TextViewer::Line& TextViewer::findLine(size_t index) const {
}
size_t TextViewer::moveLine(size_t index, int delta) const {
if (lines.empty()) return index;
const Line* line1 = &findLine(index);
const Line* line2 = line1 + delta;
if (line2 >= &lines.front() && line2 <= &lines.back()) {
......@@ -151,6 +152,7 @@ size_t TextViewer::indexAt(const RealPoint& pos) const {
}
RealRect TextViewer::charRect(size_t index) const {
if (lines.empty()) return RealRect(0,0,0,0);
const Line& l = findLine(index);
size_t pos = index - l.start;
if (pos >= l.positions.size()) {
......@@ -160,9 +162,32 @@ RealRect TextViewer::charRect(size_t index) const {
}
}
bool TextViewer::isVisible(size_t index) const {
if (lines.empty()) return false;
const Line& l = findLine(index);
size_t pos = index - l.start;
if (pos >= l.positions.size()) {
return false;
} else if (pos + 1 == l.positions.size()) {
return true; // last char of the line
} else {
return l.positions[pos + 1] - l.positions[pos] > 0.0001;
}
}
size_t TextViewer::firstVisibleChar(size_t index, int delta) const {
if (lines.empty()) return index;
const Line& l = findLine(index);
int pos = (int)(index - l.start);
while (pos + delta > 0 && (size_t)pos + delta + 1 < l.positions.size()) {
if (l.positions[pos + 1] - l.positions[pos] > 0.0001) break;
pos += delta;
}
return pos + l.start;
}
double TextViewer::heightOfLastLine() const {
if (lines.empty()) return 0;
else return lines.back().line_height;
return lines.back().line_height;
}
// ----------------------------------------------------------------------------- : Elements
......
......@@ -56,8 +56,6 @@ class TextViewer {
// --------------------------------------------------- : Positions
/// Find the character index that is before/after the given index, and which has a nonzero width
// size_t moveChar(size_t index, int delta) const;
/// Find the character index that is on a line above/below index
/** If this would move outisde the text, returns the input index */
size_t moveLine(size_t index, int delta) const;
......@@ -78,6 +76,10 @@ class TextViewer {
/// Return the rectangle around a single character
RealRect charRect(size_t index) const;
/// Is the character at the given index visible?
bool isVisible(size_t index) const;
/// Find the first character index that is at/before/after the given index, and which has a nonzero width
size_t firstVisibleChar(size_t index, int delta) const;
/// Return the height of the last line
double heightOfLastLine() const;
......
......@@ -8,6 +8,7 @@
#include <script/value.hpp>
#include <script/context.hpp>
#include <util/tagged_string.hpp>
#include <wx/regex.h>
DECLARE_TYPEOF_COLLECTION(UInt);
......@@ -56,6 +57,7 @@ class ScriptReplaceRule : public ScriptValue {
ret += inside;
input = next_input;
}
ret += input;
SCRIPT_RETURN(ret);
} else {
// dumb replacing
......@@ -79,7 +81,7 @@ SCRIPT_FUNCTION(replace_rule) {
throw ScriptError(_("Error while compiling regular expression: '")+match+_("'"));
}
// replace
ScriptValueP replace = ctx.getVariable(_("replace"));
SCRIPT_PARAM(ScriptValueP, replace);
if (replace->type() == SCRIPT_FUNCTION) {
ret->replacement_function = replace;
} else {
......@@ -234,25 +236,57 @@ String spec_sort(const String& spec, const String& input) {
}
class ScriptSortRule : public ScriptValue {
public:
ScriptSortRule(const String& order) : order(order) {}
virtual ScriptType type() const { return SCRIPT_FUNCTION; }
virtual String typeName() const { return _("sort_rule"); }
virtual ScriptValueP eval(Context& ctx) const {
SCRIPT_PARAM(String, input);
SCRIPT_RETURN(spec_sort(order, input));
}
private:
String order;
};
// Utility for defining a script rule with a single parameter
#define SCRIPT_RULE_1(funname, type1, name1) \
class ScriptRule_##funname: public ScriptValue { \
public: \
inline ScriptRule_##funname(const type1& name1) : name1(name1) {} \
virtual ScriptType type() const { return SCRIPT_FUNCTION; } \
virtual String typeName() const { return _(#funname)_("_rule"); } \
virtual ScriptValueP eval(Context& ctx) const; \
private: \
type1 name1; \
}; \
SCRIPT_FUNCTION(funname##_rule) { \
SCRIPT_PARAM(type1, name1); \
return new_intrusive1<ScriptRule_##funname>(name1); \
} \
SCRIPT_FUNCTION(funname) { \
SCRIPT_PARAM(type1, name1); \
return ScriptRule_##funname(name1).eval(ctx); \
} \
ScriptValueP ScriptRule_##funname::eval(Context& ctx) const
// Utility for defining a script rule with two parameters
#define SCRIPT_RULE_2(funname, type1, name1, type2, name2) \
class ScriptRule_##funname: public ScriptValue { \
public: \
inline ScriptRule_##funname(const type1& name1, const type2& name2) \
: name1(name1), name2(name2) {} \
virtual ScriptType type() const { return SCRIPT_FUNCTION; } \
virtual String typeName() const { return _(#funname)_("_rule"); } \
virtual ScriptValueP eval(Context& ctx) const; \
private: \
type1 name1; \
type2 name2; \
}; \
SCRIPT_FUNCTION(funname##_rule) { \
SCRIPT_PARAM(type1, name1); \
SCRIPT_PARAM(type2, name2); \
return new_intrusive2<ScriptRule_##funname>(name1, name2); \
} \
SCRIPT_FUNCTION(funname) { \
SCRIPT_PARAM(type1, name1); \
SCRIPT_PARAM(type2, name2); \
return ScriptRule_##funname(name1, name2).eval(ctx); \
} \
ScriptValueP ScriptRule_##funname::eval(Context& ctx) const
// Create a rule for spec_sorting strings
SCRIPT_FUNCTION(sort_rule) {
SCRIPT_PARAM(String, order);
return new_intrusive1<ScriptSortRule>(order);
SCRIPT_RULE_1(sort, String, order) {
SCRIPT_PARAM(String, input);
SCRIPT_RETURN(spec_sort(order, input));
}
// ----------------------------------------------------------------------------- : String stuff
......@@ -298,16 +332,55 @@ SCRIPT_FUNCTION(contains) {
SCRIPT_RETURN(input.find(match) != String::npos);
}
// ----------------------------------------------------------------------------- : Tagged stuff
String replace_tag_contents(String input, const String& tag, const ScriptValueP& contents, Context& ctx) {
String ret;
size_t pos = input.find(tag);
while (pos != String::npos) {
// find end of tag and contents
size_t end = match_close_tag(input, pos);
if (end == String::npos) break; // missing close tag
// prepare for call
String old_contents = input.substr(pos + tag.size(), end - (pos + tag.size()));
ctx.setVariable(_("contents"), toScript(old_contents));
// replace
ret += input.substr(0, pos); // before tag
ret += tag;
ret += *contents->eval(ctx);// new contents (call)
ret += close_tag(tag);
// next
input = input.substr(skip_tag(input,end));
pos = input.find(tag);
}
return ret + input;
}
// Replace the contents of a specific tag
SCRIPT_RULE_2(tag_contents, String, tag, ScriptValueP, contents) {
SCRIPT_PARAM(String, input);
SCRIPT_RETURN(replace_tag_contents(input, tag, contents, ctx));
}
SCRIPT_RULE_1(tag_remove, String, tag) {
SCRIPT_PARAM(String, input);
SCRIPT_RETURN(remove_tag(input, tag));
}
// ----------------------------------------------------------------------------- : Vector stuff
/// position of some element in a vector
/** 1 based index, 0 if not found */
/** 0 based index, -1 if not found */
int position_in_vector(const ScriptValueP& of, const ScriptValueP& in, const ScriptValueP& order_by) {
return 0;// TODO
ScriptType of_t = of->type(), in_t = in->type();
if (of_t == SCRIPT_STRING || in_t == SCRIPT_STRING) {
return (int)((String)*of).find(*in); // (int)npos == -1
}
return -1; // TODO
}
// finding positions
// finding positions, also of substrings
SCRIPT_FUNCTION(position_of) {
ScriptValueP of = ctx.getVariable(_("of"));
ScriptValueP in = ctx.getVariable(_("in"));
......@@ -323,15 +396,20 @@ SCRIPT_FUNCTION(number_of_items) {
// ----------------------------------------------------------------------------- : Initialize functions
void init_script_functions(Context& ctx) {
ctx.setVariable(_("replace rule"), script_replace_rule);
ctx.setVariable(_("filter rule"), script_filter_rule);
ctx.setVariable(_("sort rule"), script_sort_rule);
ctx.setVariable(_("to upper"), script_to_upper);
ctx.setVariable(_("to lower"), script_to_lower);
ctx.setVariable(_("to title"), script_to_title);
ctx.setVariable(_("substring"), script_substring);
ctx.setVariable(_("contains"), script_contains);
ctx.setVariable(_("position"), script_position_of);
ctx.setVariable(_("number of items"), script_number_of_items);
ctx.setVariable(_("replace rule"), script_replace_rule);
ctx.setVariable(_("filter rule"), script_filter_rule);
ctx.setVariable(_("sort"), script_sort);
ctx.setVariable(_("sort rule"), script_sort_rule);
ctx.setVariable(_("to upper"), script_to_upper);
ctx.setVariable(_("to lower"), script_to_lower);
ctx.setVariable(_("to title"), script_to_title);
ctx.setVariable(_("substring"), script_substring);
ctx.setVariable(_("contains"), script_contains);
ctx.setVariable(_("tag contents"), script_tag_contents);
ctx.setVariable(_("remove tag"), script_tag_remove);
ctx.setVariable(_("tag contents rule"), script_tag_contents_rule);
ctx.setVariable(_("tag remove rule"), script_tag_remove_rule);
ctx.setVariable(_("position"), script_position_of);
ctx.setVariable(_("number of items"), script_number_of_items);
}
......@@ -329,7 +329,12 @@ inline ScriptValueP toScript(const shared_ptr<T>& v) { return new_intrusive1<Scr
* Throws an error if the parameter is not found.
*/
#define SCRIPT_PARAM(Type, name) \
Type name = *ctx.getVariable(_(#name))
Type name = getParam<Type>(ctx.getVariable(_(#name)))
template <typename T>
inline T getParam (const ScriptValueP& value) { return *value; }
template <>
inline ScriptValueP getParam<ScriptValueP>(const ScriptValueP& value) { return value; }
/// Retrieve an optional parameter
/** Usage:
......
......@@ -33,6 +33,10 @@ class Defaultable {
assert(is_default);
value = new_value;
}
/// Assigning a value, don't change the defaultness
inline void assignDontChangeDefault(const T& new_value) {
value = new_value;
}
/// Get access to the value
inline const T& operator () () const { return value; }
......
......@@ -82,6 +82,14 @@ String fix_old_tags(const String& str) {
// ----------------------------------------------------------------------------- : Finding tags
size_t tag_start(const String& str, size_t pos) {
size_t start = str.find_last_of(_('<'), pos);
if (start == String::npos) return String::npos;
size_t end = skip_tag(str, start);
if (end <= pos) return String::npos;
return start;
}
size_t skip_tag(const String& str, size_t start) {
if (start >= str.size()) return String::npos;
size_t end = str.find_first_of(_('>'), start);
......@@ -152,6 +160,43 @@ String anti_tag(const String& tag) {
}
// ----------------------------------------------------------------------------- : Global operations
String remove_tag(const String& str, const String& tag) {
if (tag.size() < 1) return str;
String ctag = close_tag(tag);
return remove_tag_exact(remove_tag_exact(str, tag), ctag);
}
String remove_tag_exact(const String& str, const String& tag) {
String ret; ret.reserve(str.size());
size_t start = 0, pos = str.find(tag);
while (pos != String::npos) {
ret += str.substr(start, pos - start); // before
// next
start = skip_tag(str, pos);
if (start > str.size()) break;
pos = str.find(tag, start);
}
ret += str.substr(start);
return ret;
}
String remove_tag_contents(const String& str, const String& tag) {
String ret; ret.reserve(str.size());
size_t start = 0, pos = str.find(tag);
while (pos != String::npos) {
size_t end = match_close_tag(str, pos);
if (end == String::npos) return ret; // missing close tag
ret += str.substr(start, pos - start);
// next
start = skip_tag(str, end);
if (start > str.size()) break;
pos = str.find(tag, start);
}
ret += str.substr(start);
return ret;
}
// ----------------------------------------------------------------------------- : Updates
/// Return all open or close tags in the given range from a string
......
......@@ -42,6 +42,14 @@ String fix_old_tags(const String&);
// ----------------------------------------------------------------------------- : Finding tags
/// Returns the position of the tag the given position is in
/** Returns String::npos if pos is not in a tag.
* In a tag is:
* < t a g >
* n y y y y n
*/
size_t tag_start(const String& str, size_t pos);
/// Returns the position just beyond the tag starting at start
size_t skip_tag(const String& str, size_t start);
......@@ -53,8 +61,10 @@ size_t match_close_tag(const String& str, size_t start);
/** If not found returns String::npos */
size_t last_start_tag_before(const String& str, const String& tag, size_t start);
/// Is the given range entirely contained in a given tag?
/** If so: return the start position of that tag, otherwise returns String::npos */
/// Is the given range entirely contained in a given tagged block?
/** If so: return the start position of that tag, otherwise returns String::npos
* A tagged block is everything between <tag>...</tag>
*/
size_t in_tag(const String& str, const String& tag, size_t start, size_t end);
/// Return the tag at the given position (without the <>)
......@@ -78,7 +88,7 @@ String anti_tag(const String& tag);
*/
String remove_tag(const String& str, const String& tag);
/// Remove all instances of tags starting with tag
/// Remove all instances of tags starting with tag, but not its close tag
String remove_tag_exact(const String& str, const String& tag);
/// Remove all instances of a tag (including contents) from a string
......
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