Commit 95c154d6 authored by twanvl's avatar twanvl

Reader now warns about invalid UTF-8 files;

Fixed possible hang when reading multiline strings with incorrect indentation;
Warnings from reading are shown also in NewSetWindow;
Script parse errors get reported with the correct line number;

Added support for showing multiple choice fields as a single image;
Added 'line_below' to ChoiceField::Choice, for putting a line below menu items.
parent f6a4b4c5
...@@ -49,9 +49,11 @@ IMPLEMENT_REFLECTION(ChoiceField) { ...@@ -49,9 +49,11 @@ IMPLEMENT_REFLECTION(ChoiceField) {
ChoiceField::Choice::Choice() ChoiceField::Choice::Choice()
: first_id(0) : first_id(0)
, line_below(false), enabled(true)
{} {}
ChoiceField::Choice::Choice(const String& name) ChoiceField::Choice::Choice(const String& name)
: name(name), first_id(0) : name(name), first_id(0)
, line_below(false), enabled(true)
{} {}
...@@ -138,11 +140,13 @@ String ChoiceField::Choice::choiceNameNice(int id) const { ...@@ -138,11 +140,13 @@ String ChoiceField::Choice::choiceNameNice(int id) const {
IMPLEMENT_REFLECTION_NO_GET_MEMBER(ChoiceField::Choice) { IMPLEMENT_REFLECTION_NO_GET_MEMBER(ChoiceField::Choice) {
if (isGroup() || (tag.reading() && tag.isComplex())) { if (isGroup() || line_below || enabled.isScripted() || (tag.reading() && tag.isComplex())) {
// complex values are groups // complex values are groups
REFLECT(name); REFLECT(name);
REFLECT_N("group_choice", default_name); REFLECT_N("group_choice", default_name);
REFLECT(choices); REFLECT(choices);
REFLECT(line_below);
REFLECT(enabled);
} else { } else {
REFLECT_NAMELESS(name); REFLECT_NAMELESS(name);
} }
...@@ -237,6 +241,9 @@ IMPLEMENT_REFLECTION_ENUM(ChoiceRenderStyle) { ...@@ -237,6 +241,9 @@ IMPLEMENT_REFLECTION_ENUM(ChoiceRenderStyle) {
VALUE_N("checklist", RENDER_TEXT_CHECKLIST); VALUE_N("checklist", RENDER_TEXT_CHECKLIST);
VALUE_N("image checklist", RENDER_IMAGE_CHECKLIST); VALUE_N("image checklist", RENDER_IMAGE_CHECKLIST);
VALUE_N("both checklist", RENDER_BOTH_CHECKLIST); VALUE_N("both checklist", RENDER_BOTH_CHECKLIST);
VALUE_N("text list", RENDER_IMAGE_LIST);
VALUE_N("image list", RENDER_IMAGE_LIST);
VALUE_N("both list", RENDER_IMAGE_LIST);
} }
IMPLEMENT_REFLECTION(ChoiceStyle) { IMPLEMENT_REFLECTION(ChoiceStyle) {
......
...@@ -55,6 +55,8 @@ class ChoiceField::Choice : public IntrusivePtrBase<ChoiceField::Choice> { ...@@ -55,6 +55,8 @@ class ChoiceField::Choice : public IntrusivePtrBase<ChoiceField::Choice> {
String name; ///< Name/value of the item String name; ///< Name/value of the item
String default_name; ///< A default item, if this is a group and default_name.empty() there is no default String default_name; ///< A default item, if this is a group and default_name.empty() there is no default
vector<ChoiceP> choices; ///< Choices and sub groups in this group vector<ChoiceP> choices; ///< Choices and sub groups in this group
bool line_below; ///< Show a line after this item?
Scriptable<bool> enabled; ///< Is this item enabled?
/// First item-id in this group (can be the default item) /// First item-id in this group (can be the default item)
/** Item-ids are consecutive integers, a group uses all ids [first_id..lastId()). /** Item-ids are consecutive integers, a group uses all ids [first_id..lastId()).
* The top level group has first_id 0. * The top level group has first_id 0.
...@@ -109,11 +111,15 @@ enum ChoiceRenderStyle ...@@ -109,11 +111,15 @@ enum ChoiceRenderStyle
, RENDER_IMAGE = 0x10 // render an image , RENDER_IMAGE = 0x10 // render an image
, RENDER_HIDDEN = 0x20 // don't render anything, only have a menu , RENDER_HIDDEN = 0x20 // don't render anything, only have a menu
, RENDER_CHECKLIST = 0x100 // render as a checklist, intended for multiple choice , RENDER_CHECKLIST = 0x100 // render as a checklist, intended for multiple choice
, RENDER_LIST = 0x200 // render as a list of images/text, intended for multiple choice
, RENDER_BOTH = RENDER_TEXT | RENDER_IMAGE , RENDER_BOTH = RENDER_TEXT | RENDER_IMAGE
, RENDER_HIDDEN_IMAGE = RENDER_HIDDEN | RENDER_IMAGE , RENDER_HIDDEN_IMAGE = RENDER_HIDDEN | RENDER_IMAGE
, RENDER_TEXT_CHECKLIST = RENDER_CHECKLIST | RENDER_TEXT , RENDER_TEXT_CHECKLIST = RENDER_CHECKLIST | RENDER_TEXT
, RENDER_IMAGE_CHECKLIST = RENDER_CHECKLIST | RENDER_IMAGE , RENDER_IMAGE_CHECKLIST = RENDER_CHECKLIST | RENDER_IMAGE
, RENDER_BOTH_CHECKLIST = RENDER_CHECKLIST | RENDER_BOTH , RENDER_BOTH_CHECKLIST = RENDER_CHECKLIST | RENDER_BOTH
, RENDER_TEXT_LIST = RENDER_LIST | RENDER_TEXT
, RENDER_IMAGE_LIST = RENDER_LIST | RENDER_IMAGE
, RENDER_BOTH_LIST = RENDER_LIST | RENDER_BOTH
}; };
enum ThumbnailStatus enum ThumbnailStatus
......
...@@ -25,6 +25,7 @@ IMPLEMENT_REFLECTION(MultipleChoiceField) { ...@@ -25,6 +25,7 @@ IMPLEMENT_REFLECTION(MultipleChoiceField) {
REFLECT_BASE(ChoiceField); REFLECT_BASE(ChoiceField);
REFLECT(minimum_selection); REFLECT(minimum_selection);
REFLECT(maximum_selection); REFLECT(maximum_selection);
REFLECT(empty_choice);
} }
// ----------------------------------------------------------------------------- : MultipleChoiceStyle // ----------------------------------------------------------------------------- : MultipleChoiceStyle
......
...@@ -25,6 +25,7 @@ class MultipleChoiceField : public ChoiceField { ...@@ -25,6 +25,7 @@ class MultipleChoiceField : public ChoiceField {
DECLARE_FIELD_TYPE(MultipleChoiceField); DECLARE_FIELD_TYPE(MultipleChoiceField);
UInt minimum_selection, maximum_selection; ///< How many choices can be selected simultaniously? UInt minimum_selection, maximum_selection; ///< How many choices can be selected simultaniously?
String empty_choice; ///< Name to use when nothing is selected
private: private:
DECLARE_REFLECTION(); DECLARE_REFLECTION();
...@@ -56,7 +57,7 @@ class MultipleChoiceValue : public ChoiceValue { ...@@ -56,7 +57,7 @@ class MultipleChoiceValue : public ChoiceValue {
inline MultipleChoiceValue(const MultipleChoiceFieldP& field) : ChoiceValue(field, true) {} inline MultipleChoiceValue(const MultipleChoiceFieldP& field) : ChoiceValue(field, true) {}
DECLARE_HAS_FIELD(MultipleChoice); DECLARE_HAS_FIELD(MultipleChoice);
// no extra data String last_choice; ///< Which of the choices was selected/deselected last?
/// Splits the value, stores the selected choices in the out parameter /// Splits the value, stores the selected choices in the out parameter
void get(vector<String>& out) const; void get(vector<String>& out) const;
...@@ -65,6 +66,10 @@ class MultipleChoiceValue : public ChoiceValue { ...@@ -65,6 +66,10 @@ class MultipleChoiceValue : public ChoiceValue {
DECLARE_REFLECTION(); DECLARE_REFLECTION();
}; };
// ----------------------------------------------------------------------------- : Utilities
/// Is the given choice selected in the value?
bool chosen(const String& multiple_choice_value, const String& chioce);
// ----------------------------------------------------------------------------- : EOF // ----------------------------------------------------------------------------- : EOF
#endif #endif
...@@ -84,6 +84,7 @@ DropDownList::~DropDownList() { ...@@ -84,6 +84,7 @@ DropDownList::~DropDownList() {
void DropDownList::show(bool in_place, wxPoint pos) { void DropDownList::show(bool in_place, wxPoint pos) {
if (IsShown()) return; if (IsShown()) return;
onShow();
// find selection // find selection
selected_item = selection(); selected_item = selection();
// width // width
...@@ -248,6 +249,9 @@ void DropDownList::drawItem(DC& dc, int y, size_t item) { ...@@ -248,6 +249,9 @@ void DropDownList::drawItem(DC& dc, int y, size_t item) {
dc.SetBrush (wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT)); dc.SetBrush (wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT));
dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT)); dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT));
dc.DrawRectangle(marginW, y, (int)item_size.width, (int)item_size.height); dc.DrawRectangle(marginW, y, (int)item_size.width, (int)item_size.height);
} else if (!itemEnabled(item)) {
// mix between foreground and background
dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT));
} else if (highlightItem(item)) { } else if (highlightItem(item)) {
// mix a color between selection and window // mix a color between selection and window
dc.SetBrush (lerp(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT), dc.SetBrush (lerp(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT),
...@@ -266,7 +270,7 @@ void DropDownList::drawItem(DC& dc, int y, size_t item) { ...@@ -266,7 +270,7 @@ void DropDownList::drawItem(DC& dc, int y, size_t item) {
} }
// draw line below // draw line below
if (lineBelow(item)) { if (lineBelow(item)) {
dc.SetPen(wxSystemSettings::GetColour(wxSYS_COLOUR_3DSHADOW)); 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);
} }
} }
...@@ -295,8 +299,10 @@ void DropDownList::onMotion(wxMouseEvent& ev) { ...@@ -295,8 +299,10 @@ void DropDownList::onMotion(wxMouseEvent& ev) {
for (size_t i = 0 ; i < count ; ++i) { for (size_t i = 0 ; i < count ; ++i) {
int endY = startY + (int)item_size.height; int endY = startY + (int)item_size.height;
if (ev.GetY() >= startY && ev.GetY() < endY) { if (ev.GetY() >= startY && ev.GetY() < endY) {
if (itemEnabled(i)) {
selected_item = i; selected_item = i;
showSubMenu(i, startY); showSubMenu(i, startY);
}
Refresh(false); Refresh(false);
return; return;
} }
...@@ -321,18 +327,27 @@ bool DropDownList::onCharInParent(wxKeyEvent& ev) { ...@@ -321,18 +327,27 @@ bool DropDownList::onCharInParent(wxKeyEvent& ev) {
// sub menu always takes keys // sub menu always takes keys
return open_sub_menu->onCharInParent(ev); return open_sub_menu->onCharInParent(ev);
} else { } else {
size_t old_sel = selected_item;
switch (k) { switch (k) {
case WXK_UP: case WXK_UP:
if (selected_item > 0) { while (selected_item > 0) {
selected_item -= 1; selected_item -= 1;
if (itemEnabled(selected_item)) {
Refresh(false); Refresh(false);
return true;
}
} }
selected_item = old_sel;
break; break;
case WXK_DOWN: case WXK_DOWN:
if (selected_item + 1 < itemCount()) { while (selected_item + 1 < itemCount()) {
selected_item += 1; selected_item += 1;
if (itemEnabled(selected_item)) {
Refresh(false); Refresh(false);
return true;
}
} }
selected_item = old_sel;
break; break;
case WXK_RETURN: case WXK_RETURN:
if (!showSubMenu()) { if (!showSubMenu()) {
...@@ -351,6 +366,7 @@ bool DropDownList::onCharInParent(wxKeyEvent& ev) { ...@@ -351,6 +366,7 @@ bool DropDownList::onCharInParent(wxKeyEvent& ev) {
size_t count = itemCount(); size_t count = itemCount();
for (size_t i = 0 ; i < count ; ++i) { for (size_t i = 0 ; i < count ; ++i) {
size_t index = (si + i) % count; size_t index = (si + i) % count;
if (!itemEnabled(index)) continue;
String c = itemText(index); String c = itemText(index);
#ifdef UNICODE #ifdef UNICODE
if (!c.empty() && toUpper(c.GetChar(0)) == toUpper(ev.GetUnicodeKey())) { if (!c.empty() && toUpper(c.GetChar(0)) == toUpper(ev.GetUnicodeKey())) {
......
...@@ -40,6 +40,10 @@ class DropDownList : public wxPopupWindow { ...@@ -40,6 +40,10 @@ class DropDownList : public wxPopupWindow {
bool onMouseInParent(wxMouseEvent&, bool open_in_place); bool onMouseInParent(wxMouseEvent&, bool open_in_place);
protected: protected:
/// Prepare for showing the list
virtual void onShow() {}
// --------------------------------------------------- : Selection // --------------------------------------------------- : Selection
static const size_t NO_SELECTION = (size_t)-1; static const size_t NO_SELECTION = (size_t)-1;
...@@ -59,6 +63,8 @@ class DropDownList : public wxPopupWindow { ...@@ -59,6 +63,8 @@ class DropDownList : public wxPopupWindow {
virtual bool lineBelow(size_t item) const { return false; } virtual bool lineBelow(size_t item) const { return false; }
/// Should the item be highlighted? /// Should the item be highlighted?
virtual bool highlightItem(size_t item) const { return false; } virtual bool highlightItem(size_t item) const { return false; }
/// Is the item enabled?
virtual bool itemEnabled(size_t item) const { return true; }
// An extra submenu that pops up from an item, or null if there is no popup menu // An extra submenu that pops up from an item, or null if there is no popup menu
virtual DropDownList* submenu(size_t item) const { return nullptr; } virtual DropDownList* submenu(size_t item) const { return nullptr; }
......
...@@ -60,6 +60,7 @@ NewSetWindow::NewSetWindow(Window* parent) ...@@ -60,6 +60,7 @@ NewSetWindow::NewSetWindow(Window* parent)
void NewSetWindow::onGameSelect(wxCommandEvent&) { void NewSetWindow::onGameSelect(wxCommandEvent&) {
wxBusyCursor wait; wxBusyCursor wait;
GameP game = game_list->getSelection<Game>(); GameP game = game_list->getSelection<Game>();
handle_pending_errors();
settings.default_game = game->name(); settings.default_game = game->name();
GameSettings& gs = settings.gameSettingsFor(*game); GameSettings& gs = settings.gameSettingsFor(*game);
stylesheet_list->showData<StyleSheet>(game->name() + _("-*")); stylesheet_list->showData<StyleSheet>(game->name() + _("-*"));
...@@ -78,6 +79,7 @@ void NewSetWindow::onStyleSheetSelect(wxCommandEvent&) { ...@@ -78,6 +79,7 @@ void NewSetWindow::onStyleSheetSelect(wxCommandEvent&) {
// store this as default selection // store this as default selection
GameP game = game_list ->getSelection<Game>(); GameP game = game_list ->getSelection<Game>();
StyleSheetP stylesheet = stylesheet_list->getSelection<StyleSheet>(); StyleSheetP stylesheet = stylesheet_list->getSelection<StyleSheet>();
handle_pending_errors();
GameSettings& gs = settings.gameSettingsFor(*game); GameSettings& gs = settings.gameSettingsFor(*game);
gs.default_stylesheet = stylesheet->name(); gs.default_stylesheet = stylesheet->name();
UpdateWindowUI(wxUPDATE_UI_RECURSE); UpdateWindowUI(wxUPDATE_UI_RECURSE);
...@@ -113,10 +115,16 @@ void NewSetWindow::onUpdateUI(wxUpdateUIEvent& ev) { ...@@ -113,10 +115,16 @@ void NewSetWindow::onUpdateUI(wxUpdateUIEvent& ev) {
} }
} }
void NewSetWindow::onIdle(wxIdleEvent& ev) {
// Stuff that must be done in the main thread
handle_pending_errors();
}
BEGIN_EVENT_TABLE(NewSetWindow, wxDialog) BEGIN_EVENT_TABLE(NewSetWindow, wxDialog)
EVT_GALLERY_SELECT (ID_GAME_LIST, NewSetWindow::onGameSelect) EVT_GALLERY_SELECT (ID_GAME_LIST, NewSetWindow::onGameSelect)
EVT_GALLERY_SELECT (ID_STYLESHEET_LIST, NewSetWindow::onStyleSheetSelect) EVT_GALLERY_SELECT (ID_STYLESHEET_LIST, NewSetWindow::onStyleSheetSelect)
EVT_GALLERY_ACTIVATE(ID_STYLESHEET_LIST, NewSetWindow::onStyleSheetActivate) EVT_GALLERY_ACTIVATE(ID_STYLESHEET_LIST, NewSetWindow::onStyleSheetActivate)
EVT_BUTTON (wxID_OK, NewSetWindow::OnOK) EVT_BUTTON (wxID_OK, NewSetWindow::OnOK)
EVT_UPDATE_UI (wxID_ANY, NewSetWindow::onUpdateUI) EVT_UPDATE_UI (wxID_ANY, NewSetWindow::onUpdateUI)
EVT_IDLE ( NewSetWindow::onIdle)
END_EVENT_TABLE () END_EVENT_TABLE ()
...@@ -46,6 +46,7 @@ class NewSetWindow : public wxDialog { ...@@ -46,6 +46,7 @@ class NewSetWindow : public wxDialog {
virtual void OnOK(wxCommandEvent&); virtual void OnOK(wxCommandEvent&);
void onUpdateUI(wxUpdateUIEvent&); void onUpdateUI(wxUpdateUIEvent&);
void onIdle(wxIdleEvent&);
// we are done, close the window // we are done, close the window
void done(); void done();
......
...@@ -202,7 +202,7 @@ void draw_drop_down_arrow(Window* win, DC& dc, const wxRect& rect, bool active) ...@@ -202,7 +202,7 @@ void draw_drop_down_arrow(Window* win, DC& dc, const wxRect& rect, bool active)
, active ? wxCONTROL_PRESSED : 0); , active ? wxCONTROL_PRESSED : 0);
} }
void draw_checkbox(Window* win, DC& dc, const wxRect& rect, bool checked) { void draw_checkbox(Window* win, DC& dc, const wxRect& rect, bool checked, bool enabled) {
#if wxUSE_UXTHEME && defined(__WXMSW__) #if wxUSE_UXTHEME && defined(__WXMSW__)
// TODO: Windows version? // TODO: Windows version?
#endif #endif
...@@ -210,7 +210,7 @@ void draw_checkbox(Window* win, DC& dc, const wxRect& rect, bool checked) { ...@@ -210,7 +210,7 @@ void draw_checkbox(Window* win, DC& dc, const wxRect& rect, bool checked) {
if (checked) { if (checked) {
dc.DrawCheckMark(wxRect(rect.x-1,rect.y-1,rect.width+2,rect.height+2)); dc.DrawCheckMark(wxRect(rect.x-1,rect.y-1,rect.width+2,rect.height+2));
} }
dc.SetPen(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); dc.SetPen(wxSystemSettings::GetColour(enabled ? wxSYS_COLOUR_WINDOWTEXT: wxSYS_COLOUR_GRAYTEXT));
dc.SetBrush(*wxTRANSPARENT_BRUSH); dc.SetBrush(*wxTRANSPARENT_BRUSH);
dc.DrawRectangle(rect.x, rect.y, rect.width, rect.height); dc.DrawRectangle(rect.x, rect.y, rect.width, rect.height);
} }
...@@ -61,7 +61,7 @@ void draw_menu_arrow(Window* win, DC& dc, const wxRect& rect, bool active); ...@@ -61,7 +61,7 @@ void draw_menu_arrow(Window* win, DC& dc, const wxRect& rect, bool active);
void draw_drop_down_arrow(Window* win, DC& dc, const wxRect& rect, bool active); void draw_drop_down_arrow(Window* win, DC& dc, const wxRect& rect, bool active);
/// Draws a check box /// Draws a check box
void draw_checkbox(Window* win, DC& dc, const wxRect& rect, bool checked); void draw_checkbox(Window* win, DC& dc, const wxRect& rect, bool checked, bool enabled = true);
// ----------------------------------------------------------------------------- : EOF // ----------------------------------------------------------------------------- : EOF
#endif #endif
...@@ -106,6 +106,14 @@ DropDownChoiceListBase::DropDownChoiceListBase ...@@ -106,6 +106,14 @@ DropDownChoiceListBase::DropDownChoiceListBase
item_size.height = max(16., item_size.height); item_size.height = max(16., item_size.height);
} }
void DropDownChoiceListBase::onShow() {
// update 'enabled'
Context& ctx = cve.viewer.getContext();
FOR_EACH(c, group->choices) {
c->enabled.update(ctx);
}
}
size_t DropDownChoiceListBase::itemCount() const { size_t DropDownChoiceListBase::itemCount() const {
return group->choices.size() + hasDefault(); return group->choices.size() + hasDefault();
} }
...@@ -129,7 +137,10 @@ String DropDownChoiceListBase::itemText(size_t item) const { ...@@ -129,7 +137,10 @@ String DropDownChoiceListBase::itemText(size_t item) const {
} }
} }
bool DropDownChoiceListBase::lineBelow(size_t item) const { bool DropDownChoiceListBase::lineBelow(size_t item) const {
return isDefault(item); return isDefault(item) || getChoice(item)->line_below;
}
bool DropDownChoiceListBase::itemEnabled(size_t item) const {
return isDefault(item) || getChoice(item)->enabled;
} }
DropDownList* DropDownChoiceListBase::submenu(size_t item) const { DropDownList* DropDownChoiceListBase::submenu(size_t item) const {
if (isDefault(item)) return nullptr; if (isDefault(item)) return nullptr;
...@@ -157,7 +168,7 @@ void DropDownChoiceListBase::drawIcon(DC& dc, int x, int y, size_t item, bool se ...@@ -157,7 +168,7 @@ void DropDownChoiceListBase::drawIcon(DC& dc, int x, int y, size_t item, bool se
} }
// draw image // draw image
if (image_id < il->GetImageCount()) { if (image_id < il->GetImageCount()) {
il->Draw(image_id, dc, x, y); il->Draw(image_id, dc, x, y, itemEnabled(item) ? wxIMAGELIST_DRAW_NORMAL : wxIMAGELIST_DRAW_TRANSPARENT);
} }
} }
...@@ -196,6 +207,12 @@ DropDownChoiceList::DropDownChoiceList(Window* parent, bool is_submenu, ValueVie ...@@ -196,6 +207,12 @@ DropDownChoiceList::DropDownChoiceList(Window* parent, bool is_submenu, ValueVie
: DropDownChoiceListBase(parent, is_submenu, cve, group) : DropDownChoiceListBase(parent, is_submenu, cve, group)
{} {}
void DropDownChoiceList::onShow() {
DropDownChoiceListBase::onShow();
// we need thumbnail images soon
generateThumbnailImages();
}
void DropDownChoiceList::select(size_t item) { void DropDownChoiceList::select(size_t item) {
if (isFieldDefault(item)) { if (isFieldDefault(item)) {
dynamic_cast<ChoiceValueEditor&>(cve).change( Defaultable<String>() ); dynamic_cast<ChoiceValueEditor&>(cve).change( Defaultable<String>() );
...@@ -206,8 +223,6 @@ void DropDownChoiceList::select(size_t item) { ...@@ -206,8 +223,6 @@ void DropDownChoiceList::select(size_t item) {
} }
size_t DropDownChoiceList::selection() const { size_t DropDownChoiceList::selection() const {
// we need thumbnail images soon
const_cast<DropDownChoiceList*>(this)->generateThumbnailImages();
// selected item // selected item
const Defaultable<String>& value = dynamic_cast<ChoiceValueEditor&>(cve).value().value(); const Defaultable<String>& value = dynamic_cast<ChoiceValueEditor&>(cve).value().value();
int id = field().choices->choiceId(value); int id = field().choices->choiceId(value);
......
...@@ -49,8 +49,10 @@ class DropDownChoiceListBase : public DropDownList { ...@@ -49,8 +49,10 @@ class DropDownChoiceListBase : public DropDownList {
DropDownChoiceListBase(Window* parent, bool is_submenu, ValueViewer& cve, ChoiceField::ChoiceP group); DropDownChoiceListBase(Window* parent, bool is_submenu, ValueViewer& cve, ChoiceField::ChoiceP group);
protected: protected:
virtual void onShow();
virtual size_t itemCount() const; virtual size_t itemCount() const;
virtual bool lineBelow(size_t item) const; virtual bool lineBelow(size_t item) const;
virtual bool itemEnabled(size_t item) const;
virtual String itemText(size_t item) const; virtual String itemText(size_t item) const;
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;
...@@ -92,6 +94,7 @@ class DropDownChoiceList : public DropDownChoiceListBase { ...@@ -92,6 +94,7 @@ class DropDownChoiceList : public DropDownChoiceListBase {
DropDownChoiceList(Window* parent, bool is_submenu, ValueViewer& cve, ChoiceField::ChoiceP group); DropDownChoiceList(Window* parent, bool is_submenu, ValueViewer& cve, ChoiceField::ChoiceP group);
protected: protected:
virtual void onShow();
virtual void select(size_t item); virtual void select(size_t item);
virtual size_t selection() const; virtual size_t selection() const;
virtual DropDownList* createSubMenu(ChoiceField::ChoiceP group) const; virtual DropDownList* createSubMenu(ChoiceField::ChoiceP group) const;
......
...@@ -33,27 +33,31 @@ DropDownMultipleChoiceList::DropDownMultipleChoiceList ...@@ -33,27 +33,31 @@ DropDownMultipleChoiceList::DropDownMultipleChoiceList
} }
void DropDownMultipleChoiceList::select(size_t item) { void DropDownMultipleChoiceList::select(size_t item) {
MultipleChoiceValueEditor& mcve = dynamic_cast<MultipleChoiceValueEditor&>(cve);
if (isFieldDefault(item)) { if (isFieldDefault(item)) {
// should not happen // make default
mcve.getSet().actions.add(value_action(mcve.valueP(), Defaultable<String>()));
} else { } else {
ChoiceField::ChoiceP choice = getChoice(item); ChoiceField::ChoiceP choice = getChoice(item);
dynamic_cast<MultipleChoiceValueEditor&>(cve).toggle(choice->first_id); mcve.toggle(choice->first_id);
} }
} }
void DropDownMultipleChoiceList::drawIcon(DC& dc, int x, int y, size_t item, bool selected) const { void DropDownMultipleChoiceList::drawIcon(DC& dc, int x, int y, size_t item, bool selected) const {
// is this item active? // is this item active/checked?
bool active = false; bool active = false;
if (!isFieldDefault(item)) { if (!isFieldDefault(item)) {
ChoiceField::ChoiceP choice = getChoice(item); ChoiceField::ChoiceP choice = getChoice(item);
active = dynamic_cast<MultipleChoiceValueEditor&>(cve).active[choice->first_id]; active = dynamic_cast<MultipleChoiceValueEditor&>(cve).active[choice->first_id];
} else {
active = dynamic_cast<MultipleChoiceValueEditor&>(cve).value().value.isDefault();
} }
// draw checkbox // draw checkbox
dc.SetPen(*wxTRANSPARENT_PEN); dc.SetPen(*wxTRANSPARENT_PEN);
dc.SetBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); dc.SetBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
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);
draw_checkbox(nullptr, dc, rect, active); draw_checkbox(nullptr, dc, rect, active, itemEnabled(item));
// draw icon // draw icon
DropDownChoiceListBase::drawIcon(dc, x + 16, y, item, selected); DropDownChoiceListBase::drawIcon(dc, x + 16, y, item, selected);
} }
......
...@@ -32,11 +32,54 @@ void MultipleChoiceValueViewer::draw(RotatedDC& dc) { ...@@ -32,11 +32,54 @@ void MultipleChoiceValueViewer::draw(RotatedDC& dc) {
if (active) select_it++; if (active) select_it++;
drawChoice(dc, pos, choice, active); drawChoice(dc, pos, choice, active);
} }
} else { } else if (style().render_style & RENDER_LIST) {
// render only selected choices // render only selected choices
FOR_EACH(choice, selected) { FOR_EACH(choice, selected) {
drawChoice(dc, pos, choice); drawChoice(dc, pos, choice);
} }
} else {
// COPY FROM ChoiceValueViewer
if (value().value().empty()) return;
double margin = 0;
if (style().render_style & RENDER_IMAGE) {
// draw image
map<String,ScriptableImage>::iterator it = style().choice_images.find(cannocial_name_form(value().value()));
if (it != style().choice_images.end() && it->second.isReady()) {
ScriptableImage& img = it->second;
GeneratedImage::Options img_options(0,0, viewer.stylesheet.get(), &getSet());
if (nativeLook()) {
img_options.width = img_options.height = 16;
img_options.preserve_aspect = ASPECT_BORDER;
} else if(style().render_style & RENDER_TEXT) {
// also drawing text, use original size
} else {
img_options.width = (int) dc.trS(style().width);
img_options.height = (int) dc.trS(style().height);
img_options.preserve_aspect = style().alignment == ALIGN_STRETCH ? ASPECT_STRETCH : ASPECT_FIT;
}
Image image = img.generate(img_options, true);
ImageCombine combine = img.combine();
// apply mask?
style().loadMask(*viewer.stylesheet);
if (style().mask.Ok()) {
set_alpha(image, style().mask);
}
// draw
dc.DrawImage(image,
align_in_rect(style().alignment, RealSize(image.GetWidth(), image.GetHeight()), style().getRect()),
combine == COMBINE_NORMAL ? style().combine : combine,
style().angle
);
margin = dc.trInvS(image.GetWidth()) + 1;
}
}
if (style().render_style & RENDER_TEXT) {
// draw text
dc.DrawText(tr(*viewer.stylesheet, value().value(), capitalize(value().value())),
align_in_rect(ALIGN_MIDDLE_LEFT, RealSize(0, dc.GetCharHeight()), style().getRect()) + RealSize(margin, 0)
);
}
// COPY ENDS HERE
} }
} }
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include <data/game.hpp> #include <data/game.hpp>
#include <data/field/text.hpp> #include <data/field/text.hpp>
#include <data/field/choice.hpp> #include <data/field/choice.hpp>
#include <data/field/multiple_choice.hpp>
DECLARE_TYPEOF_COLLECTION(FieldP); DECLARE_TYPEOF_COLLECTION(FieldP);
DECLARE_TYPEOF_COLLECTION(TextValue*); DECLARE_TYPEOF_COLLECTION(TextValue*);
...@@ -140,7 +141,7 @@ SCRIPT_FUNCTION(primary_choice) { ...@@ -140,7 +141,7 @@ SCRIPT_FUNCTION(primary_choice) {
SCRIPT_PARAM(ValueP,input); SCRIPT_PARAM(ValueP,input);
ChoiceValueP value = dynamic_pointer_cast<ChoiceValue>(input); ChoiceValueP value = dynamic_pointer_cast<ChoiceValue>(input);
if (!value) { if (!value) {
throw ScriptError(_("Argument to 'primary_choice' should be a choice field")); throw ScriptError(_("Argument to 'primary_choice' should be a choice value"));
} }
// determine choice // determine choice
int id = value->field().choices->choiceId(value->value); int id = value->field().choices->choiceId(value->value);
...@@ -154,10 +155,46 @@ SCRIPT_FUNCTION(primary_choice) { ...@@ -154,10 +155,46 @@ SCRIPT_FUNCTION(primary_choice) {
SCRIPT_RETURN(_("")); SCRIPT_RETURN(_(""));
} }
// ----------------------------------------------------------------------------- : Multiple choice values
// is the given choice active?
SCRIPT_FUNCTION(chosen) {
SCRIPT_PARAM(String,choice);
SCRIPT_PARAM(String,input);
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();
if (end - pos == choice.size() && is_substr(input, pos, choice)) {
SCRIPT_RETURN(true);
}
pos = end + 1;
}
}
SCRIPT_RETURN(false);
}
// add the given choice if it is not already active
SCRIPT_FUNCTION(require_choice) {
SCRIPT_PARAM(ValueP,input);
MultipleChoiceValueP value = dynamic_pointer_cast<MultipleChoiceValue>(input);
if (!value) {
throw ScriptError(_("Argument 'input' to 'require_choice' should be a multiple choice value"));
}
SCRIPT_PARAM(String,choice);
// TODO
SCRIPT_RETURN(input);
}
// ----------------------------------------------------------------------------- : Init // ----------------------------------------------------------------------------- : Init
void init_script_editor_functions(Context& ctx) { void init_script_editor_functions(Context& ctx) {
ctx.setVariable(_("forward editor"), script_combined_editor); // combatability ctx.setVariable(_("forward editor"), script_combined_editor); // combatability
ctx.setVariable(_("combined editor"), script_combined_editor); ctx.setVariable(_("combined editor"), script_combined_editor);
ctx.setVariable(_("primary choice"), script_primary_choice); ctx.setVariable(_("primary choice"), script_primary_choice);
ctx.setVariable(_("chosen"), script_chosen);
ctx.setVariable(_("require choice"), script_require_choice);
} }
...@@ -567,7 +567,7 @@ void parseOper(TokenIterator& input, Script& script, Precedence minPrec, Instruc ...@@ -567,7 +567,7 @@ void parseOper(TokenIterator& input, Script& script, Precedence minPrec, Instruc
t = input.peek(); t = input.peek();
} }
} }
input.read(); // skip the ) expectToken(input, _(")"));
// generate instruction // generate instruction
script.addInstruction(I_CALL, (unsigned int)arguments.size()); script.addInstruction(I_CALL, (unsigned int)arguments.size());
FOR_EACH(arg,arguments) { FOR_EACH(arg,arguments) {
......
...@@ -14,6 +14,8 @@ ...@@ -14,6 +14,8 @@
Alignment from_string(const String&); Alignment from_string(const String&);
DECLARE_TYPEOF_COLLECTION(ScriptParseError);
// ----------------------------------------------------------------------------- : Store // ----------------------------------------------------------------------------- : Store
void store(const ScriptValueP& val, String& var) { var = val->toString(); } void store(const ScriptValueP& val, String& var) { var = val->toString(); }
...@@ -43,10 +45,16 @@ ScriptValueP OptionalScript::invoke(Context& ctx, bool open_scope) const { ...@@ -43,10 +45,16 @@ ScriptValueP OptionalScript::invoke(Context& ctx, bool open_scope) const {
} }
void OptionalScript::parse(Reader& reader, bool string_mode) { void OptionalScript::parse(Reader& reader, bool string_mode) {
try { vector<ScriptParseError> errors;
script = ::parse(unparsed, string_mode); script = ::parse(unparsed, string_mode, errors);
} catch (const ParseError& e) { // show parse errors as warnings
reader.warning(e.what()); FOR_EACH(e, errors) {
// find line number
int line = 0;
for (size_t i = 0 ; i < unparsed.size() && i < e.start ; ++i) {
if (unparsed.GetChar(i) == _('\n')) line++;
}
reader.warning(e.ParseError::what(), line); // use ParseError::what because we don't want e.start in the error message
} }
} }
......
...@@ -147,6 +147,7 @@ void Reader::handle(Scriptable<T>& s) { ...@@ -147,6 +147,7 @@ void Reader::handle(Scriptable<T>& s) {
} else if (s.script.unparsed.find_first_of('{') != String::npos) { } else if (s.script.unparsed.find_first_of('{') != String::npos) {
s.script.parse(*this, true); s.script.parse(*this, true);
} else { } else {
unhandle();
handle(s.value); handle(s.value);
} }
} }
......
...@@ -14,19 +14,19 @@ ...@@ -14,19 +14,19 @@
// ----------------------------------------------------------------------------- : Reader // ----------------------------------------------------------------------------- : Reader
Reader::Reader(const InputStreamP& input, const String& filename, bool ignore_invalid) Reader::Reader(const InputStreamP& input, const String& filename, bool ignore_invalid)
: indent(0), expected_indent(0), just_opened(false) : indent(0), expected_indent(0), state(OUTSIDE)
, filename(filename), line_number(0) , filename(filename), line_number(0), previous_line_number(0)
, ignore_invalid(ignore_invalid) , ignore_invalid(ignore_invalid)
, input(input), stream(*input) , input(input)
{ {
moveNext(); moveNext();
handleAppVersion(); handleAppVersion();
} }
Reader::Reader(const String& filename) Reader::Reader(const String& filename)
: indent(0), expected_indent(0), just_opened(false) : indent(0), expected_indent(0), state(OUTSIDE)
, filename(filename), line_number(0) , filename(filename), line_number(0), previous_line_number(0)
, input(packages.openFileFromPackage(filename)), stream(*input) , input(packages.openFileFromPackage(filename))
{ {
moveNext(); moveNext();
handleAppVersion(); handleAppVersion();
...@@ -48,8 +48,10 @@ void Reader::handleAppVersion() { ...@@ -48,8 +48,10 @@ void Reader::handleAppVersion() {
} }
} }
void Reader::warning(const String& msg) { void Reader::warning(const String& msg, int line_number_delta, bool warn_on_previous_line) {
warnings += String(_("\nOn line ")) << line_number << _(": \t") << msg; warnings += String(_("\nOn line "))
<< ((warn_on_previous_line ? previous_line_number : line_number) + line_number_delta)
<< _(": \t") << msg;
} }
void Reader::showWarnings() { void Reader::showWarnings() {
...@@ -59,11 +61,19 @@ void Reader::showWarnings() { ...@@ -59,11 +61,19 @@ void Reader::showWarnings() {
} }
} }
bool Reader::enterAnyBlock() {
if (state == ENTERED) moveNext(); // on the key of the parent block, first move inside it
if (indent != expected_indent) return false; // not enough indentation
state = ENTERED;
expected_indent += 1; // the indent inside the block must be at least this much
return true;
}
bool Reader::enterBlock(const Char* name) { bool Reader::enterBlock(const Char* name) {
if (just_opened) moveNext(); // on the key of the parent block, first move inside it if (state == ENTERED) moveNext(); // on the key of the parent block, first move inside it
if (indent != expected_indent) return false; // not enough indentation if (indent != expected_indent) return false; // not enough indentation
if (cannocial_name_compare(key, name)) { if (cannocial_name_compare(key, name)) {
just_opened = true; state = ENTERED;
expected_indent += 1; // the indent inside the block must be at least this much expected_indent += 1; // the indent inside the block must be at least this much
return true; return true;
} else { } else {
...@@ -74,20 +84,21 @@ bool Reader::enterBlock(const Char* name) { ...@@ -74,20 +84,21 @@ bool Reader::enterBlock(const Char* name) {
void Reader::exitBlock() { void Reader::exitBlock() {
assert(expected_indent > 0); assert(expected_indent > 0);
expected_indent -= 1; expected_indent -= 1;
multi_line_str.clear(); assert(state != UNHANDLED);
if (just_opened) moveNext(); // leave this key previous_value.clear();
if (state == ENTERED) moveNext(); // leave this key
// Dump the remainder of the block // Dump the remainder of the block
// TODO: issue warnings? // TODO: issue warnings?
while (indent > expected_indent) { while (indent > expected_indent) {
moveNext(); moveNext();
} }
handled = true; state = HANDLED;
} }
void Reader::moveNext() { void Reader::moveNext() {
just_opened = false; previous_line_number = line_number;
state = HANDLED;
key.clear(); key.clear();
multi_line_str.clear();
indent = -1; // if no line is read it never has the expected indentation indent = -1; // if no line is read it never has the expected indentation
// repeat until we have a good line // repeat until we have a good line
while (key.empty() && !input->Eof()) { while (key.empty() && !input->Eof()) {
...@@ -100,10 +111,55 @@ void Reader::moveNext() { ...@@ -100,10 +111,55 @@ void Reader::moveNext() {
} }
} }
/// Read an UTF-8 encoded line from an input stream
/** As opposed to wx functions, this one actually reports errors
*/
String read_utf8_line(wxInputStream& input, bool eat_bom = true, bool until_eof = false);
String read_utf8_line(wxInputStream& input, bool eat_bom, bool until_eof) {
vector<char> buffer;
while (!input.Eof()) {
Byte c = input.GetC(); if (input.LastRead() <= 0) break;
if (!until_eof) {
if (c == '\n') break;
if (c == '\r') {
if (input.Eof()) break;
c = input.GetC(); if (input.LastRead() <= 0) break;
if (c != '\n') {
input.Ungetch(c); // \r but not \r\n
}
break;
}
}
buffer.push_back(c);
}
// convert to string
buffer.push_back('\0');
size_t size = wxConvUTF8.MB2WC(nullptr, &buffer[0], 0);
if (size == -1) {
throw ParseError(_("Invalid UTF-8 sequence"));
} else if (size == 0) {
return _("");
}
String result;
#ifdef UNICODE
// NOTE: wx doc is wrong, parameter to GetWritableChar is numer of characters, not bytes
Char* result_buf = result.GetWriteBuf(size + 1);
wxConvUTF8.MB2WC(result_buf, &buffer[0], size + 1);
result.UngetWriteBuf(size);
#else
// TODO!
#endif
return eat_bom ? decodeUTF8BOM(result) : result;
}
void Reader::readLine(bool in_string) { void Reader::readLine(bool in_string) {
// fix UTF8 in ascii builds; skip BOM
line = decodeUTF8BOM(stream.ReadLine());
line_number += 1; line_number += 1;
// We have to do our own line reading, because wxTextInputStream is insane
try {
line = read_utf8_line(*input, line_number == 1);
} catch (const ParseError& e) {
throw ParseError(e.what() + String(_(" on line ")) << line_number);
}
// read indentation // read indentation
indent = 0; indent = 0;
while ((UInt)indent < line.size() && line.GetChar(indent) == _('\t')) { while ((UInt)indent < line.size() && line.GetChar(indent) == _('\t')) {
...@@ -118,7 +174,7 @@ void Reader::readLine(bool in_string) { ...@@ -118,7 +174,7 @@ void Reader::readLine(bool in_string) {
} }
key = line.substr(indent, pos - indent); key = line.substr(indent, pos - indent);
if (!ignore_invalid && !in_string && starts_with(key, _(" "))) { if (!ignore_invalid && !in_string && starts_with(key, _(" "))) {
warning(_("key: '") + key + _("' starts with a space; only use TABs for indentation!")); warning(_("key: '") + key + _("' starts with a space; only use TABs for indentation!"), 0, false);
// try to fix up: 8 spaces is a tab // try to fix up: 8 spaces is a tab
while (starts_with(key, _(" "))) { while (starts_with(key, _(" "))) {
key = key.substr(8); key = key.substr(8);
...@@ -146,7 +202,7 @@ void Reader::unknownKey() { ...@@ -146,7 +202,7 @@ void Reader::unknownKey() {
} else if (it->second.end_version <= file_app_version) { } else if (it->second.end_version <= file_app_version) {
// alias not used for this version, use in warning // alias not used for this version, use in warning
if (indent == expected_indent) { if (indent == expected_indent) {
warning(_("Unexpected key: '") + key + _("' use '") + it->second.new_key + _("'")); warning(_("Unexpected key: '") + key + _("' use '") + it->second.new_key + _("'"), 0, false);
do { do {
moveNext(); moveNext();
} while (indent > expected_indent); } while (indent > expected_indent);
...@@ -159,7 +215,7 @@ void Reader::unknownKey() { ...@@ -159,7 +215,7 @@ void Reader::unknownKey() {
} }
} }
if (indent >= expected_indent) { if (indent >= expected_indent) {
warning(_("Unexpected key: '") + key + _("'")); warning(_("Unexpected key: '") + key + _("'"), 0, false);
do { do {
moveNext(); moveNext();
} while (indent > expected_indent); } while (indent > expected_indent);
...@@ -169,23 +225,31 @@ void Reader::unknownKey() { ...@@ -169,23 +225,31 @@ void Reader::unknownKey() {
// ----------------------------------------------------------------------------- : Handling basic types // ----------------------------------------------------------------------------- : Handling basic types
void Reader::unhandle() {
assert(state == HANDLED);
state = UNHANDLED;
}
const String& Reader::getValue() { const String& Reader::getValue() {
handled = true; assert(state != HANDLED); // don't try to handle things twice
if (!multi_line_str.empty()) { if (state == UNHANDLED) {
return multi_line_str; state = HANDLED;
return previous_value;
} else if (value.empty()) { } else if (value.empty()) {
// a multiline string // a multiline string
previous_value.clear();
bool first = true; bool first = true;
// read all lines that are indented enough // read all lines that are indented enough
readLine(); readLine();
previous_line_number = line_number;
while (indent >= expected_indent && !input->Eof()) { while (indent >= expected_indent && !input->Eof()) {
if (!first) multi_line_str += _('\n'); if (!first) previous_value += _('\n');
first = false; first = false;
multi_line_str += line.substr(expected_indent); // strip expected indent previous_value += line.substr(expected_indent); // strip expected indent
readLine(true); readLine(true);
} }
// moveNext(), but without emptying multi_line_str // moveNext(), but without the initial readLine()
just_opened = false; state = HANDLED;
while (key.empty() && !input->Eof()) { while (key.empty() && !input->Eof()) {
readLine(); readLine();
} }
...@@ -194,9 +258,16 @@ const String& Reader::getValue() { ...@@ -194,9 +258,16 @@ const String& Reader::getValue() {
line_number += 1; line_number += 1;
indent = -1; indent = -1;
} }
return multi_line_str; if (indent >= expected_indent) {
warning(_("Blank line or comment in text block, that is insufficiently indented.\n")
_("\t\tEither indent the comment/blank line, or add a 'key:' after it.\n")
_("\t\tThis could cause more more error messages.\n"), -1, false);
}
return previous_value;
} else { } else {
return value; previous_value = value;
moveNext();
return previous_value;
} }
} }
...@@ -217,7 +288,8 @@ template <> void Reader::handle(double& d) { ...@@ -217,7 +288,8 @@ template <> void Reader::handle(double& d) {
getValue().ToDouble(&d); getValue().ToDouble(&d);
} }
template <> void Reader::handle(bool& b) { template <> void Reader::handle(bool& b) {
b = (getValue()==_("true") || getValue()==_("1") || getValue()==_("yes")); const String& v = getValue();
b = (v==_("true") || v==_("1") || v==_("yes"));
} }
// ----------------------------------------------------------------------------- : Handling less basic util types // ----------------------------------------------------------------------------- : Handling less basic util types
......
...@@ -11,7 +11,6 @@ ...@@ -11,7 +11,6 @@
#include <util/prec.hpp> #include <util/prec.hpp>
#include <util/version.hpp> #include <util/version.hpp>
#include <wx/txtstrm.h>
template <typename T> class Defaultable; template <typename T> class Defaultable;
template <typename T> class Scriptable; template <typename T> class Scriptable;
...@@ -57,7 +56,7 @@ class Reader { ...@@ -57,7 +56,7 @@ class Reader {
void handleAppVersion(); void handleAppVersion();
/// Add a warning message, but continue reading /// Add a warning message, but continue reading
void warning(const String& msg); void warning(const String& msg, int line_number_delta = 0, bool warn_on_previous_line = true);
/// Show all warning messages, but continue reading /// Show all warning messages, but continue reading
void showWarnings(); void showWarnings();
...@@ -66,11 +65,9 @@ class Reader { ...@@ -66,11 +65,9 @@ class Reader {
template <typename T> template <typename T>
void handle_greedy(T& object) { void handle_greedy(T& object) {
do { do {
// UInt l = line_number;
handled = false;
handle(object); handle(object);
// if (l == line_number && !handled) unknownKey(object); if (state != HANDLED) unknownKey(object);
if (!handled) unknownKey(object); state = OUTSIDE;
} while (indent >= expected_indent); } while (indent >= expected_indent);
} }
...@@ -106,6 +103,9 @@ class Reader { ...@@ -106,6 +103,9 @@ class Reader {
void handle(GameP&); void handle(GameP&);
void handle(StyleSheetP&); void handle(StyleSheetP&);
/// Indicate that the last value from getValue() was not handled, allowing it to be handled again
void unhandle();
// --------------------------------------------------- : Data // --------------------------------------------------- : Data
/// App version this file was made with /// App version this file was made with
Version file_app_version; Version file_app_version;
...@@ -114,16 +114,19 @@ class Reader { ...@@ -114,16 +114,19 @@ class Reader {
String line; String line;
/// The key and value of the last line we read /// The key and value of the last line we read
String key, value; String key, value;
/// A string spanning multiple lines /// Value of the *previous* line, only valid in state==HANDLED
String multi_line_str; String previous_value;
/// Has the current line been handled?
bool handled;
/// Indentation of the last line we read /// Indentation of the last line we read
int indent; int indent;
/// Indentation of the block we are in /// Indentation of the block we are in
int expected_indent; int expected_indent;
/// Did we just open a block (i.e. not read any more lines of it)? /// State of the reader
bool just_opened; enum State {
OUTSIDE, ///< We have not entered the block of the current key
ENTERED, ///< We just entered the block of the current key
HANDLED, ///< We have handled a value, and moved to the next line, previous_value is the value we just handled
UNHANDLED, ///< Something has been 'unhandled()'
} state;
/// Aliasses for compatability /// Aliasses for compatability
struct Alias { struct Alias {
String new_key; String new_key;
...@@ -136,19 +139,21 @@ class Reader { ...@@ -136,19 +139,21 @@ class Reader {
/// Filename for error messages /// Filename for error messages
String filename; String filename;
/// Line number for error messages /// Line number of the current line for error messages
UInt line_number; int line_number;
/// Line number of the previous_line
int previous_line_number;
/// Input stream we are reading from /// Input stream we are reading from
InputStreamP input; InputStreamP input;
/// Text stream wrapping the input stream
wxTextInputStream stream;
/// Accumulated warning messages /// Accumulated warning messages
String warnings; String warnings;
// --------------------------------------------------- : Reading the stream // --------------------------------------------------- : Reading the stream
/// Is there a block with the given key under the current cursor? /// Is there a block with the given key under the current cursor? if so, enter it
bool enterBlock(const Char* name); bool enterBlock(const Char* name);
/// Enter any block, no matter what the key
bool enterAnyBlock();
/// Leave the block we are in /// Leave the block we are in
void exitBlock(); void exitBlock();
...@@ -208,13 +213,7 @@ void Reader::handle(intrusive_ptr<T>& pointer) { ...@@ -208,13 +213,7 @@ void Reader::handle(intrusive_ptr<T>& pointer) {
template <typename V> template <typename V>
void Reader::handle(map<String, V>& m) { void Reader::handle(map<String, V>& m) {
while (true) { while (enterAnyBlock()) {
// same as enterBlock
if (just_opened) moveNext(); // on the key of the parent block, first move inside it
if (indent != expected_indent) return; // not enough indentation
just_opened = true;
expected_indent += 1;
// now read the value
handle_greedy(m[key]); handle_greedy(m[key]);
exitBlock(); exitBlock();
} }
......
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