Commit c0d4b975 authored by twanvl's avatar twanvl

Added 'insert symbol' menu for SymbolFonts;

Added scriptable 'enabled' to symbols in symbol font, used instead of scripted filenames. This means changing the tap symbol style now works;
Added localisation for games, stylesheets and symbolfonts;
Warnings from Reader are now shown onIdle;
parent 5602cec0
......@@ -305,6 +305,38 @@ game:
# Set info
# descriptions/help text
#stylesheet:
# magic-new:
#
\ No newline at end of file
stylesheet:
magic-new:
symbol font:
magic-mana-small:
menu item T: &Tap symbol T
menu item W: &White mana W
menu item U: Bl&ue mana U
menu item B: &Black mana B
menu item R: &Red mana R
menu item G: &Green mana G
menu item S: &Snow mana S
menu item X: Variable mana &X X
menu item Y: Variable mana &Y Y
menu item Z: Variable mana &Z Z
menu item colorless: &Colorless mana...
menu item half: &Half mana
menu item |W: &White |W
menu item |U: Bl&ue |U
menu item |B: &Black |B
menu item |R: &Red |R
menu item |G: &Green |G
menu item |S: &Snow |S
menu item 1/2: &Colorless 1/2
menu item hybrid: H&ybrid mana (two color)
menu item W/U: White/Blue mana W/U
menu item U/B: Blue/Black mana U/B
menu item B/R: Black/Ref mana B/R
menu item R/G: Red/Green mana R/G
menu item G/W: Green/White mana G/W
menu item W/B: White/Black mana W/B
menu item U/R: Blue/Red mana U/R
menu item B/G: Black/Green mana B/G
menu item R/W: Red/White mana R/W
menu item G/U: Green/blue mana G/U
mse version: 0.2.7
mse version: 0.3.0
# Symbol font in the 'popup' style, used for casting costs on modern cards
image font size: 135
......@@ -7,7 +7,15 @@ symbol:
image: mana_circle.png
symbol:
code: T
image: script: mana_t()
image: mana_t_older.png
enabled: { mana_t() == "older" }
symbol:
code: T
image: mana_t_old.png
enabled: { mana_t() == "old" }
symbol:
code: T
image: mana_t.png
symbol:
code: W/U
image: mana_wu.png
......
mse version: 0.2.7
mse version: 0.3.0
# Symbol font in the normal, flat, style, used for text boxes and on old style cards
# Note:
# Define small_mana_t:="mana_t(_old)?.png" in the init script of the style
# Define mana_t := {"new|old|older"} in the init script of the style
#
# So for example:
#
#init script:
# small_mana_t := "mana_t.png"
# mana_t := {"new"}
image font size: 135
horizontal space: 2
......@@ -14,7 +14,15 @@ symbol:
image: mana_circle.png
symbol:
code: T
image: { mana_t() }
image: mana_t_older.png
enabled: { mana_t() == "older" }
symbol:
code: T
image: mana_t_old.png
enabled: { mana_t() == "old" }
symbol:
code: T
image: mana_t.png
symbol:
code: W/U
image: mana_wu.png
......@@ -102,4 +110,50 @@ text font:
text margin left: 3
text margin right: 2
text margin top: -1
text margin bottom: -1
\ No newline at end of file
text margin bottom: -1
##############################################################
# Insert-symbol menu
insert symbol menu:
item: T
item:
type: line
item: X
item: Y
item: Z
item:
type: custom
name: colorless
item:
type: line
item: W
item: U
item: B
item: R
item: G
item: S
item:
type: line
item:
name: half
item: 1/2
item: |W
item: |U
item: |B
item: |R
item: |G
item: |S
item:
name: hybrid
item: W/U
item: U/B
item: B/R
item: R/G
item: G/W
item:
type: line
item: W/B
item: U/R
item: B/G
item: R/W
item: G/U
......@@ -22,7 +22,7 @@ init script:
land_template := { "acard.jpg" }
# Use the normal tap symbol
mana_t := { "mana_t.png" }
mana_t := { "new" }
# Does the card have a color that requires a white font for copyright/artist?
white_font_colors := filter_rule(match:"^(hybrid )?black|^land")
......
......@@ -26,9 +26,9 @@ init script:
# Use the normal tap symbol
mana_t := {
if styling.tap_symbol == "old" then "mana_t_old.png"
else if styling.tap_symbol == "diagonal T" then "mana_t_older.png"
else "mana_t.png"
if styling.tap_symbol == "old" then "old"
else if styling.tap_symbol == "diagonal T" then "older"
else "new"
}
# Does the card have a color that requires a white font for copyright/artist?
......
......@@ -23,8 +23,8 @@ init script:
# Horizontal 5 color blends are not supported
card_hybrid_5b := card_hybrid_5
# Use the normal tap symbol
mana_t := { "mana_t_old.png" }
# Use the old tap symbol
mana_t := { "old" }
# Does the card have a color that requires a black font for copyright/artist?
black_font_colors := filter_rule(match:"^(hybrid 2 color)?white")
......
......@@ -23,8 +23,8 @@ init script:
# Horizontal 5 color blends are not supported
card_hybrid_5b := card_hybrid_5
# Use the normal tap symbol
mana_t := { "mana_t_old.png" }
# Use the old tap symbol
mana_t := { "old" }
# Does the card have a color that requires a black font for copyright/artist?
black_font_colors := filter_rule(match:"^(hybrid 2 color)?white")
......
......@@ -25,7 +25,7 @@ init script:
pt_template := { input + "pt.jpg" }
# Use the normal tap symbol
small_mana_t := "mana_t.png"
mana_t := { "new" }
# Does the card have a color that requires a white font for copyright/artist?
white_font_colors := filter_rule(match:"^(hybrid 2 color)?black|^land")
......
......@@ -9,8 +9,8 @@ icon: card-sample.png
############################################################## Extra scripts
init script:
# Use the normal tap symbol
small_mana_t := "mana_t.png"
# Use the old tap symbol
mana_t := { "old" }
############################################################## Set info fields
info style:
......
......@@ -21,8 +21,7 @@ IMPLEMENT_REFLECTION(KeywordMode) {
REFLECT(description);
}
IMPLEMENT_REFLECTION(KeywordExpansion) {
REFLECT(before);
REFLECT(after);
REFLECT(match);
REFLECT(reminder);
}
......@@ -36,15 +35,17 @@ void read_compat(Reader& tag, Keyword* k) {
if (!separator.empty() || !parameter.empty() || !reminder.empty()) {
// old style keyword declaration, no separate expansion
KeywordExpansionP e(new KeywordExpansion);
e->match = k->keyword;
size_t start = separator.find_first_of('[');
size_t end = separator.find_first_of(']');
if (start != String::npos && end != String::npos) {
e->after += separator.substr(start + 1, end - start - 1);
e->match += separator.substr(start + 1, end - start - 1);
}
if (!parameter.empty()) {
e->after += _("<param>") + parameter + _("</param>");
e->match += _("<param>") + parameter + _("</param>");
}
e->reminder.set(reminder);
k->expansions.push_back(e);
}
}
......
......@@ -46,11 +46,11 @@ class KeywordMode {
/// A way to use a keyword
class KeywordExpansion {
public:
String before; ///< Components before the keyword: parameters and separators (tagged string)
String after; ///< Components after the keyword: parameters and separators
String match; ///< String to match, <param> tags are used for parameters
vector<KeywordParamP> parameters; ///< The types of parameters
wxRegEx splitter; ///< Regular expression to split/match the components, automatically generated
// wxRegEx splitter; ///< Regular expression to split/match the components, automatically generated
StringScript reminder; ///< Reminder text of the keyword
String mode; ///< Mode of use, can be used by scripts (only gives the name). Default is the mode of the Keyword.
DECLARE_REFLECTION();
};
......
......@@ -7,6 +7,9 @@
// ----------------------------------------------------------------------------- : Includes
#include <data/locale.hpp>
#include <data/game.hpp>
#include <data/stylesheet.hpp>
#include <data/symbol_font.hpp>
#include <util/io/package_manager.hpp>
#include <script/to_value.hpp>
......@@ -32,19 +35,64 @@ IMPLEMENT_REFLECTION(Locale) {
REFLECT_N("error", translations[LOCALE_CAT_ERROR]);
REFLECT_N("type", translations[LOCALE_CAT_TYPE]);
REFLECT_N("game", game_translations);
REFLECT_N("stylesheet", stylesheet_translations);
REFLECT_N("symbol font", symbol_font_translations);
}
IMPLEMENT_REFLECTION_NAMELESS(GameLocale) {
IMPLEMENT_REFLECTION_NAMELESS(SubLocale) {
REFLECT_NAMELESS(translations);
}
// ----------------------------------------------------------------------------- : Translation
String SubLocale::tr(const String& key) {
map<String,String>::const_iterator it = translations.find(key);
if (it == translations.end()) {
return _("missing:") + key;
} else {
return it->second;
}
}
String SubLocale::tr(const String& key, const String& def) {
map<String,String>::const_iterator it = translations.find(key);
if (it == translations.end()) {
return def;
} else {
return it->second;
}
}
// from util/locale.hpp
String tr(LocaleCategory cat, const String& key) {
if (!the_locale) return key; // no locale loaded (yet)
map<String,String>::const_iterator it = the_locale->translations[cat].find(key);
if (it == the_locale->translations[cat].end()) return _("missing:") + key;
return it->second;
return the_locale->translations[cat].tr(key);
}
String tr(const Game& g, const String& key) {
if (!the_locale) return key; // no locale loaded (yet)
return the_locale->game_translations[g.name()]->tr(key);
}
String tr(const StyleSheet& s, const String& key) {
if (!the_locale) return key; // no locale loaded (yet)
return the_locale->stylesheet_translations[s.name()]->tr(key);
}
String tr(const SymbolFont& f, const String& key) {
if (!the_locale) return key; // no locale loaded (yet)
return the_locale->symbol_font_translations[f.name()]->tr(key);
}
String tr(const Game& g, const String& key, const String& def) {
if (!the_locale) return key; // no locale loaded (yet)
return the_locale->game_translations[g.name()]->tr(key, def);
}
String tr(const StyleSheet& s, const String& key, const String& def) {
if (!the_locale) return key; // no locale loaded (yet)
return the_locale->stylesheet_translations[s.name()]->tr(key, def);
}
String tr(const SymbolFont& f, const String& key, const String& def) {
if (!the_locale) return key; // no locale loaded (yet)
return the_locale->symbol_font_translations[f.name()]->tr(key, def);
}
......@@ -15,14 +15,20 @@
#include <util/io/package.hpp>
DECLARE_POINTER_TYPE(Locale);
DECLARE_POINTER_TYPE(GameLocale);
DECLARE_POINTER_TYPE(SubLocale);
// ----------------------------------------------------------------------------- : Locale class
/// Translations of the texts of a game
class GameLocale {
/// Translations of the texts of a game/stylesheet/symbolfont
class SubLocale {
public:
map<String,String> translations;
/// Translate a key
String tr(const String& key);
/// Translate a key with a default value
String tr(const String& key, const String& def);
DECLARE_REFLECTION();
};
......@@ -30,9 +36,13 @@ class GameLocale {
class Locale : public Packaged {
public:
/// Translations of UI strings in each category
map<String,String> translations[LOCALE_CAT_MAX];
/// Translations of game specific texts, by game name
map<String,GameLocaleP> game_translations;
SubLocale translations[LOCALE_CAT_MAX];
/// Translations of Game specific texts, by game name
map<String,SubLocaleP> game_translations;
/// Translations of StyleSheet specific texts, by stylesheet name
map<String,SubLocaleP> stylesheet_translations;
/// Translations of SymbolFont specific texts, by symbol font name
map<String,SubLocaleP> symbol_font_translations;
/// Open a locale with the given name
static LocaleP byName(const String& name);
......
......@@ -10,12 +10,14 @@
#include <util/dynamic_arg.hpp>
#include <util/io/package_manager.hpp>
#include <util/rotation.hpp>
#include <util/error.hpp>
#include <util/window_id.hpp>
#include <render/text/element.hpp> // fot CharInfo
#include <script/image.hpp>
#include <util/error.hpp>
DECLARE_TYPEOF_COLLECTION(SymbolFont::DrawableSymbol);
DECLARE_TYPEOF_COLLECTION(SymbolInFontP);
DECLARE_TYPEOF_COLLECTION(InsertSymbolMenuP);
// ----------------------------------------------------------------------------- : SymbolFont
......@@ -31,8 +33,13 @@ SymbolFont::SymbolFont()
, text_margin_top(0), text_margin_bottom(0)
, text_alignment(ALIGN_MIDDLE_CENTER)
, merge_numbers(false)
, processed_insert_symbol_menu(nullptr)
{}
SymbolFont::~SymbolFont() {
delete processed_insert_symbol_menu;
}
String SymbolFont::typeNameStatic() { return _("symbol-font"); }
String SymbolFont::typeName() const { return _("symbol-font"); }
......@@ -57,6 +64,7 @@ IMPLEMENT_REFLECTION(SymbolFont) {
REFLECT(text_margin_top);
REFLECT(text_margin_bottom);
REFLECT(text_alignment);
REFLECT(insert_symbol_menu);
}
// ----------------------------------------------------------------------------- : SymbolInFont
......@@ -69,15 +77,21 @@ class SymbolInFont {
/// Get a shrunk, zoomed bitmap
Bitmap getBitmap(Context& ctx, Package& pkg, double size);
/// Get a bitmap with the given size
Bitmap getBitmap(Context& ctx, Package& pkg, wxSize size);
/// Size of a (zoomed) bitmap
/** This is the size of the resulting image, it does NOT convert back to internal coordinates */
RealSize size(Context& ctx, Package& pkg, double size);
String code; ///< Code for this symbol
void update(Context& ctx);
String code; ///< Code for this symbol
Scriptable<bool> enabled; ///< Is this symbol enabled?
private:
ScriptableImage image; ///< The image for this symbol
double img_size; ///< Font size used by the image
wxSize actual_size; ///< Actual image size, only known after loading the image
ScriptableImage image; ///< The image for this symbol
double img_size; ///< Font size used by the image
wxSize actual_size; ///< Actual image size, only known after loading the image
/// Cached bitmaps for different sizes
map<double, Bitmap> bitmaps;
......@@ -86,6 +100,7 @@ class SymbolInFont {
SymbolInFont::SymbolInFont()
: actual_size(0,0)
, enabled(true)
{
assert(symbol_font_for_reading());
img_size = symbol_font_for_reading()->img_size;
......@@ -112,6 +127,18 @@ Bitmap SymbolInFont::getBitmap(Context& ctx, Package& pkg, double size) {
}
return bmp;
}
Bitmap SymbolInFont::getBitmap(Context& ctx, Package& pkg, wxSize size) {
// generate new bitmap
if (!image) {
throw Error(_("No image specified for symbol with code '") + code + _("' in symbol font."));
}
Image img = image.generate(ctx, pkg)->image;
actual_size = wxSize(img.GetWidth(), img.GetHeight());
// scale to match expected size
Image resampled_image(size.GetWidth(), size.GetHeight(), false);
resample_preserve_aspect(img, resampled_image);
return Bitmap(resampled_image);
}
RealSize SymbolInFont::size(Context& ctx, Package& pkg, double size) {
if (actual_size.GetWidth() == 0) {
......@@ -121,9 +148,14 @@ RealSize SymbolInFont::size(Context& ctx, Package& pkg, double size) {
return wxSize(actual_size * size / img_size);
}
void SymbolInFont::update(Context& ctx) {
enabled.update(ctx);
}
IMPLEMENT_REFLECTION(SymbolInFont) {
REFLECT(code);
REFLECT(image);
REFLECT(enabled);
REFLECT_N("image font size", img_size);
}
......@@ -139,7 +171,11 @@ class SymbolFont::DrawableSymbol {
SymbolInFont* symbol; ///< Symbol to draw, if nullptr, use the default symbol and draw the text
};
void SymbolFont::split(const String& text, SplitSymbols& out) const {
void SymbolFont::split(const String& text, Context& ctx, SplitSymbols& out) const {
// update all symbol-in-fonts
FOR_EACH_CONST(sym, symbols) {
sym->update(ctx);
}
// read a single symbol until we are done with the text
for (size_t pos = 0 ; pos < text.size() ; ) {
// 1. check merged numbers
......@@ -154,7 +190,7 @@ void SymbolFont::split(const String& text, SplitSymbols& out) const {
}
// 2. check symbol list
FOR_EACH_CONST(sym, symbols) {
if (!sym->code.empty() && is_substr(text, pos, sym->code)) { // symbol matches
if (!sym->code.empty() && sym->enabled && is_substr(text, pos, sym->code)) { // symbol matches
out.push_back(DrawableSymbol(sym->code, sym.get()));
pos += sym->code.size();
goto next_symbol; // continue two levels
......@@ -178,7 +214,7 @@ SymbolInFont* SymbolFont::defaultSymbol() const {
void SymbolFont::draw(RotatedDC& dc, Context& ctx, const RealRect& rect, double font_size, const Alignment& align, const String& text) {
SplitSymbols symbols;
split(text, symbols);
split(text, ctx, symbols);
draw(dc, ctx, rect, font_size, align, symbols);
}
......@@ -253,7 +289,7 @@ void SymbolFont::drawWithText(RotatedDC& dc, Context& ctx, const RealRect& rect,
void SymbolFont::getCharInfo(RotatedDC& dc, Context& ctx, double font_size, const String& text, vector<CharInfo>& out) {
SplitSymbols symbols;
split(text, symbols);
split(text, ctx, symbols);
getCharInfo(dc, ctx, font_size, symbols, out);
}
......@@ -283,6 +319,133 @@ RealSize SymbolFont::defaultSymbolSize(Context& ctx, double font_size) {
}
// ----------------------------------------------------------------------------- : InsertSymbolMenu
wxMenu* SymbolFont::insertSymbolMenu(Context& ctx) {
if (!processed_insert_symbol_menu && insert_symbol_menu) {
// Make menu
processed_insert_symbol_menu = insert_symbol_menu->makeMenu(ID_INSERT_SYMBOL_MENU_MIN, ctx, *this);
}
return processed_insert_symbol_menu;
}
String SymbolFont::insertSymbolCode(int menu_id) const {
// find item
if (insert_symbol_menu) {
return insert_symbol_menu->getCode(menu_id - ID_INSERT_SYMBOL_MENU_MIN, *this);
} else {
return wxEmptyString;
}
}
InsertSymbolMenu::InsertSymbolMenu()
: type(ITEM_CODE)
{}
int InsertSymbolMenu::size() const {
if (type == ITEM_CODE || type == ITEM_CUSTOM) {
return 1;
} else if (type == ITEM_SUBMENU) {
int count = 0;
FOR_EACH_CONST(i, items) {
count += i->size();
}
return count;
} else {
return 0;
}
}
String InsertSymbolMenu::getCode(int id, const SymbolFont& font) const {
if (type == ITEM_SUBMENU) {
FOR_EACH_CONST(i, items) {
int id2 = id - i->size();
if (id2 < 0) {
return i->getCode(id, font);
}
id = id2;
}
} else if (id == 0 && type == ITEM_CODE) {
return name;
} else if (id == 0 && type == ITEM_CUSTOM) {
String message = tr(font,name,name);
return wxGetTextFromUser(message, message);
}
return wxEmptyString;
}
wxMenu* InsertSymbolMenu::makeMenu(int id, Context& ctx, SymbolFont& font) const {
if (type == ITEM_SUBMENU) {
wxMenu* menu = new wxMenu();
FOR_EACH_CONST(i, items) {
menu->Append(i->makeMenuItem(menu, id, ctx, font));
id += i->size();
}
return menu;
}
return nullptr;
}
wxMenuItem* InsertSymbolMenu::makeMenuItem(wxMenu* parent, int first_id, Context& ctx, SymbolFont& font) const {
if (type == ITEM_SUBMENU) {
wxMenuItem* item = new wxMenuItem(parent, wxID_ANY, tr(font, _("menu item ") + name, name),
wxEmptyString, wxITEM_NORMAL,
makeMenu(first_id, ctx, font));
item->SetBitmap(wxNullBitmap);
return item;
} else if (type == ITEM_LINE) {
wxMenuItem* item = new wxMenuItem(parent, wxID_SEPARATOR);
return item;
} else {
wxMenuItem* item = new wxMenuItem(parent, first_id, tr(font, _("menu item ") + name, name));
// Generate bitmap for use on this item
SymbolInFont* symbol = nullptr;
if (type == ITEM_CUSTOM) {
symbol = font.defaultSymbol();
} else {
FOR_EACH(sym, font.symbols) {
if (!sym->code.empty() && sym->enabled && name == sym->code) {
symbol = sym.get();
break;
}
}
}
if (symbol) {
item->SetBitmap(symbol->getBitmap(ctx, font, wxSize(16,16)));
} else {
item->SetBitmap(wxNullBitmap);
}
return item;
}
}
IMPLEMENT_REFLECTION_ENUM(MenuItemType) {
VALUE_N("code", ITEM_CODE);
VALUE_N("custom", ITEM_CUSTOM);
VALUE_N("line", ITEM_LINE);
VALUE_N("submenu", ITEM_SUBMENU);
}
IMPLEMENT_REFLECTION_NO_GET_MEMBER(InsertSymbolMenu) {
if (!items.empty() || (tag.reading() && tag.isComplex())) {
// complex values are groups
REFLECT(type);
REFLECT(name);
REFLECT(items);
if (!items.empty()) type = ITEM_SUBMENU;
} else {
REFLECT_NAMELESS(name);
}
}
template <> void GetDefaultMember::handle(const InsertSymbolMenu& m) {
handle(m.name);
}
template <> void GetMember::handle(const InsertSymbolMenu& m) {
handle(_("type"), m.type);
handle(_("name"), m.name);
handle(_("items"), m.items);
}
// ----------------------------------------------------------------------------- : SymbolFontRef
SymbolFontRef::SymbolFontRef()
......
......@@ -17,6 +17,7 @@
DECLARE_POINTER_TYPE(Font);
DECLARE_POINTER_TYPE(SymbolFont);
DECLARE_POINTER_TYPE(SymbolInFont);
DECLARE_POINTER_TYPE(InsertSymbolMenu);
class RotatedDC;
struct CharInfo;
......@@ -26,6 +27,7 @@ struct CharInfo;
class SymbolFont : public Packaged {
public:
SymbolFont();
~SymbolFont();
/// Loads the symbol font with a given name, for example "magic-mana-large"
static SymbolFontP byName(const String& name);
......@@ -33,7 +35,7 @@ class SymbolFont : public Packaged {
class DrawableSymbol;
typedef vector<DrawableSymbol> SplitSymbols;
/// Split a string into separate symbols for drawing and for determining their size
void split(const String& text, SplitSymbols& out) const;
void split(const String& text, Context& ctx, SplitSymbols& out) const;
/// Draw a piece of text prepared using split
void draw(RotatedDC& dc, Context& ctx, const RealRect& rect, double font_size, const Alignment& align, const String& text);
......@@ -48,6 +50,16 @@ class SymbolFont : public Packaged {
static String typeNameStatic();
virtual String typeName() const;
/// Generate a 'insert symbol' menu.
/** This class owns the menu!
* All ids used will be in the range ID_INSERT_SYMBOL_MENU_MIN...ID_INSERT_SYMBOL_MENU_MAX.
* If there is no insert symbol menu, returns nullptr.
*/
wxMenu* insertSymbolMenu(Context& ctx);
/// Process a choice from the insert symbol menu
/** Return the code representing the symbol */
String insertSymbolCode(int menu_id) const;
private:
UInt img_size; ///< Font size that the images use
UInt min_size; ///< Minimum font size
......@@ -61,8 +73,11 @@ class SymbolFont : public Packaged {
double text_margin_bottom;
Alignment text_alignment;
bool merge_numbers; ///< Merge numbers? e.g. "11" is a single symbol ('1' must not exist as a symbol)
InsertSymbolMenuP insert_symbol_menu;
wxMenu* processed_insert_symbol_menu;
friend class SymbolInFont;
friend class InsertSymbolMenu;
vector<SymbolInFontP> symbols; ///< The individual symbols
/// Find the default symbol
......@@ -83,6 +98,35 @@ class SymbolFont : public Packaged {
DECLARE_REFLECTION();
};
// ----------------------------------------------------------------------------- : InsertSymbolMenu
enum MenuItemType
{ ITEM_CODE ///< Name gives the code to insert
, ITEM_CUSTOM ///< Use a dialog box
, ITEM_LINE ///< A menu separator
, ITEM_SUBMENU ///< A submenu
};
/// Description of a menu to insert symbols from a symbol font into the text
class InsertSymbolMenu {
public:
InsertSymbolMenu();
MenuItemType type;
String name;
vector<InsertSymbolMenuP> items;
/// Number of ids used (recursive)
int size() const;
/// Get the code for an item, id relative to the start of this menu
String getCode(int id, const SymbolFont& font) const;
/// Make an actual menu
wxMenu* makeMenu(int first_id, Context& ctx, SymbolFont& font) const;
/// Make an actual menu item
wxMenuItem* makeMenuItem(wxMenu* parent, int first_id, Context& ctx, SymbolFont& font) const;
DECLARE_REFLECTION();
};
// ----------------------------------------------------------------------------- : SymbolFontRef
......
......@@ -143,6 +143,20 @@ void DataEditor::doCopy() { if (current_editor) current_ed
void DataEditor::doPaste() { if (current_editor) current_editor->doPaste(); }
void DataEditor::doFormat(int type) { if (current_editor) current_editor->doFormat(type); }
wxMenu* DataEditor::getMenu(int type) const {
if (current_editor) {
return current_editor->getMenu(type);
} else {
return nullptr;
}
}
void DataEditor::onCommand(int id) {
if (current_editor) {
current_editor->onCommand(id);
}
}
// ----------------------------------------------------------------------------- : Mouse events
void DataEditor::onLeftDown(wxMouseEvent& ev) {
......@@ -274,7 +288,9 @@ void DataEditor::onContextMenu(wxContextMenuEvent& ev) {
}
void DataEditor::onMenu(wxCommandEvent& ev) {
if (current_editor) {
current_editor->onMenu(ev);
if (!current_editor->onCommand(ev.GetId())) {
ev.Skip();
}
} else {
ev.Skip();
}
......
......@@ -53,6 +53,10 @@ class DataEditor : public CardViewer {
bool canFormat(int type) const;
bool hasFormat(int type) const;
void doFormat (int type);
/// Get a special menu, events should be sent to onCommand
wxMenu* getMenu(int type) const;
/// A menu item from getMenu was selected
void onCommand(int id);
// --------------------------------------------------- : ValueViewers
......
......@@ -282,7 +282,9 @@ void CardListBase::rebuild() {
if (f.second->card_list_align & ALIGN_RIGHT) align = wxLIST_FORMAT_RIGHT;
else if (f.second->card_list_align & ALIGN_CENTER) align = wxLIST_FORMAT_CENTRE;
else align = wxLIST_FORMAT_LEFT;
InsertColumn((long)column_fields.size(), capitalize(f.second->card_list_name), align, cs.width);
InsertColumn((long)column_fields.size(),
tr(*set->game, f.second->card_list_name, capitalize(f.second->card_list_name)),
align, cs.width);
column_fields.push_back(f.second);
}
// find field that determines color
......
......@@ -75,7 +75,7 @@ void CardListColumnSelectDialog::initList() {
// Init items
Color window_color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
FOR_EACH(c, columns) {
list->Append(capitalize(c.field->card_list_name));
list->Append(tr(*game, c.field->card_list_name, capitalize(c.field->card_list_name)));
// check
int i = list->GetCount() - 1;
list->Check(i, c.settings.visible);
......@@ -88,7 +88,7 @@ void CardListColumnSelectDialog::initList() {
void CardListColumnSelectDialog::refreshItem(int i) {
list->Check (i, columns[i].settings.visible);
list->SetString(i, capitalize(columns[i].field->card_list_name));
list->SetString(i, tr(*game, columns[i].field->card_list_name, capitalize(columns[i].field->card_list_name)) );
}
// ----------------------------------------------------------------------------- : Events
......
......@@ -38,7 +38,9 @@ void NativeLookEditor::drawViewer(RotatedDC& dc, ValueViewer& v) {
dc.DrawRectangle(s.getRect().grow(1));
// draw label
dc.SetFont(*wxNORMAL_FONT);
dc.DrawText(capitalize_sentence(s.fieldP->name), RealPoint(margin_left, s.top + 1));
// TODO : tr using stylesheet or using game?
dc.DrawText(tr(*set->game, s.fieldP->name, capitalize_sentence(s.fieldP->name)),
RealPoint(margin_left, s.top + 1));
// draw 3D border
draw_control_border(this, dc.getDC(), RealRect(s.left - 1, s.top - 1, s.width + 2, s.height + 2));
// draw viewer
......
......@@ -77,6 +77,11 @@ void IconMenu::Append(int id, const String& text, const String& help, wxMenu* su
wxMenu::Append(item);
}
void IconMenu::Append(wxMenuItem* item) {
item->SetBitmap(wxNullBitmap);
wxMenu::Append(item);
}
void IconMenu::Insert(size_t pos, int id, const String& text, const String& help) {
wxMenuItem* item = new wxMenuItem (this, id, text, help);
item->SetBitmap(wxNullBitmap);
......
......@@ -27,6 +27,8 @@ class IconMenu : public wxMenu {
void Append(int id, const String& text, const String& help);
/// Append a menu item, without an image
void Append(int id, const String& text, const String& help, wxMenu* submenu);
/// Append a menu item, without an image
void Append(wxMenuItem* item);
/// Insert a menu item, without an image
void Insert(size_t pos, int id, const String& text, const String& help);
};
......
......@@ -47,10 +47,49 @@ CardsPanel::CardsPanel(Window* parent, int id)
s->Add(splitter, 1, wxEXPAND);
s->SetSizeHints(this);
SetSizer(s);
// init menus
menuCard = new IconMenu();
menuCard->Append(ID_CARD_PREV, _("Select &Previous Card\tPgUp"), _("Selects the previous card in the list"));
menuCard->Append(ID_CARD_NEXT, _("Select &Next Card\tPgDn"), _("Selects the next card in the list"));
menuCard->AppendSeparator();
menuCard->Append(ID_CARD_ADD, _("card_add"), _("&Add Card\tCtrl++"), _("Add a new, blank, card to this set"));
menuCard->Append(ID_CARD_ADD_MULT, _("card_add_multiple"), _("Add &Multiple Cards..."), _("Add multiple cards to the set"));
// NOTE: space after "Del" prevents wx from making del an accellerator
// otherwise we delete a card when delete is pressed inside the editor
menuCard->Append(ID_CARD_REMOVE, _("card_del"), _("&Remove Select Card\tDel "), _("Delete the selected card from this set"));
menuCard->AppendSeparator();
IconMenu* menuRotate = new IconMenu();
menuRotate->Append(ID_CARD_ROTATE_0, _("card_rotate_0"), _("&Normal"), _("Display the card with the right side up"), wxITEM_CHECK);
menuRotate->Append(ID_CARD_ROTATE_270, _("card_rotate_270"), _("Rotated 90 &Clockwise"), _("Display the card rotated clockwise"), wxITEM_CHECK);
menuRotate->Append(ID_CARD_ROTATE_90, _("card_rotate_90"), _("Rotated 90 C&ounter Clockwise"), _("Display the card rotated counter-clockwise (anti-clockwise for the British)"), wxITEM_CHECK);
menuRotate->Append(ID_CARD_ROTATE_180, _("card_rotate_180"), _("Rotated 180, &Up Side Down"), _("Display the card up side down"), wxITEM_CHECK);
menuCard->Append(wxID_ANY, _("card_rotate"), _("&Orientation"), _("Orientation of the card display"), wxITEM_NORMAL, menuRotate);
menuCard->AppendSeparator();
// This probably belongs in the window menu, but there we can't remove the separator once it is added
menuCard->Append(ID_SELECT_COLUMNS, _("C&ard List Columns..."), _("Select what columns should be shown and in what order."));
menuFormat = new IconMenu();
menuFormat->Append(ID_FORMAT_BOLD, _("bold"), _("Bold\tCtrl+B"), _("Makes the selected text bold"), wxITEM_CHECK);
menuFormat->Append(ID_FORMAT_ITALIC, _("italic"), _("Italic\tCtrl+I"), _("Makes the selected text italic"), wxITEM_CHECK);
menuFormat->Append(ID_FORMAT_SYMBOL, _("symbol"), _("Symbols\tCtrl+M"), _("Draws the selected text with symbols"), wxITEM_CHECK);
menuFormat->Append(ID_FORMAT_REMINDER, _("reminder"), _("Reminder Text\tCtrl+R"), _("Show reminder text for the selected keyword"), wxITEM_CHECK);
menuFormat->AppendSeparator();
insertSymbolMenu = new wxMenuItem(menuFormat, ID_INSERT_SYMBOL, _("Insert Symbol"));
menuFormat->Append(insertSymbolMenu);
}
CardsPanel::~CardsPanel() {
// settings.card_notes_height = splitter->GetSashPosition();
// we don't own the submenu
wxMenu* menu = insertSymbolMenu->GetSubMenu();
if (menu && menu->GetParent() == menuFormat) {
menu->SetParent(nullptr);
}
insertSymbolMenu->SetSubMenu(nullptr);
// delete menus
delete menuCard;
delete menuFormat;
}
void CardsPanel::onChangeSet() {
......@@ -78,32 +117,7 @@ void CardsPanel::initUI(wxToolBar* tb, wxMenuBar* mb) {
tb->AddTool(ID_CARD_ROTATE, _(""), load_resource_tool_image(_("card_rotate")), wxNullBitmap,wxITEM_NORMAL,_TOOL_("rotate card"));
tb->Realize();
// Menus
IconMenu* menuCard = new IconMenu();
menuCard->Append(ID_CARD_PREV, _("Select &Previous Card\tPgUp"), _("Selects the previous card in the list"));
menuCard->Append(ID_CARD_NEXT, _("Select &Next Card\tPgDn"), _("Selects the next card in the list"));
menuCard->AppendSeparator();
menuCard->Append(ID_CARD_ADD, _("card_add"), _("&Add Card\tCtrl++"), _("Add a new, blank, card to this set"));
menuCard->Append(ID_CARD_ADD_MULT, _("card_add_multiple"), _("Add &Multiple Cards..."), _("Add multiple cards to the set"));
// NOTE: space after "Del" prevents wx from making del an accellerator
// otherwise we delete a card when delete is pressed inside the editor
menuCard->Append(ID_CARD_REMOVE, _("card_del"), _("&Remove Select Card\tDel "), _("Delete the selected card from this set"));
menuCard->AppendSeparator();
IconMenu* menuRotate = new IconMenu();
menuRotate->Append(ID_CARD_ROTATE_0, _("card_rotate_0"), _("&Normal"), _("Display the card with the right side up"), wxITEM_CHECK);
menuRotate->Append(ID_CARD_ROTATE_270, _("card_rotate_270"), _("Rotated 90 &Clockwise"), _("Display the card rotated clockwise"), wxITEM_CHECK);
menuRotate->Append(ID_CARD_ROTATE_90, _("card_rotate_90"), _("Rotated 90 C&ounter Clockwise"), _("Display the card rotated counter-clockwise (anti-clockwise for the British)"), wxITEM_CHECK);
menuRotate->Append(ID_CARD_ROTATE_180, _("card_rotate_180"), _("Rotated 180, &Up Side Down"), _("Display the card up side down"), wxITEM_CHECK);
menuCard->Append(wxID_ANY, _("card_rotate"), _("&Orientation"), _("Orientation of the card display"), wxITEM_NORMAL, menuRotate);
menuCard->AppendSeparator();
// This probably belongs in the window menu, but there we can't remove the separator once it is added
menuCard->Append(ID_SELECT_COLUMNS, _("C&ard List Columns..."), _("Select what columns should be shown and in what order."));
mb->Insert(2, menuCard, _("&Cards"));
IconMenu* menuFormat = new IconMenu();
menuFormat->Append(ID_FORMAT_BOLD, _("bold"), _("Bold\tCtrl+B"), _("Makes the selected text bold"), wxITEM_CHECK);
menuFormat->Append(ID_FORMAT_ITALIC, _("italic"), _("Italic\tCtrl+I"), _("Makes the selected text italic"), wxITEM_CHECK);
menuFormat->Append(ID_FORMAT_SYMBOL, _("symbol"), _("Symbols\tCtrl+M"), _("Draws the selected text with symbols"), wxITEM_CHECK);
menuFormat->Append(ID_FORMAT_REMINDER, _("reminder"), _("Reminder Text\tCtrl+R"), _("Show reminder text for the selected keyword"), wxITEM_CHECK);
mb->Insert(3, menuFormat, _("&Format"));
}
......@@ -120,8 +134,8 @@ void CardsPanel::destroyUI(wxToolBar* tb, wxMenuBar* mb) {
tb->DeleteToolByPos(10); // delete separator
tb->DeleteToolByPos(10); // delete separator
// Menus
delete mb->Remove(3);
delete mb->Remove(2);
mb->Remove(3);
mb->Remove(2);
}
void CardsPanel::onUpdateUI(wxUpdateUIEvent& ev) {
......@@ -148,6 +162,16 @@ void CardsPanel::onUpdateUI(wxUpdateUIEvent& ev) {
}
break;
}
case ID_INSERT_SYMBOL: {
wxMenu* menu = editor->getMenu(ID_INSERT_SYMBOL);
ev.Enable(menu);
if (insertSymbolMenu->GetSubMenu() != menu || menu->GetParent() != menuFormat) {
// re-add the menu
menuFormat->Remove(insertSymbolMenu);
insertSymbolMenu->SetSubMenu(menu);
menuFormat->Append(insertSymbolMenu);
}
}
}
}
......@@ -187,6 +211,12 @@ void CardsPanel::onCommand(int id) {
break;
}
}
default: {
if (id >= ID_INSERT_SYMBOL_MENU_MIN && id <= ID_INSERT_SYMBOL_MENU_MAX) {
// pass on to editor
editor->onCommand(id);
}
}
}
}
......
......@@ -16,6 +16,7 @@ class wxSplitterWindow;
class ImageCardList;
class DataEditor;
class TextCtrl;
class IconMenu;
// ----------------------------------------------------------------------------- : CardsPanel
......@@ -95,7 +96,8 @@ class CardsPanel : public SetWindowPanel {
TextCtrl* notes;
// --------------------------------------------------- : Menus & tools
wxMenu* cardMenu, formatMenu;
IconMenu* menuCard, *menuFormat;
wxMenuItem* insertSymbolMenu; // owned by menuFormat, but submenu owned by SymbolFont
};
// ----------------------------------------------------------------------------- : EOF
......
......@@ -53,8 +53,10 @@ class ValueEditor {
/// a context menu is requested, add extra items to the menu m
/** return false to suppress menu */
virtual bool onContextMenu(wxMenu& m, wxContextMenuEvent& ev) { return true; }
/// A menu item was selected
virtual void onMenu(wxCommandEvent& ev) { ev.Skip(); }
/// Get a special menu, events will be sent to onMenu
virtual wxMenu* getMenu(int type) const { return nullptr; }
/// A menu item was selected, return true if the command was processed
virtual bool onCommand(int id) { return false; }
// --------------------------------------------------- : Clipboard
......
......@@ -192,7 +192,7 @@ void TextValueEditor::onChar(wxKeyEvent& ev) {
// TODO: Find a more correct way to determine normal characters,
// this might not work for internationalized input.
// It might also not be portable!
replaceSelection(String(ev.GetUnicodeKey(), 1), _("Typing"));
replaceSelection(escape(String(ev.GetUnicodeKey(), 1)), _("Typing"));
}
}
}
......@@ -224,6 +224,31 @@ bool TextValueEditor::onContextMenu(wxMenu& m, wxContextMenuEvent& ev) {
// always show the menu
return true;
}
bool TextValueEditor::onCommand(int id) {
if (id >= ID_INSERT_SYMBOL_MENU_MIN && id <= ID_INSERT_SYMBOL_MENU_MAX) {
// Insert a symbol
if ((style().always_symbol || style().allow_formating) && style().symbol_font.valid()) {
String code = style().symbol_font.font->insertSymbolCode(id);
if (!style().always_symbol) {
code = _("<sym>") + code + _("</sym>");
}
replaceSelection(code, _("Insert Symbol"));
return true;
}
}
return false;
}
wxMenu* TextValueEditor::getMenu(int type) const {
if (type == ID_INSERT_SYMBOL && (style().always_symbol || style().allow_formating)
&& style().symbol_font.valid()) {
return style().symbol_font.font->insertSymbolMenu(viewer.getContext());
} else {
return nullptr;
}
}
/*
/// TODO : move to doFormat
void TextValueEditor::onMenu(wxCommandEvent& ev) {
if (ev.GetId() == ID_FORMAT_REMINDER) {
// toggle reminder text
......@@ -235,6 +260,7 @@ void TextValueEditor::onMenu(wxCommandEvent& ev) {
ev.Skip();
}
}
*/
// ----------------------------------------------------------------------------- : Other overrides
......
......@@ -44,7 +44,8 @@ class TextValueEditor : public TextValueViewer, public ValueEditor {
virtual void onMouseWheel(const RealPoint& pos, wxMouseEvent& ev);
virtual bool onContextMenu(wxMenu& m, wxContextMenuEvent&);
virtual void onMenu(wxCommandEvent&);
virtual wxMenu* getMenu(int type) const;
virtual bool onCommand(int);
virtual void onChar(wxKeyEvent&);
......@@ -98,6 +99,7 @@ class TextValueEditor : public TextValueViewer, public ValueEditor {
void moveSelectionNoRedraw(IndexType t, size_t new_end, bool also_move_start=true, Movement dir = MOVE_MID);
/// Replace the current selection with 'replacement', name the action
/** replacement should be a tagged string (i.e. already escaped) */
void replaceSelection(const String& replacement, const String& name);
/// Make sure the selection satisfies its constraints
......
......@@ -24,7 +24,8 @@ String Error::what() const {
// Errors for which a message box was already shown
vector<String> previous_errors;
String pending_error;
String pending_errors;
String pending_warnings;
DECLARE_TYPEOF_COLLECTION(String);
void handle_error(const String& e, bool allow_duplicate = true, bool now = true) {
......@@ -38,8 +39,8 @@ void handle_error(const String& e, bool allow_duplicate = true, bool now = true)
}
// Only show errors in the main thread
if (!now || !wxThread::IsMain()) {
if (!pending_error.empty()) pending_error += _("\n\n");
pending_error += e;
if (!pending_errors.empty()) pending_errors += _("\n\n");
pending_errors += e;
return;
}
// show message
......@@ -50,10 +51,27 @@ void handle_error(const Error& e, bool allow_duplicate, bool now) {
handle_error(e.what(), allow_duplicate, now);
}
void handle_warning(const String& w, bool now) {
// Check duplicates
// TODO: thread safety
// Only show errors in the main thread
if (!now || !wxThread::IsMain()) {
if (!pending_warnings.empty()) pending_warnings += _("\n\n");
pending_warnings += w;
return;
}
// show message
wxMessageBox(w, _("Warning"), wxOK | wxICON_EXCLAMATION);
}
void handle_pending_errors() {
assert(wxThread::IsMain());
if (!pending_error.empty()) {
handle_error(pending_error);
pending_error.clear();
if (!pending_errors.empty()) {
handle_error(pending_errors);
pending_errors.clear();
}
if (!pending_warnings.empty()) {
handle_warning(pending_warnings);
pending_warnings.clear();
}
}
......@@ -96,7 +96,10 @@ class ScriptError : public Error {
*/
void handle_error(const Error& e, bool allow_duplicate = true, bool now = true);
/// Handle errors that were not handled immediatly in handleError
/// Handle a warning by showing a message box
void handle_warning(const String& w, bool now = true);
/// Handle errors and warnings that were not handled immediatly in handleError
/** Should be called repeatedly (e.g. in an onIdle event handler) */
void handle_pending_errors();
......
......@@ -59,7 +59,7 @@ class Package {
bool needSaveAs() const;
/// Determines the short name of this package: the filename without path or extension
String name() const;
/// Return the full name of this package, by default equal to name()
/// Return the (user friendly) full name of this package, by default equal to name()
virtual String fullName() const;
/// Return the absolute filename of this file
const String& absoluteFilename() const;
......
......@@ -41,7 +41,7 @@ void Reader::handleAppVersion() {
if (enterBlock(_("mse_version"))) {
handle(file_app_version);
if (app_version < file_app_version) {
wxMessageBox(_ERROR_2_("newer version", filename, file_app_version.toString()), _("Warning"), wxOK | wxICON_EXCLAMATION);
handle_warning(_ERROR_2_("newer version", filename, file_app_version.toString()), false);
}
exitBlock();
}
......@@ -53,7 +53,7 @@ void Reader::warning(const String& msg) {
void Reader::showWarnings() {
if (!warnings.empty()) {
wxMessageBox(_("Warnings while reading file:\n") + filename + _("\n") + warnings, _("Warning"), wxOK | wxICON_EXCLAMATION);
handle_warning(_("Warnings while reading file:\n") + filename + _("\n") + warnings, false);
warnings.clear();
}
}
......
......@@ -19,6 +19,10 @@
#include <util/prec.hpp>
#include <util/string.hpp>
class Game;
class StyleSheet;
class SymbolFont;
// ----------------------------------------------------------------------------- : Localisation macros
enum LocaleCategory
......@@ -37,6 +41,24 @@ enum LocaleCategory
/// Translate 'key' in the category 'cat' using the current locale
String tr(LocaleCategory cat, const String& key);
/// Translate 'key' in the for a Game using the current locale
String tr(const Game&, const String& key);
/// Translate 'key' in the for a StyleSheet using the current locale
String tr(const StyleSheet&, const String& key);
/// Translate 'key' in the for a SymbolFont using the current locale
String tr(const SymbolFont&, const String& key);
/// Translate 'key' in the for a Game using the current locale
/** If the key is not found, use the default value */
String tr(const Game&, const String& key, const String& def);
/// Translate 'key' in the for a StyleSheet using the current locale
/** If the key is not found, use the default value */
String tr(const StyleSheet&, const String& key, const String& def);
/// Translate 'key' in the for a SymbolFont using the current locale
/** If the key is not found, use the default value */
String tr(const SymbolFont&, const String& key, const String& def);
/// A localized string for menus/toolbar buttons
#define _MENU_(s) tr(LOCALE_CAT_MENU, _(s))
/// A localized string for help/statusbar text
......
......@@ -141,13 +141,17 @@ String cannocial_name_form(const String& str) {
ret.reserve(str.size());
bool leading = true;
FOR_EACH_CONST(c, str) {
if ((c == _('_') || c == _(' ')) && !leading) {
ret += _(' ');
if ((c == _('_') || c == _(' '))) {
if (!leading) ret += _(' ');
} else {
ret += c;
leading = false;
/*
} else if (isAlnum(c) || c == _('-')) {
ret += toLower(c);
leading = false;
} else {
// ignore non alpha numeric
// ignore non alpha numeric*/
}
}
return ret;
......
......@@ -331,7 +331,7 @@ String tagged_substr_replace(const String& input, size_t start, size_t end, cons
return simplify_tagged(
substr_replace(input, start, end,
get_tags(input, start, end, true) + // close tags
escape(replacement) +
replacement +
get_tags(input, start, end, false) // open tags
));
}
......
......@@ -127,7 +127,7 @@ String remove_tag_contents(const String& str, const String& tag);
* This function makes sure tags still match. It also attempts to cancel out tags.
* This means that when removing "<x>a</x>" nothing is left,
* but with input "<x>a" -> "<x>" and "</>a" -> "</>".
* Escapes the replacement, i.e. all < in become \1.
* Does not escape the replacement.
*/
String tagged_substr_replace(const String& input, size_t start, size_t end, const String& replacement);
......
......@@ -81,7 +81,7 @@ enum MenuID {
enum ChildMenuID {
ID_CHILD_MIN = 1000
, ID_CHILD_MAX = 2999
, ID_CHILD_MAX = 3999
// Cards menu
, ID_CARD_ADD = 1001
......@@ -106,6 +106,7 @@ enum ChildMenuID {
, ID_FORMAT_ITALIC
, ID_FORMAT_SYMBOL
, ID_FORMAT_REMINDER
, ID_INSERT_SYMBOL
// SymbolSelectEditor toolbar/menu
, ID_PART = 2001
......@@ -146,6 +147,10 @@ enum ChildMenuID {
// Style
, ID_STYLE_USE_FOR_ALL = 3201
// SymbolFont (Format menu)
, ID_INSERT_SYMBOL_MENU_MIN = 3301
, ID_INSERT_SYMBOL_MENU_MAX = 3999
};
......
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