mirror of
https://github.com/amyinspace/MagicSetEditor2.git
synced 2026-06-10 04:57:00 -04:00
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. git-svn-id: svn://svn.code.sf.net/p/magicseteditor/code/trunk@420 0fc631ac-6414-0410-93d0-97cfa31319b6
This commit is contained in:
@@ -49,9 +49,11 @@ IMPLEMENT_REFLECTION(ChoiceField) {
|
||||
|
||||
ChoiceField::Choice::Choice()
|
||||
: first_id(0)
|
||||
, line_below(false), enabled(true)
|
||||
{}
|
||||
ChoiceField::Choice::Choice(const String& name)
|
||||
: name(name), first_id(0)
|
||||
, line_below(false), enabled(true)
|
||||
{}
|
||||
|
||||
|
||||
@@ -138,11 +140,13 @@ String ChoiceField::Choice::choiceNameNice(int id) const {
|
||||
|
||||
|
||||
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
|
||||
REFLECT(name);
|
||||
REFLECT_N("group_choice", default_name);
|
||||
REFLECT(choices);
|
||||
REFLECT(line_below);
|
||||
REFLECT(enabled);
|
||||
} else {
|
||||
REFLECT_NAMELESS(name);
|
||||
}
|
||||
@@ -237,6 +241,9 @@ IMPLEMENT_REFLECTION_ENUM(ChoiceRenderStyle) {
|
||||
VALUE_N("checklist", RENDER_TEXT_CHECKLIST);
|
||||
VALUE_N("image checklist", RENDER_IMAGE_CHECKLIST);
|
||||
VALUE_N("both checklist", RENDER_BOTH_CHECKLIST);
|
||||
VALUE_N("text list", RENDER_IMAGE_LIST);
|
||||
VALUE_N("image list", RENDER_IMAGE_LIST);
|
||||
VALUE_N("both list", RENDER_IMAGE_LIST);
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION(ChoiceStyle) {
|
||||
|
||||
@@ -52,9 +52,11 @@ class ChoiceField::Choice : public IntrusivePtrBase<ChoiceField::Choice> {
|
||||
Choice();
|
||||
Choice(const String& name);
|
||||
|
||||
String name; ///< Name/value of the item
|
||||
String default_name; ///< A default item, if this is a group and default_name.empty() there is no default
|
||||
vector<ChoiceP> choices; ///< Choices and sub groups in this group
|
||||
String name; ///< Name/value of the item
|
||||
String default_name; ///< A default item, if this is a group and default_name.empty() there is no default
|
||||
vector<ChoiceP> choices; ///< Choices and sub groups in this group
|
||||
bool line_below; ///< Show a line after this item?
|
||||
Scriptable<bool> enabled; ///< Is this item enabled?
|
||||
/// First item-id in this group (can be the default item)
|
||||
/** Item-ids are consecutive integers, a group uses all ids [first_id..lastId()).
|
||||
* The top level group has first_id 0.
|
||||
@@ -109,11 +111,15 @@ enum ChoiceRenderStyle
|
||||
, RENDER_IMAGE = 0x10 // render an image
|
||||
, RENDER_HIDDEN = 0x20 // don't render anything, only have a menu
|
||||
, RENDER_CHECKLIST = 0x100 // render as a checklist, intended for multiple choice
|
||||
, RENDER_LIST = 0x200 // render as a list of images/text, intended for multiple choice
|
||||
, RENDER_BOTH = RENDER_TEXT | RENDER_IMAGE
|
||||
, RENDER_HIDDEN_IMAGE = RENDER_HIDDEN | RENDER_IMAGE
|
||||
, RENDER_TEXT_CHECKLIST = RENDER_CHECKLIST | RENDER_TEXT
|
||||
, RENDER_IMAGE_CHECKLIST = RENDER_CHECKLIST | RENDER_IMAGE
|
||||
, RENDER_BOTH_CHECKLIST = RENDER_CHECKLIST | RENDER_BOTH
|
||||
, RENDER_TEXT_LIST = RENDER_LIST | RENDER_TEXT
|
||||
, RENDER_IMAGE_LIST = RENDER_LIST | RENDER_IMAGE
|
||||
, RENDER_BOTH_LIST = RENDER_LIST | RENDER_BOTH
|
||||
};
|
||||
|
||||
enum ThumbnailStatus
|
||||
|
||||
@@ -25,6 +25,7 @@ IMPLEMENT_REFLECTION(MultipleChoiceField) {
|
||||
REFLECT_BASE(ChoiceField);
|
||||
REFLECT(minimum_selection);
|
||||
REFLECT(maximum_selection);
|
||||
REFLECT(empty_choice);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : MultipleChoiceStyle
|
||||
|
||||
@@ -25,6 +25,7 @@ class MultipleChoiceField : public ChoiceField {
|
||||
DECLARE_FIELD_TYPE(MultipleChoiceField);
|
||||
|
||||
UInt minimum_selection, maximum_selection; ///< How many choices can be selected simultaniously?
|
||||
String empty_choice; ///< Name to use when nothing is selected
|
||||
|
||||
private:
|
||||
DECLARE_REFLECTION();
|
||||
@@ -56,7 +57,7 @@ class MultipleChoiceValue : public ChoiceValue {
|
||||
inline MultipleChoiceValue(const MultipleChoiceFieldP& field) : ChoiceValue(field, true) {}
|
||||
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
|
||||
void get(vector<String>& out) const;
|
||||
@@ -65,6 +66,10 @@ class MultipleChoiceValue : public ChoiceValue {
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Utilities
|
||||
|
||||
/// Is the given choice selected in the value?
|
||||
bool chosen(const String& multiple_choice_value, const String& chioce);
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
#endif
|
||||
|
||||
@@ -84,6 +84,7 @@ DropDownList::~DropDownList() {
|
||||
|
||||
void DropDownList::show(bool in_place, wxPoint pos) {
|
||||
if (IsShown()) return;
|
||||
onShow();
|
||||
// find selection
|
||||
selected_item = selection();
|
||||
// width
|
||||
@@ -248,6 +249,9 @@ void DropDownList::drawItem(DC& dc, int y, size_t item) {
|
||||
dc.SetBrush (wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT));
|
||||
dc.SetTextForeground(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT));
|
||||
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)) {
|
||||
// mix a color between selection and window
|
||||
dc.SetBrush (lerp(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT),
|
||||
@@ -266,7 +270,7 @@ void DropDownList::drawItem(DC& dc, int y, size_t item) {
|
||||
}
|
||||
// draw line below
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -295,8 +299,10 @@ void DropDownList::onMotion(wxMouseEvent& ev) {
|
||||
for (size_t i = 0 ; i < count ; ++i) {
|
||||
int endY = startY + (int)item_size.height;
|
||||
if (ev.GetY() >= startY && ev.GetY() < endY) {
|
||||
selected_item = i;
|
||||
showSubMenu(i, startY);
|
||||
if (itemEnabled(i)) {
|
||||
selected_item = i;
|
||||
showSubMenu(i, startY);
|
||||
}
|
||||
Refresh(false);
|
||||
return;
|
||||
}
|
||||
@@ -321,18 +327,27 @@ bool DropDownList::onCharInParent(wxKeyEvent& ev) {
|
||||
// sub menu always takes keys
|
||||
return open_sub_menu->onCharInParent(ev);
|
||||
} else {
|
||||
size_t old_sel = selected_item;
|
||||
switch (k) {
|
||||
case WXK_UP:
|
||||
if (selected_item > 0) {
|
||||
while (selected_item > 0) {
|
||||
selected_item -= 1;
|
||||
Refresh(false);
|
||||
if (itemEnabled(selected_item)) {
|
||||
Refresh(false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
selected_item = old_sel;
|
||||
break;
|
||||
case WXK_DOWN:
|
||||
if (selected_item + 1 < itemCount()) {
|
||||
while (selected_item + 1 < itemCount()) {
|
||||
selected_item += 1;
|
||||
Refresh(false);
|
||||
if (itemEnabled(selected_item)) {
|
||||
Refresh(false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
selected_item = old_sel;
|
||||
break;
|
||||
case WXK_RETURN:
|
||||
if (!showSubMenu()) {
|
||||
@@ -351,6 +366,7 @@ bool DropDownList::onCharInParent(wxKeyEvent& ev) {
|
||||
size_t count = itemCount();
|
||||
for (size_t i = 0 ; i < count ; ++i) {
|
||||
size_t index = (si + i) % count;
|
||||
if (!itemEnabled(index)) continue;
|
||||
String c = itemText(index);
|
||||
#ifdef UNICODE
|
||||
if (!c.empty() && toUpper(c.GetChar(0)) == toUpper(ev.GetUnicodeKey())) {
|
||||
|
||||
@@ -40,6 +40,10 @@ class DropDownList : public wxPopupWindow {
|
||||
bool onMouseInParent(wxMouseEvent&, bool open_in_place);
|
||||
|
||||
protected:
|
||||
|
||||
/// Prepare for showing the list
|
||||
virtual void onShow() {}
|
||||
|
||||
// --------------------------------------------------- : Selection
|
||||
static const size_t NO_SELECTION = (size_t)-1;
|
||||
|
||||
@@ -59,6 +63,8 @@ class DropDownList : public wxPopupWindow {
|
||||
virtual bool lineBelow(size_t item) const { return false; }
|
||||
/// Should the item be highlighted?
|
||||
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
|
||||
virtual DropDownList* submenu(size_t item) const { return nullptr; }
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ NewSetWindow::NewSetWindow(Window* parent)
|
||||
void NewSetWindow::onGameSelect(wxCommandEvent&) {
|
||||
wxBusyCursor wait;
|
||||
GameP game = game_list->getSelection<Game>();
|
||||
handle_pending_errors();
|
||||
settings.default_game = game->name();
|
||||
GameSettings& gs = settings.gameSettingsFor(*game);
|
||||
stylesheet_list->showData<StyleSheet>(game->name() + _("-*"));
|
||||
@@ -78,6 +79,7 @@ void NewSetWindow::onStyleSheetSelect(wxCommandEvent&) {
|
||||
// store this as default selection
|
||||
GameP game = game_list ->getSelection<Game>();
|
||||
StyleSheetP stylesheet = stylesheet_list->getSelection<StyleSheet>();
|
||||
handle_pending_errors();
|
||||
GameSettings& gs = settings.gameSettingsFor(*game);
|
||||
gs.default_stylesheet = stylesheet->name();
|
||||
UpdateWindowUI(wxUPDATE_UI_RECURSE);
|
||||
@@ -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)
|
||||
EVT_GALLERY_SELECT (ID_GAME_LIST, NewSetWindow::onGameSelect)
|
||||
EVT_GALLERY_SELECT (ID_STYLESHEET_LIST, NewSetWindow::onStyleSheetSelect)
|
||||
EVT_GALLERY_ACTIVATE(ID_STYLESHEET_LIST, NewSetWindow::onStyleSheetActivate)
|
||||
EVT_BUTTON (wxID_OK, NewSetWindow::OnOK)
|
||||
EVT_UPDATE_UI (wxID_ANY, NewSetWindow::onUpdateUI)
|
||||
EVT_IDLE ( NewSetWindow::onIdle)
|
||||
END_EVENT_TABLE ()
|
||||
|
||||
@@ -42,10 +42,11 @@ class NewSetWindow : public wxDialog {
|
||||
|
||||
void onStyleSheetSelect (wxCommandEvent&);
|
||||
void onStyleSheetActivate(wxCommandEvent&);
|
||||
|
||||
|
||||
virtual void OnOK(wxCommandEvent&);
|
||||
|
||||
void onUpdateUI(wxUpdateUIEvent&);
|
||||
void onIdle(wxIdleEvent&);
|
||||
|
||||
// we are done, close the window
|
||||
void done();
|
||||
|
||||
+2
-2
@@ -202,7 +202,7 @@ void draw_drop_down_arrow(Window* win, DC& dc, const wxRect& rect, bool active)
|
||||
, 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__)
|
||||
// TODO: Windows version?
|
||||
#endif
|
||||
@@ -210,7 +210,7 @@ void draw_checkbox(Window* win, DC& dc, const wxRect& rect, bool checked) {
|
||||
if (checked) {
|
||||
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.DrawRectangle(rect.x, rect.y, rect.width, rect.height);
|
||||
}
|
||||
|
||||
+1
-1
@@ -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);
|
||||
|
||||
/// 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
|
||||
#endif
|
||||
|
||||
@@ -106,6 +106,14 @@ DropDownChoiceListBase::DropDownChoiceListBase
|
||||
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 {
|
||||
return group->choices.size() + hasDefault();
|
||||
}
|
||||
@@ -129,7 +137,10 @@ String DropDownChoiceListBase::itemText(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 {
|
||||
if (isDefault(item)) return nullptr;
|
||||
@@ -157,7 +168,7 @@ void DropDownChoiceListBase::drawIcon(DC& dc, int x, int y, size_t item, bool se
|
||||
}
|
||||
// draw image
|
||||
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
|
||||
: DropDownChoiceListBase(parent, is_submenu, cve, group)
|
||||
{}
|
||||
|
||||
void DropDownChoiceList::onShow() {
|
||||
DropDownChoiceListBase::onShow();
|
||||
// we need thumbnail images soon
|
||||
generateThumbnailImages();
|
||||
}
|
||||
|
||||
void DropDownChoiceList::select(size_t item) {
|
||||
if (isFieldDefault(item)) {
|
||||
dynamic_cast<ChoiceValueEditor&>(cve).change( Defaultable<String>() );
|
||||
@@ -206,8 +223,6 @@ void DropDownChoiceList::select(size_t item) {
|
||||
}
|
||||
|
||||
size_t DropDownChoiceList::selection() const {
|
||||
// we need thumbnail images soon
|
||||
const_cast<DropDownChoiceList*>(this)->generateThumbnailImages();
|
||||
// selected item
|
||||
const Defaultable<String>& value = dynamic_cast<ChoiceValueEditor&>(cve).value().value();
|
||||
int id = field().choices->choiceId(value);
|
||||
|
||||
@@ -48,9 +48,11 @@ class DropDownChoiceListBase : public DropDownList {
|
||||
public:
|
||||
DropDownChoiceListBase(Window* parent, bool is_submenu, ValueViewer& cve, ChoiceField::ChoiceP group);
|
||||
|
||||
protected:
|
||||
protected:
|
||||
virtual void onShow();
|
||||
virtual size_t itemCount() const;
|
||||
virtual bool lineBelow(size_t item) const;
|
||||
virtual bool itemEnabled(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 DropDownList* submenu(size_t item) const;
|
||||
@@ -92,6 +94,7 @@ class DropDownChoiceList : public DropDownChoiceListBase {
|
||||
DropDownChoiceList(Window* parent, bool is_submenu, ValueViewer& cve, ChoiceField::ChoiceP group);
|
||||
|
||||
protected:
|
||||
virtual void onShow();
|
||||
virtual void select(size_t item);
|
||||
virtual size_t selection() const;
|
||||
virtual DropDownList* createSubMenu(ChoiceField::ChoiceP group) const;
|
||||
|
||||
@@ -33,27 +33,31 @@ DropDownMultipleChoiceList::DropDownMultipleChoiceList
|
||||
}
|
||||
|
||||
void DropDownMultipleChoiceList::select(size_t item) {
|
||||
MultipleChoiceValueEditor& mcve = dynamic_cast<MultipleChoiceValueEditor&>(cve);
|
||||
if (isFieldDefault(item)) {
|
||||
// should not happen
|
||||
// make default
|
||||
mcve.getSet().actions.add(value_action(mcve.valueP(), Defaultable<String>()));
|
||||
} else {
|
||||
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 {
|
||||
// is this item active?
|
||||
// is this item active/checked?
|
||||
bool active = false;
|
||||
if (!isFieldDefault(item)) {
|
||||
ChoiceField::ChoiceP choice = getChoice(item);
|
||||
active = dynamic_cast<MultipleChoiceValueEditor&>(cve).active[choice->first_id];
|
||||
} else {
|
||||
active = dynamic_cast<MultipleChoiceValueEditor&>(cve).value().value.isDefault();
|
||||
}
|
||||
// draw checkbox
|
||||
dc.SetPen(*wxTRANSPARENT_PEN);
|
||||
dc.SetBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
|
||||
dc.DrawRectangle(x,y,16,16);
|
||||
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
|
||||
DropDownChoiceListBase::drawIcon(dc, x + 16, y, item, selected);
|
||||
}
|
||||
|
||||
@@ -32,11 +32,54 @@ void MultipleChoiceValueViewer::draw(RotatedDC& dc) {
|
||||
if (active) select_it++;
|
||||
drawChoice(dc, pos, choice, active);
|
||||
}
|
||||
} else {
|
||||
} else if (style().render_style & RENDER_LIST) {
|
||||
// render only selected choices
|
||||
FOR_EACH(choice, selected) {
|
||||
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 @@
|
||||
#include <data/game.hpp>
|
||||
#include <data/field/text.hpp>
|
||||
#include <data/field/choice.hpp>
|
||||
#include <data/field/multiple_choice.hpp>
|
||||
|
||||
DECLARE_TYPEOF_COLLECTION(FieldP);
|
||||
DECLARE_TYPEOF_COLLECTION(TextValue*);
|
||||
@@ -140,7 +141,7 @@ SCRIPT_FUNCTION(primary_choice) {
|
||||
SCRIPT_PARAM(ValueP,input);
|
||||
ChoiceValueP value = dynamic_pointer_cast<ChoiceValue>(input);
|
||||
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
|
||||
int id = value->field().choices->choiceId(value->value);
|
||||
@@ -154,10 +155,46 @@ SCRIPT_FUNCTION(primary_choice) {
|
||||
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
|
||||
|
||||
void init_script_editor_functions(Context& ctx) {
|
||||
ctx.setVariable(_("forward editor"), script_combined_editor); // combatability
|
||||
ctx.setVariable(_("combined editor"), script_combined_editor);
|
||||
ctx.setVariable(_("primary choice"), script_primary_choice);
|
||||
ctx.setVariable(_("chosen"), script_chosen);
|
||||
ctx.setVariable(_("require choice"), script_require_choice);
|
||||
}
|
||||
|
||||
@@ -567,7 +567,7 @@ void parseOper(TokenIterator& input, Script& script, Precedence minPrec, Instruc
|
||||
t = input.peek();
|
||||
}
|
||||
}
|
||||
input.read(); // skip the )
|
||||
expectToken(input, _(")"));
|
||||
// generate instruction
|
||||
script.addInstruction(I_CALL, (unsigned int)arguments.size());
|
||||
FOR_EACH(arg,arguments) {
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
|
||||
Alignment from_string(const String&);
|
||||
|
||||
DECLARE_TYPEOF_COLLECTION(ScriptParseError);
|
||||
|
||||
// ----------------------------------------------------------------------------- : Store
|
||||
|
||||
void store(const ScriptValueP& val, String& var) { var = val->toString(); }
|
||||
@@ -43,10 +45,16 @@ ScriptValueP OptionalScript::invoke(Context& ctx, bool open_scope) const {
|
||||
}
|
||||
|
||||
void OptionalScript::parse(Reader& reader, bool string_mode) {
|
||||
try {
|
||||
script = ::parse(unparsed, string_mode);
|
||||
} catch (const ParseError& e) {
|
||||
reader.warning(e.what());
|
||||
vector<ScriptParseError> errors;
|
||||
script = ::parse(unparsed, string_mode, errors);
|
||||
// show parse errors as warnings
|
||||
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) {
|
||||
} else if (s.script.unparsed.find_first_of('{') != String::npos) {
|
||||
s.script.parse(*this, true);
|
||||
} else {
|
||||
unhandle();
|
||||
handle(s.value);
|
||||
}
|
||||
}
|
||||
|
||||
+102
-30
@@ -14,19 +14,19 @@
|
||||
// ----------------------------------------------------------------------------- : Reader
|
||||
|
||||
Reader::Reader(const InputStreamP& input, const String& filename, bool ignore_invalid)
|
||||
: indent(0), expected_indent(0), just_opened(false)
|
||||
, filename(filename), line_number(0)
|
||||
: indent(0), expected_indent(0), state(OUTSIDE)
|
||||
, filename(filename), line_number(0), previous_line_number(0)
|
||||
, ignore_invalid(ignore_invalid)
|
||||
, input(input), stream(*input)
|
||||
, input(input)
|
||||
{
|
||||
moveNext();
|
||||
handleAppVersion();
|
||||
}
|
||||
|
||||
Reader::Reader(const String& filename)
|
||||
: indent(0), expected_indent(0), just_opened(false)
|
||||
, filename(filename), line_number(0)
|
||||
, input(packages.openFileFromPackage(filename)), stream(*input)
|
||||
: indent(0), expected_indent(0), state(OUTSIDE)
|
||||
, filename(filename), line_number(0), previous_line_number(0)
|
||||
, input(packages.openFileFromPackage(filename))
|
||||
{
|
||||
moveNext();
|
||||
handleAppVersion();
|
||||
@@ -48,8 +48,10 @@ void Reader::handleAppVersion() {
|
||||
}
|
||||
}
|
||||
|
||||
void Reader::warning(const String& msg) {
|
||||
warnings += String(_("\nOn line ")) << line_number << _(": \t") << msg;
|
||||
void Reader::warning(const String& msg, int line_number_delta, bool warn_on_previous_line) {
|
||||
warnings += String(_("\nOn line "))
|
||||
<< ((warn_on_previous_line ? previous_line_number : line_number) + line_number_delta)
|
||||
<< _(": \t") << msg;
|
||||
}
|
||||
|
||||
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) {
|
||||
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 (cannocial_name_compare(key, name)) {
|
||||
just_opened = true;
|
||||
state = ENTERED;
|
||||
expected_indent += 1; // the indent inside the block must be at least this much
|
||||
return true;
|
||||
} else {
|
||||
@@ -74,20 +84,21 @@ bool Reader::enterBlock(const Char* name) {
|
||||
void Reader::exitBlock() {
|
||||
assert(expected_indent > 0);
|
||||
expected_indent -= 1;
|
||||
multi_line_str.clear();
|
||||
if (just_opened) moveNext(); // leave this key
|
||||
assert(state != UNHANDLED);
|
||||
previous_value.clear();
|
||||
if (state == ENTERED) moveNext(); // leave this key
|
||||
// Dump the remainder of the block
|
||||
// TODO: issue warnings?
|
||||
while (indent > expected_indent) {
|
||||
moveNext();
|
||||
}
|
||||
handled = true;
|
||||
state = HANDLED;
|
||||
}
|
||||
|
||||
void Reader::moveNext() {
|
||||
just_opened = false;
|
||||
previous_line_number = line_number;
|
||||
state = HANDLED;
|
||||
key.clear();
|
||||
multi_line_str.clear();
|
||||
indent = -1; // if no line is read it never has the expected indentation
|
||||
// repeat until we have a good line
|
||||
while (key.empty() && !input->Eof()) {
|
||||
@@ -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) {
|
||||
// fix UTF8 in ascii builds; skip BOM
|
||||
line = decodeUTF8BOM(stream.ReadLine());
|
||||
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
|
||||
indent = 0;
|
||||
while ((UInt)indent < line.size() && line.GetChar(indent) == _('\t')) {
|
||||
@@ -118,7 +174,7 @@ void Reader::readLine(bool in_string) {
|
||||
}
|
||||
key = line.substr(indent, pos - indent);
|
||||
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
|
||||
while (starts_with(key, _(" "))) {
|
||||
key = key.substr(8);
|
||||
@@ -146,7 +202,7 @@ void Reader::unknownKey() {
|
||||
} else if (it->second.end_version <= file_app_version) {
|
||||
// alias not used for this version, use in warning
|
||||
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 {
|
||||
moveNext();
|
||||
} while (indent > expected_indent);
|
||||
@@ -159,7 +215,7 @@ void Reader::unknownKey() {
|
||||
}
|
||||
}
|
||||
if (indent >= expected_indent) {
|
||||
warning(_("Unexpected key: '") + key + _("'"));
|
||||
warning(_("Unexpected key: '") + key + _("'"), 0, false);
|
||||
do {
|
||||
moveNext();
|
||||
} while (indent > expected_indent);
|
||||
@@ -169,23 +225,31 @@ void Reader::unknownKey() {
|
||||
|
||||
// ----------------------------------------------------------------------------- : Handling basic types
|
||||
|
||||
void Reader::unhandle() {
|
||||
assert(state == HANDLED);
|
||||
state = UNHANDLED;
|
||||
}
|
||||
|
||||
const String& Reader::getValue() {
|
||||
handled = true;
|
||||
if (!multi_line_str.empty()) {
|
||||
return multi_line_str;
|
||||
assert(state != HANDLED); // don't try to handle things twice
|
||||
if (state == UNHANDLED) {
|
||||
state = HANDLED;
|
||||
return previous_value;
|
||||
} else if (value.empty()) {
|
||||
// a multiline string
|
||||
previous_value.clear();
|
||||
bool first = true;
|
||||
// read all lines that are indented enough
|
||||
readLine();
|
||||
previous_line_number = line_number;
|
||||
while (indent >= expected_indent && !input->Eof()) {
|
||||
if (!first) multi_line_str += _('\n');
|
||||
if (!first) previous_value += _('\n');
|
||||
first = false;
|
||||
multi_line_str += line.substr(expected_indent); // strip expected indent
|
||||
previous_value += line.substr(expected_indent); // strip expected indent
|
||||
readLine(true);
|
||||
}
|
||||
// moveNext(), but without emptying multi_line_str
|
||||
just_opened = false;
|
||||
// moveNext(), but without the initial readLine()
|
||||
state = HANDLED;
|
||||
while (key.empty() && !input->Eof()) {
|
||||
readLine();
|
||||
}
|
||||
@@ -194,9 +258,16 @@ const String& Reader::getValue() {
|
||||
line_number += 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 {
|
||||
return value;
|
||||
previous_value = value;
|
||||
moveNext();
|
||||
return previous_value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,7 +288,8 @@ template <> void Reader::handle(double& d) {
|
||||
getValue().ToDouble(&d);
|
||||
}
|
||||
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
|
||||
|
||||
|
||||
+23
-24
@@ -11,7 +11,6 @@
|
||||
|
||||
#include <util/prec.hpp>
|
||||
#include <util/version.hpp>
|
||||
#include <wx/txtstrm.h>
|
||||
|
||||
template <typename T> class Defaultable;
|
||||
template <typename T> class Scriptable;
|
||||
@@ -57,7 +56,7 @@ class Reader {
|
||||
void handleAppVersion();
|
||||
|
||||
/// 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
|
||||
void showWarnings();
|
||||
|
||||
@@ -66,11 +65,9 @@ class Reader {
|
||||
template <typename T>
|
||||
void handle_greedy(T& object) {
|
||||
do {
|
||||
// UInt l = line_number;
|
||||
handled = false;
|
||||
handle(object);
|
||||
// if (l == line_number && !handled) unknownKey(object);
|
||||
if (!handled) unknownKey(object);
|
||||
if (state != HANDLED) unknownKey(object);
|
||||
state = OUTSIDE;
|
||||
} while (indent >= expected_indent);
|
||||
}
|
||||
|
||||
@@ -106,6 +103,9 @@ class Reader {
|
||||
void handle(GameP&);
|
||||
void handle(StyleSheetP&);
|
||||
|
||||
/// Indicate that the last value from getValue() was not handled, allowing it to be handled again
|
||||
void unhandle();
|
||||
|
||||
// --------------------------------------------------- : Data
|
||||
/// App version this file was made with
|
||||
Version file_app_version;
|
||||
@@ -114,16 +114,19 @@ class Reader {
|
||||
String line;
|
||||
/// The key and value of the last line we read
|
||||
String key, value;
|
||||
/// A string spanning multiple lines
|
||||
String multi_line_str;
|
||||
/// Has the current line been handled?
|
||||
bool handled;
|
||||
/// Value of the *previous* line, only valid in state==HANDLED
|
||||
String previous_value;
|
||||
/// Indentation of the last line we read
|
||||
int indent;
|
||||
/// Indentation of the block we are in
|
||||
int expected_indent;
|
||||
/// Did we just open a block (i.e. not read any more lines of it)?
|
||||
bool just_opened;
|
||||
/// State of the reader
|
||||
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
|
||||
struct Alias {
|
||||
String new_key;
|
||||
@@ -136,19 +139,21 @@ class Reader {
|
||||
|
||||
/// Filename for error messages
|
||||
String filename;
|
||||
/// Line number for error messages
|
||||
UInt line_number;
|
||||
/// Line number of the current line for error messages
|
||||
int line_number;
|
||||
/// Line number of the previous_line
|
||||
int previous_line_number;
|
||||
/// Input stream we are reading from
|
||||
InputStreamP input;
|
||||
/// Text stream wrapping the input stream
|
||||
wxTextInputStream stream;
|
||||
/// Accumulated warning messages
|
||||
String warnings;
|
||||
|
||||
// --------------------------------------------------- : 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);
|
||||
/// Enter any block, no matter what the key
|
||||
bool enterAnyBlock();
|
||||
/// Leave the block we are in
|
||||
void exitBlock();
|
||||
|
||||
@@ -208,13 +213,7 @@ void Reader::handle(intrusive_ptr<T>& pointer) {
|
||||
|
||||
template <typename V>
|
||||
void Reader::handle(map<String, V>& m) {
|
||||
while (true) {
|
||||
// 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
|
||||
while (enterAnyBlock()) {
|
||||
handle_greedy(m[key]);
|
||||
exitBlock();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user