Implement unique IDs and card linking

This commit is contained in:
GenevensiS
2025-08-11 16:17:13 +02:00
committed by GitHub
parent 13406b946c
commit 3bf9de18b1
100 changed files with 2432 additions and 1219 deletions
+83 -2
View File
@@ -13,6 +13,7 @@
#include <data/pack.hpp>
#include <data/stylesheet.hpp>
#include <util/error.hpp>
#include <util/uid.hpp>
// ----------------------------------------------------------------------------- : Add card
@@ -36,10 +37,53 @@ String AddCardAction::getName(bool to_undo) const {
}
void AddCardAction::perform(bool to_undo) {
// If we are adding cards, resolve any uid conflicts
// (If we are re-adding cards, from a remove undo, there shouldn't be any uid conflicts)
// We always assume uid conflicts occur because a card was copy-pasted into the same set,
// and never because two different cards randomly got assigned the same uid
if (action.adding && !to_undo) {
// Tally existing unique ids
unordered_map<String, CardP> all_existing_uids;
FOR_EACH(card, set.cards) {
all_existing_uids.insert({ card->uid, card });
}
// Tally added unique ids
unordered_map<String, CardP> all_added_uids;
for (size_t pos = 0; pos < action.steps.size(); ++pos) {
CardP card = action.steps[pos].item;
all_added_uids.insert({ card->uid, card });
}
FOR_EACH(added_pair, all_added_uids) {
String old_uid = added_pair.first;
CardP added_card = added_pair.second;
// Assign new unique ids
if (all_existing_uids.find(old_uid) != all_existing_uids.end()) {
String new_uid = generate_uid();
added_card->uid = new_uid;
all_added_uids.insert({ new_uid, added_card });
// Update links on linked cards
OTHER_LINKED_PAIRS(linked_pairs, added_card);
FOR_EACH(linked_pair, linked_pairs) {
String& linked_uid = linked_pair.first.get();
String& linked_relation = linked_pair.second.get();
if (linked_uid == wxEmptyString) continue;
// If it's an added card, replace the link
if (all_added_uids.find(linked_uid) != all_added_uids.end()) {
all_added_uids.at(linked_uid)->updateLink(old_uid, new_uid);
}
// Otherwise, if it's an existing card, copy the link
else if (all_existing_uids.find(linked_uid) != all_existing_uids.end()) {
all_existing_uids.at(linked_uid)->copyLink(set, old_uid, new_uid);
}
}
}
}
}
// Add or remove cards
action.perform(set.cards, to_undo);
}
// ----------------------------------------------------------------------------- : Reorder cards
ReorderCardsAction::ReorderCardsAction(Set& set, size_t card_id1, size_t card_id2)
@@ -55,13 +99,50 @@ void ReorderCardsAction::perform(bool to_undo) {
assert(card_id1 < set.cards.size());
assert(card_id2 < set.cards.size());
#endif
if (card_id1 >= set.cards.size() || card_id2 < set.cards.size()) {
if (card_id1 >= set.cards.size() || card_id2 >= set.cards.size()) {
// TODO : Too lazy to fix this right now.
return;
}
swap(set.cards[card_id1], set.cards[card_id2]);
}
// ----------------------------------------------------------------------------- : Link cards
LinkCardsAction::LinkCardsAction(Set& set, const CardP& selected_card, vector<CardP>& linked_cards, const String& selected_relation, const String& linked_relation)
: CardListAction(set), selected_card(selected_card), linked_cards(linked_cards), selected_relation(selected_relation), linked_relation(linked_relation)
{}
String LinkCardsAction::getName(bool to_undo) const {
return _("Link cards");
}
void LinkCardsAction::perform(bool to_undo) {
if (!to_undo) {
selected_card->link(set, linked_cards, selected_relation, linked_relation);
} else {
selected_card->unlink(linked_cards);
}
}
UnlinkCardsAction::UnlinkCardsAction(Set& set, const CardP& selected_card, CardP& unlinked_card)
: CardListAction(set), selected_card(selected_card), unlinked_card(unlinked_card)
{}
String UnlinkCardsAction::getName(bool to_undo) const {
return _("Unlink card");
}
void UnlinkCardsAction::perform(bool to_undo) {
if (!to_undo) {
pair<String, String> relations = selected_card->unlink(unlinked_card);
selected_relation = relations.first;
unlinked_relation = relations.second;
}
else {
selected_card->link(set, unlinked_card, selected_relation, unlinked_relation);
}
}
// ----------------------------------------------------------------------------- : Change stylesheet
String DisplayChangeAction::getName(bool to_undo) const {
+31
View File
@@ -64,6 +64,37 @@ public:
const size_t card_id1, card_id2; ///< Positions of the two cards to swap
};
// ----------------------------------------------------------------------------- : Link cards
/// Add a link between two or more cards
class LinkCardsAction : public CardListAction {
public:
LinkCardsAction(Set& set, const CardP& selected_card, vector<CardP>& linked_cards, const String& selected_relation, const String& linked_relation);
String getName(bool to_undo) const override;
void perform(bool to_undo) override;
//private:
CardP selected_card; ///< The card currently selected in the cards tab
vector<CardP> linked_cards; ///< The cards that will be linked to the selected card
String selected_relation; ///< The nature of the relation of the selected card
String linked_relation; ///< The nature of the relation of the linked cards
};
/// Remove a link between two cards
class UnlinkCardsAction : public CardListAction {
public:
UnlinkCardsAction(Set& set, const CardP& selected_card, CardP& unlinked_card);
String getName(bool to_undo) const override;
void perform(bool to_undo) override;
//private:
CardP selected_card; ///< The card currently selected in the cards tab
CardP unlinked_card; ///< The card that will be unlinked from the selected card
String selected_relation; ///< The nature of the relation of the selected card
String unlinked_relation; ///< The nature of the relation of the unlinked card
};
// ----------------------------------------------------------------------------- : Change stylesheet
/// An action that affects the rendering/display/look of a set or cards in the set
+231 -1
View File
@@ -8,19 +8,23 @@
#include <util/prec.hpp>
#include <data/card.hpp>
#include <data/set.hpp>
#include <data/game.hpp>
#include <data/stylesheet.hpp>
#include <data/field.hpp>
#include <util/error.hpp>
#include <util/reflect.hpp>
#include <util/delayed_index_maps.hpp>
#include <util/uid.hpp>
#include <unordered_set>
// ----------------------------------------------------------------------------- : Card
Card::Card()
// for files made before we saved these times, set the time to 'yesterday'
// for files made before we saved these, set the time to 'yesterday', generate a uid
: time_created (wxDateTime::Now().Subtract(wxDateSpan::Day()).ResetTime())
, time_modified(wxDateTime::Now().Subtract(wxDateSpan::Day()).ResetTime())
, uid(generate_uid())
, has_styling(false)
{
if (!game_for_reading()) {
@@ -32,6 +36,7 @@ Card::Card()
Card::Card(const Game& game)
: time_created (wxDateTime::Now())
, time_modified(wxDateTime::Now())
, uid(generate_uid())
, has_styling(false)
{
data.init(game.card_fields);
@@ -60,6 +65,222 @@ bool Card::contains(QuickFilterPart const& query) const {
return false;
}
void Card::link(const Set& set, const vector<CardP>& linked_cards, const String& selected_relation, const String& linked_relation)
{
unlink(linked_cards);
unordered_set<String> all_existing_uids;
FOR_EACH(card, set.cards) {
all_existing_uids.insert(card->uid);
}
int free_link_count = 0;
THIS_LINKED_PAIRS(this_linked_pairs);
FOR_EACH(this_linked_pair, this_linked_pairs) {
String& this_linked_uid = this_linked_pair.first.get();
if (
this_linked_uid == wxEmptyString || // Not a reference
all_existing_uids.find(this_linked_uid) == all_existing_uids.end() // Reference to nonexistent card
) free_link_count++;
}
if (free_link_count < linked_cards.size()) {
queue_message(MESSAGE_WARNING, _ERROR_("not enough free links"));
return;
}
vector<CardP> all_missed_cards;
FOR_EACH(linked_card, linked_cards) {
bool written = false;
// Try to write to a free spot
FOR_EACH(this_linked_pair, this_linked_pairs) {
String& this_linked_uid = this_linked_pair.first.get();
String& this_linked_relation = this_linked_pair.second.get();
if (this_linked_uid == wxEmptyString) {
this_linked_uid = linked_card->uid;
this_linked_relation = linked_relation;
written = true;
break;
}
}
// Try to write to an erasable spot
if (!written) {
FOR_EACH(this_linked_pair, this_linked_pairs) {
String& this_linked_uid = this_linked_pair.first.get();
String& this_linked_relation = this_linked_pair.second.get();
if (all_existing_uids.find(this_linked_uid) == all_existing_uids.end()) {
this_linked_uid = linked_card->uid;
this_linked_relation = linked_relation;
written = true;
break;
}
}
}
if (!written) {
// Should be impossible to end up here?
}
OTHER_LINKED_PAIRS(linked_pairs, linked_card);
written = false;
// Try to write to a free spot
FOR_EACH(linked_pair, linked_pairs) {
String& linked_uid = linked_pair.first.get();
String& linked_relation = linked_pair.second.get();
if (linked_uid == wxEmptyString) {
linked_uid = uid;
linked_relation = selected_relation;
written = true;
break;
}
}
// Try to write to an erasable spot
if (!written) {
FOR_EACH(linked_pair, linked_pairs) {
String& linked_uid = linked_pair.first.get();
String& linked_relation = linked_pair.second.get();
if (all_existing_uids.find(linked_uid) == all_existing_uids.end()) {
linked_uid = uid;
linked_relation = selected_relation;
written = true;
break;
}
}
}
// Notify we couldn't write
if (!written) {
all_missed_cards.push_back(linked_card);
}
}
if (all_missed_cards.size() > 0) {
std::stringstream ss;
ss << _ERROR_("could not link");
for (size_t pos = 0; pos < all_missed_cards.size(); ++pos) {
ss << all_missed_cards[pos]->identification();
if (pos < all_missed_cards.size() - 1) ss << ", ";
};
String wxString(ss.str().c_str(), wxConvUTF8);
queue_message(MESSAGE_WARNING, wxString);
}
}
void Card::link(const Set& set, CardP& linked_card, const String& selected_relation, const String& linked_relation)
{
vector<CardP> linked_cards { linked_card };
link(set, linked_cards, selected_relation, linked_relation);
}
void Card::unlink(const vector<CardP>& unlinked_cards)
{
for (size_t pos = 0; pos < unlinked_cards.size(); ++pos) {
CardP unlinked_card = unlinked_cards[pos];
unlink(unlinked_card);
}
}
pair<String, String> Card::unlink(CardP& unlinked_card)
{
String old_selected_relation = wxEmptyString;
THIS_LINKED_PAIRS(this_linked_pairs);
FOR_EACH(this_linked_pair, this_linked_pairs) {
String& this_linked_uid = this_linked_pair.first.get();
String& this_linked_relation = this_linked_pair.second.get();
if (this_linked_uid == unlinked_card->uid) {
old_selected_relation = this_linked_relation;
this_linked_uid = wxEmptyString;
this_linked_relation = wxEmptyString;
}
}
String old_unlinked_relation = wxEmptyString;
OTHER_LINKED_PAIRS(unlinked_pairs, unlinked_card);
FOR_EACH(unlinked_pair, unlinked_pairs) {
String& unlinked_uid = unlinked_pair.first.get();
String& unlinked_relation = unlinked_pair.second.get();
if (unlinked_uid == uid) {
old_unlinked_relation = unlinked_relation;
unlinked_uid = wxEmptyString;
unlinked_relation = wxEmptyString;
}
}
return make_pair(old_selected_relation, old_unlinked_relation);
}
void Card::copyLink(const Set& set, String old_uid, String new_uid) {
// Find what relation we need to copy
String relation_copy = wxEmptyString;
THIS_LINKED_PAIRS(this_linked_pairs);
FOR_EACH(this_linked_pair, this_linked_pairs) {
String& this_linked_uid = this_linked_pair.first.get();
String& this_linked_relation = this_linked_pair.second.get();
if (this_linked_uid == old_uid) {
relation_copy = this_linked_relation;
break;
}
}
// Nothing to copy
if (relation_copy == wxEmptyString) {
return;
}
// Try to copy to a free spot
bool written = false;
FOR_EACH(this_linked_pair, this_linked_pairs) {
String& this_linked_uid = this_linked_pair.first.get();
String& this_linked_relation = this_linked_pair.second.get();
if (this_linked_uid == wxEmptyString) {
this_linked_uid = new_uid;
this_linked_relation = relation_copy;
written = true;
break;
}
}
// Try to copy to an erasable spot
if (!written) {
unordered_set<String> all_existing_uids;
FOR_EACH(card, set.cards) {
all_existing_uids.insert(card->uid);
}
FOR_EACH(this_linked_pair, this_linked_pairs) {
String& this_linked_uid = this_linked_pair.first.get();
String& this_linked_relation = this_linked_pair.second.get();
if (all_existing_uids.find(this_linked_uid) == all_existing_uids.end()) {
this_linked_uid = new_uid;
this_linked_relation = relation_copy;
written = true;
break;
}
}
}
// Notify we couldn't copy
if (!written) {
queue_message(MESSAGE_WARNING, _ERROR_("not enough free links for copy"));
}
}
void Card::updateLink(String old_uid, String new_uid) {
THIS_LINKED_PAIRS(this_linked_pairs);
FOR_EACH(this_linked_pair, this_linked_pairs) {
String& this_linked_uid = this_linked_pair.first.get();
if (this_linked_uid == old_uid) {
this_linked_uid = new_uid;
return;
}
}
}
vector<pair<CardP, String>> Card::getLinkedCards(const Set& set) {
unordered_map<String, String> links{
{ linked_card_1, linked_relation_1 },
{ linked_card_2, linked_relation_2 },
{ linked_card_3, linked_relation_3 },
{ linked_card_4, linked_relation_4 }
};
vector<pair<CardP, String>> linked_cards;
FOR_EACH(other_card, set.cards) {
if (links.find(other_card->uid) != links.end()) {
linked_cards.push_back(make_pair(other_card, links.at(other_card->uid)));
}
}
return linked_cards;
}
IndexMap<FieldP, ValueP>& Card::extraDataFor(const StyleSheet& stylesheet) {
return extra_data.get(stylesheet.name(), stylesheet.extra_card_fields);
}
@@ -89,6 +310,15 @@ IMPLEMENT_REFLECTION(Card) {
}
}
REFLECT(notes);
REFLECT(uid);
REFLECT(linked_card_1);
REFLECT(linked_card_2);
REFLECT(linked_card_3);
REFLECT(linked_card_4);
REFLECT(linked_relation_1);
REFLECT(linked_relation_2);
REFLECT(linked_relation_3);
REFLECT(linked_relation_4);
REFLECT(time_created);
REFLECT(time_modified);
REFLECT(extra_data); // don't allow scripts to depend on style specific data
+27
View File
@@ -17,11 +17,15 @@
class Game;
class Dependency;
class Keyword;
DECLARE_POINTER_TYPE(Set);
DECLARE_POINTER_TYPE(Card);
DECLARE_POINTER_TYPE(Field);
DECLARE_POINTER_TYPE(Value);
DECLARE_POINTER_TYPE(StyleSheet);
#define THIS_LINKED_PAIRS(var) vector<pair<reference_wrapper<String>, reference_wrapper<String>>> var { make_pair(ref(linked_card_1), ref(linked_relation_1)), make_pair(ref(linked_card_2), ref(linked_relation_2)), make_pair(ref(linked_card_3), ref(linked_relation_3)), make_pair(ref(linked_card_4), ref(linked_relation_4)) }
#define OTHER_LINKED_PAIRS(var, other_card) vector<pair<reference_wrapper<String>, reference_wrapper<String>>> var { make_pair(ref(other_card->linked_card_1), ref(other_card->linked_relation_1)), make_pair(ref(other_card->linked_card_2), ref(other_card->linked_relation_2)), make_pair(ref(other_card->linked_card_3), ref(other_card->linked_relation_3)), make_pair(ref(other_card->linked_card_4), ref(other_card->linked_relation_4)) }
// ----------------------------------------------------------------------------- : Card
/// A card from a card Set
@@ -37,6 +41,18 @@ public:
IndexMap<FieldP, ValueP> data;
/// Notes for this card
String notes;
/// Unique identifier for this card, so other cards can refer to it, and be linked to it
String uid;
/// Up to four uid of other cards, to encode relations such as front face/back face, or generator/token, etc...
String linked_card_1;
String linked_card_2;
String linked_card_3;
String linked_card_4;
/// Nature of the relatation with the respective linked card, such as back face, or token, etc...
String linked_relation_1;
String linked_relation_2;
String linked_relation_3;
String linked_relation_4;
/// Time the card was created/last modified
wxDateTime time_created, time_modified;
/// Alternative style to use for this card
@@ -64,6 +80,17 @@ public:
/// Does any field contains the given query string?
bool contains(QuickFilterPart const& query) const;
/// Link or unlink other cards to this card
void link(const Set& set, const vector<CardP>& linked_cards, const String& selected_relation, const String& linked_relation);
void link(const Set& set, CardP& linked_card, const String& selected_relation, const String& linked_relation);
void unlink(const vector<CardP>& linked_cards);
pair<String, String> unlink(CardP& unlinked_card); // Returns the relations that were deleted, so we can undo
void copyLink(const Set& set, String old_uid, String new_uid);
void updateLink(String old_uid, String new_uid);
vector<pair<CardP, String>> getLinkedCards(const Set& set);
/// Find a value in the data by name and type
template <typename T> T& value(const String& name) {
for(IndexMap<FieldP, ValueP>::iterator it = data.begin() ; it != data.end() ; ++it) {
+36 -6
View File
@@ -68,14 +68,14 @@ IMPLEMENT_REFLECTION(Field) {
}
void Field::after_reading(Version ver) {
name = canonical_name_form(name);
if(caption.default_.empty()) caption.default_ = tr(package_relative_filename, name, name_to_caption);
name = canonical_name_form(name);
if(caption.default_.empty()) caption.default_ = tr(package_relative_filename, name, name_to_caption);
if(card_list_name.default_.empty()) card_list_name.default_ = tr(package_relative_filename, caption.default_, capitalize);
}
template <>
intrusive_ptr<Field> read_new<Field>(Reader& reader) {
intrusive_ptr<Field> field;
intrusive_ptr<Field> field;
// there must be a type specified
String type;
reader.handle(_("type"), type);
@@ -95,7 +95,7 @@ intrusive_ptr<Field> read_new<Field>(Reader& reader) {
} else {
reader.warning(_ERROR_1_("unsupported field type", type));
throw ParseError(_ERROR_("aborting parsing"));
}
}
field->package_relative_filename = reader.getPackage()->relativeFilename().Clone();
return field;
}
@@ -222,8 +222,38 @@ void Style::checkContentDependencies(Context& ctx, const Dependency& dep) const
void Style::markDependencyMember(const String& name, const Dependency& dep) const {
// mark dependencies on content
if (dep.type == DEP_DUMMY && dep.index == false && (starts_with(name, _("content")) || name == "layout") ) {
// anything that starts with "content_" is a content property
if (
dep.type == DEP_DUMMY && dep.index == false && (
starts_with(name, _("content")) ||
name == "layout" ||
name == "lines" ||
name == "paragraphs" ||
name == "blocks" ||
name == "separators" ||
name == "font" ||
name == "symbol_font" ||
name == "always_symbol" ||
name == "allow_formating" ||
name == "alignment" ||
name == "padding_left" ||
name == "padding_right" ||
name == "padding_top" ||
name == "padding_bottom" ||
name == "padding_left_min" ||
name == "padding_right_min" ||
name == "padding_top_min" ||
name == "padding_bottom_min" ||
name == "line_height_soft" ||
name == "line_height_hard" ||
name == "line_height_line" ||
name == "line_height_soft_max" ||
name == "line_height_hard_max" ||
name == "line_height_line_max" ||
name == "paragraph_height" ||
name == "block_height_min" ||
name == "direction"
)
) {
const_cast<Dependency&>(dep).index = true;
}
}
+1 -1
View File
@@ -61,7 +61,7 @@ public:
Alignment card_list_align; ///< Alignment of the card list colummn.
OptionalScript sort_script; ///< The script to use when sorting this, if not the value.
OptionalScript import_script; ///< The script to apply to the supplied value, when creating a new card.
Dependencies dependent_scripts; ///< Scripts that depend on values of this field
Dependencies dependent_scripts; ///< Scripts that depend on values of this field
String package_relative_filename;
/// Creates a new Value corresponding to this Field
+50 -50
View File
@@ -8,10 +8,10 @@
#include <util/prec.hpp>
#include <data/font.hpp>
#include <wx/stdpaths.h>
#include <wx/dir.h>
#include <wx/font.h>
#include <wx/stdpaths.h>
#include <wx/dir.h>
#include <wx/font.h>
// ----------------------------------------------------------------------------- : Font
Font::Font()
@@ -27,59 +27,59 @@ Font::Font()
, separator_color(Color(0,0,0,128))
, flags(FONT_NORMAL)
{}
bool Font::PreloadResourceFonts(bool recursive) {
#if wxUSE_PRIVATE_FONTS
String pathSeparator(wxFileName::GetPathSeparator());
String appPath(wxFileName(wxStandardPaths::Get().GetExecutablePath()).GetPath());
wxDir appDir(appPath);
if (!appDir.IsOpened()) return true;
bool preloadHadErrors = false;
bool Font::PreloadResourceFonts(bool recursive) {
#if wxUSE_PRIVATE_FONTS
String pathSeparator(wxFileName::GetPathSeparator());
String appPath(wxFileName(wxStandardPaths::Get().GetExecutablePath()).GetPath());
wxDir appDir(appPath);
if (!appDir.IsOpened()) return true;
bool preloadHadErrors = false;
wxString folder;
bool cont = appDir.GetFirst(&folder, wxEmptyString, wxDIR_DIRS);
while (cont)
{
{
if (folder.Lower().Contains("fonts")) {
String folderPath = appPath + pathSeparator + folder + pathSeparator;
// tally fonts
vector<String> fontFilePaths;
TallyResourceFonts(folderPath, fontFilePaths, recursive);
// load fonts
for (const String& fontFilePath : fontFilePaths) {
if (!wxFont::AddPrivateFont(fontFilePath)) {
preloadHadErrors = true;
}
String folderPath = appPath + pathSeparator + folder + pathSeparator;
// tally fonts
vector<String> fontFilePaths;
TallyResourceFonts(folderPath, fontFilePaths, recursive);
// load fonts
for (const String& fontFilePath : fontFilePaths) {
if (!wxFont::AddPrivateFont(fontFilePath)) {
preloadHadErrors = true;
}
}
}
}
cont = appDir.GetNext(&folder);
}
return preloadHadErrors;
#endif // wxUSE_PRIVATE_FONTS
return false;
}
void Font::TallyResourceFonts(String fontsDirectoryPath, vector<String>& fontFilePaths, bool recursive) {
wxDir fontsDirectory(fontsDirectoryPath);
String fontFileName = wxEmptyString;
bool hasNext = fontsDirectory.GetFirst(&fontFileName);
while (hasNext) {
String fontFilePath = fontsDirectoryPath + fontFileName;
if (wxDirExists(fontFilePath)) {
if (recursive) {
TallyResourceFonts(fontFilePath + wxFileName::GetPathSeparator(), fontFilePaths, true);
}
}
else if (fontFilePath.EndsWith(_(".ttf")) || fontFilePath.EndsWith(_(".otf"))) {
fontFilePaths.push_back(fontFilePath);
}
hasNext = fontsDirectory.GetNext(&fontFileName);
}
}
}
return preloadHadErrors;
#endif // wxUSE_PRIVATE_FONTS
return false;
}
void Font::TallyResourceFonts(String fontsDirectoryPath, vector<String>& fontFilePaths, bool recursive) {
wxDir fontsDirectory(fontsDirectoryPath);
String fontFileName = wxEmptyString;
bool hasNext = fontsDirectory.GetFirst(&fontFileName);
while (hasNext) {
String fontFilePath = fontsDirectoryPath + fontFileName;
if (wxDirExists(fontFilePath)) {
if (recursive) {
TallyResourceFonts(fontFilePath + wxFileName::GetPathSeparator(), fontFilePaths, true);
}
}
else if (fontFilePath.EndsWith(_(".ttf")) || fontFilePath.EndsWith(_(".otf"))) {
fontFilePaths.push_back(fontFilePath);
}
hasNext = fontsDirectory.GetNext(&fontFileName);
}
}
bool Font::update(Context& ctx) {
bool changes = false;
+10 -9
View File
@@ -46,15 +46,16 @@ public:
Scriptable<double> shadow_displacement_y;///< Position of the shadow
Scriptable<double> shadow_blur; ///< Blur radius of the shadow
Color separator_color; ///< Color for <sep> text
int flags; ///< FontFlags for this font
int flags; ///< FontFlags for this font
Font();
/// Load fonts (.ttf or .otf) from all directories in the app directory that contain "fonts" in their names,
/// and optionaly their subdirectories, returns true if there were errors
static bool PreloadResourceFonts(bool recursive);
/// Adds font file paths from the given directory into fontFilePaths
static void TallyResourceFonts(String fontsDirectoryPath, vector<String>& fontFilePaths, bool recursive);
/// Load fonts (.ttf or .otf) from all directories in the app directory that contain "fonts" in their names,
/// and optionaly their subdirectories, returns true if there were errors
static bool PreloadResourceFonts(bool recursive);
/// Adds font file paths from the given directory into fontFilePaths
static void TallyResourceFonts(String fontsDirectoryPath, vector<String>& fontFilePaths, bool recursive);
/// Update the scritables, returns true if there is a change
bool update(Context& ctx);
/// Add the given dependency to the dependent_scripts list for the variables this font depends on
@@ -70,7 +71,7 @@ public:
/// Convert this font to a wxFont
wxFont toWxFont(double scale) const;
private:
DECLARE_REFLECTION();
};
+3
View File
@@ -147,6 +147,9 @@ CardsOnClipboard::CardsOnClipboard(const SetP& set, const vector<CardP>& cards)
if (cards.size() == 1) {
Add(new wxBitmapDataObject(export_bitmap(set, cards[0])));
}
else if (cards.size() < 6) {
Add(new wxBitmapDataObject(export_bitmap(set, cards, true, 0, 1.0, 0.0)));
}
// Conversion to serialized card format
Add(new CardsDataObject(set, cards), true);
}
+3 -2
View File
@@ -99,11 +99,12 @@ void export_images(const SetP& set, const vector<CardP>& cards,
void export_image(const SetP& set, const CardP& card, const String& filename);
/// Generate a bitmap image of a card
Bitmap export_bitmap(const SetP& set, const CardP& card);
Bitmap export_bitmap(const SetP& set, const CardP& card);
Bitmap export_bitmap(const SetP& set, const CardP& card, const double zoom, const Radians angle_radians);
Bitmap export_bitmap(const SetP& set, const vector<CardP>& cards, bool scale_to_lowest_dpi, int padding, const double zoom, const Radians angle_radians);
/// Export a set to Magic Workstation format
void export_mws(Window* parent, const SetP& set);
/// Export a set to Apprentice
void export_apprentice(Window* parent, const SetP& set);
void export_apprentice(Window* parent, const SetP& set);
+87 -35
View File
@@ -13,6 +13,7 @@
#include <data/card.hpp>
#include <data/stylesheet.hpp>
#include <data/settings.hpp>
#include <gui/util.hpp>
#include <render/card/viewer.hpp>
#include <wx/filename.h>
@@ -25,47 +26,47 @@ void export_image(const SetP& set, const CardP& card, const String& filename) {
}
class UnzoomedDataViewer : public DataViewer {
public:
UnzoomedDataViewer();
public:
UnzoomedDataViewer();
UnzoomedDataViewer(double zoom, double angle);
virtual ~UnzoomedDataViewer() {};
Rotation getRotation() const override;
private:
private:
double zoom;
double angle;
double angle;
bool declared_values;
};
UnzoomedDataViewer::UnzoomedDataViewer()
: zoom(1.0)
, angle(0.0)
, declared_values(false)
{}
UnzoomedDataViewer::UnzoomedDataViewer(const double zoom, const Radians angle = 0.0)
: zoom(zoom)
, angle(angle)
, declared_values(true)
};
UnzoomedDataViewer::UnzoomedDataViewer()
: zoom(1.0)
, angle(0.0)
, declared_values(false)
{}
UnzoomedDataViewer::UnzoomedDataViewer(const double zoom, const Radians angle = 0.0)
: zoom(zoom)
, angle(angle)
, declared_values(true)
{}
Rotation UnzoomedDataViewer::getRotation() const {
if (!stylesheet) stylesheet = set->stylesheet;
if (declared_values) {
return Rotation(angle, stylesheet->getCardRect(), zoom, 1.0, ROTATION_ATTACH_TOP_LEFT);
}
if (!stylesheet) stylesheet = set->stylesheet;
if (declared_values) {
return Rotation(angle, stylesheet->getCardRect(), zoom, 1.0, ROTATION_ATTACH_TOP_LEFT);
}
double export_zoom = settings.stylesheetSettingsFor(set->stylesheetFor(card)).export_zoom();
bool use_viewer_rotation = !settings.stylesheetSettingsFor(set->stylesheetFor(card)).card_normal_export();
if (use_viewer_rotation) {
double export_zoom = settings.stylesheetSettingsFor(set->stylesheetFor(card)).export_zoom();
bool use_viewer_rotation = !settings.stylesheetSettingsFor(set->stylesheetFor(card)).card_normal_export();
if (use_viewer_rotation) {
return Rotation(DataViewer::getRotation().getAngle(), stylesheet->getCardRect(), export_zoom, 1.0, ROTATION_ATTACH_TOP_LEFT);
} else {
return Rotation(angle, stylesheet->getCardRect(), export_zoom, 1.0, ROTATION_ATTACH_TOP_LEFT);
}
}
Bitmap export_bitmap(const SetP& set, const CardP& card) {
if (!set) throw Error(_("no set"));
}
Bitmap export_bitmap(const SetP& set, const CardP& card) {
if (!set) throw Error(_("no set"));
UnzoomedDataViewer viewer = UnzoomedDataViewer();
viewer.setSet(set);
viewer.setCard(card);
@@ -79,11 +80,11 @@ Bitmap export_bitmap(const SetP& set, const CardP& card) {
// draw
viewer.draw(dc);
dc.SelectObject(wxNullBitmap);
return bitmap;
}
Bitmap export_bitmap(const SetP& set, const CardP& card, const double zoom, const Radians angle = 0.0) {
if (!set) throw Error(_("no set"));
return bitmap;
}
Bitmap export_bitmap(const SetP& set, const CardP& card, const double zoom, const Radians angle = 0.0) {
if (!set) throw Error(_("no set"));
UnzoomedDataViewer viewer = UnzoomedDataViewer(zoom, angle);
viewer.setSet(set);
viewer.setCard(card);
@@ -97,8 +98,59 @@ Bitmap export_bitmap(const SetP& set, const CardP& card, const double zoom, cons
// draw
viewer.draw(dc);
dc.SelectObject(wxNullBitmap);
return bitmap;
}
return bitmap;
}
// put multiple card images into one bitmap
Bitmap export_bitmap(const SetP& set, const vector<CardP>& cards, bool scale_to_lowest_dpi, int padding, const double zoom, const Radians angle = 0.0) {
if (!set) throw Error(_("no set"));
vector<Bitmap> bitmaps;
int width = 0;
int height = 0;
double lowest_dpi = 1200.0;
if (scale_to_lowest_dpi) {
FOR_EACH(card, cards) {
lowest_dpi = min(lowest_dpi, set->stylesheetFor(card).card_dpi);
}
lowest_dpi = max(lowest_dpi, 150.0);
}
// Draw card bitmaps
FOR_EACH(card, cards) {
double scaled_zoom = zoom;
if (scale_to_lowest_dpi) {
double dpi = max(set->stylesheetFor(card).card_dpi, 150.0);
scaled_zoom *= lowest_dpi / dpi;
}
UnzoomedDataViewer viewer = UnzoomedDataViewer(scaled_zoom, angle);
viewer.setSet(set);
viewer.setCard(card);
RealSize size = viewer.getRotation().getExternalSize();
Bitmap bitmap((int)size.width, (int)size.height);
if (!bitmap.Ok()) throw InternalError(_("Unable to create bitmap"));
wxMemoryDC bufferDC;
bufferDC.SelectObject(bitmap);
clearDC(bufferDC, *wxWHITE_BRUSH);
viewer.draw(bufferDC);
bufferDC.SelectObject(wxNullBitmap);
width += (int)size.width;
height = max(height, (int)size.height);
bitmaps.push_back(bitmap);
}
// Draw global bitmap
Bitmap global_bitmap(width + (bitmaps.size()-1) * padding, height);
if (!global_bitmap.Ok()) throw InternalError(_("Unable to create bitmap"));
wxMemoryDC globalDC;
globalDC.SelectObject(global_bitmap);
clearDC(globalDC, *wxWHITE_BRUSH);
int offset = 0;
FOR_EACH(bitmap, bitmaps) {
globalDC.SetDeviceOrigin(offset, 0);
globalDC.DrawBitmap(bitmap, 0, 0);
offset += bitmap.GetWidth() + padding;
}
globalDC.SelectObject(wxNullBitmap);
return global_bitmap;
}
// ----------------------------------------------------------------------------- : Multiple card export
+21 -20
View File
@@ -48,6 +48,7 @@ IMPLEMENT_REFLECTION(Game) {
}
REFLECT_NO_SCRIPT(default_set_style);
REFLECT_NO_SCRIPT(card_fields);
REFLECT_NO_SCRIPT(card_links);
REFLECT_NO_SCRIPT(card_list_color_script);
REFLECT_NO_SCRIPT(import_script);
REFLECT_NO_SCRIPT(json_paths);
@@ -95,26 +96,26 @@ void Game::validate(Version v) {
pack->filter = OptionalScript(_("true"));
pack->select = SELECT_NO_REPLACE;
pack_types.push_back(pack);
}
// alternate card field names map
}
// alternate card field names map
for (auto it = card_fields.begin(); it != card_fields.end(); ++it) {
FieldP field = *it;
String unified_name = unified_form(field->name);
if (card_fields_alt_names.count(unified_name)) {
queue_message(MESSAGE_WARNING, _("Duplicate alternate card field name: ") + unified_name);
}
else {
card_fields_alt_names.emplace(unified_name, field->name);
}
//String column_name = field->card_list_name.get();
//card_fields_alt_names.emplace(unified_form(column_name), field->name);
for (auto it2 = field->alt_names.begin(); it2 != field->alt_names.end(); ++it2) {
unified_name = unified_form(*it2);
if (card_fields_alt_names.count(unified_name)) {
queue_message(MESSAGE_WARNING, _("Duplicate alternate card field name: ") + unified_name);
}
else {
card_fields_alt_names.emplace(unified_name, field->name);
FieldP field = *it;
String unified_name = unified_form(field->name);
if (card_fields_alt_names.count(unified_name)) {
queue_message(MESSAGE_WARNING, _("Duplicate alternate card field name: ") + unified_name);
}
else {
card_fields_alt_names.emplace(unified_name, field->name);
}
//String column_name = field->card_list_name.get();
//card_fields_alt_names.emplace(unified_form(column_name), field->name);
for (auto it2 = field->alt_names.begin(); it2 != field->alt_names.end(); ++it2) {
unified_name = unified_form(*it2);
if (card_fields_alt_names.count(unified_name)) {
queue_message(MESSAGE_WARNING, _("Duplicate alternate card field name: ") + unified_name);
}
else {
card_fields_alt_names.emplace(unified_name, field->name);
}
}
}
@@ -138,7 +139,7 @@ void Game::initCardListColorScript() {
return;
}
}
}
}
// special behaviour of reading/writing GamePs: only read/write the name
+3 -3
View File
@@ -12,7 +12,7 @@
#include <util/io/package.hpp>
#include <script/scriptable.hpp>
#include <script/dependency.hpp>
#include <util/dynamic_arg.hpp>
#include <util/dynamic_arg.hpp>
DECLARE_POINTER_TYPE(Field);
DECLARE_POINTER_TYPE(Style);
@@ -41,6 +41,7 @@ public:
vector<FieldP> set_fields; ///< Fields for set information
IndexMap<FieldP,StyleP> default_set_style; ///< Default style for the set fields, because it is often the same
vector<FieldP> card_fields; ///< Fields on each card
vector<String> card_links; ///< Possible links between cards
OptionalScript card_list_color_script; ///< Script that determines the color of items in the card list
OptionalScript import_script; ///< Script applied as the last step of the new_card function
vector<String> json_paths; ///< Paths inside JSON files to find the card array
@@ -50,8 +51,7 @@ public:
vector<WordListP> word_lists; ///< Word lists for editing with a drop down list
vector<AddCardsScriptP> add_cards_scripts; ///< Scripts for adding multiple cards to the set
vector<AutoReplaceP> auto_replaces; ///< Things to autoreplace in textboxes
map<String,String> card_fields_alt_names; ///< Other names that fields might go by, for example in CSV files
map<String,String> card_fields_alt_names; ///< Other names that fields might go by, for example in CSV files
bool has_keywords; ///< Does this game use keywords?
OptionalScript keyword_match_script; ///< For the keyword editor
vector<KeywordParamP> keyword_parameter_types;///< Types of keyword parameters
+4 -4
View File
@@ -118,8 +118,8 @@ String tr(const Package& pkg, const String& subcat, const String& key, DefaultLo
loc = find_wildcard_and_set(the_locale->package_translations, pkg.relativeFilename());
}
return loc->tr(subcat, key, def);
}
}
String tr(const String& pkg_relative_filename, const String& key, DefaultLocaleFun def) {
if (!the_locale) return def(key);
SubLocaleP loc = the_locale->package_translations[pkg_relative_filename];
@@ -127,8 +127,8 @@ String tr(const String& pkg_relative_filename, const String& key, DefaultLocaleF
loc = find_wildcard_and_set(the_locale->package_translations, pkg_relative_filename);
}
return loc->tr(key, def);
}
}
String tr(const String& pkg_relative_filename, const String& subcat, const String& key, DefaultLocaleFun def) {
if (!the_locale) return def(key);
SubLocaleP loc = the_locale->package_translations[pkg_relative_filename];
+5 -5
View File
@@ -126,7 +126,7 @@ StyleSheetSettings::StyleSheetSettings()
, card_angle (0, true)
, card_anti_alias (true, true)
, card_borders (true, true)
, card_draw_editing (true, true)
, card_draw_editing (true, true)
, card_normal_export (true, true)
, card_spellcheck_enabled(true, true)
{}
@@ -137,7 +137,7 @@ void StyleSheetSettings::useDefault(const StyleSheetSettings& ss) {
if (card_angle .isDefault()) card_angle .assignDefault(ss.card_angle);
if (card_anti_alias .isDefault()) card_anti_alias .assignDefault(ss.card_anti_alias);
if (card_borders .isDefault()) card_borders .assignDefault(ss.card_borders);
if (card_draw_editing .isDefault()) card_draw_editing .assignDefault(ss.card_draw_editing);
if (card_draw_editing .isDefault()) card_draw_editing .assignDefault(ss.card_draw_editing);
if (card_normal_export .isDefault()) card_normal_export .assignDefault(ss.card_normal_export);
if (card_spellcheck_enabled.isDefault()) card_spellcheck_enabled.assignDefault(ss.card_spellcheck_enabled);
}
@@ -148,7 +148,7 @@ IMPLEMENT_REFLECTION_NO_SCRIPT(StyleSheetSettings) {
REFLECT(card_angle);
REFLECT(card_anti_alias);
REFLECT(card_borders);
REFLECT(card_draw_editing);
REFLECT(card_draw_editing);
REFLECT(card_normal_export);
REFLECT(card_spellcheck_enabled);
}
@@ -225,8 +225,8 @@ ColumnSettings& Settings::columnSettingsFor(const Game& game, const Field& field
}
return cs;
}
StyleSheetSettings& Settings::stylesheetSettingsFor(const StyleSheet& stylesheet) {
// Use the canonical form here since the stylesheet name will be used as a stored key.
StyleSheetSettings& Settings::stylesheetSettingsFor(const StyleSheet& stylesheet) {
// Use the canonical form here since the stylesheet name will be used as a stored key.
// This does introduce the possibility of collision if two stylesheets return the same value canonically, but I think that's just a necessary risk.
StyleSheetSettingsP& ss = stylesheet_settings[canonical_name_form(stylesheet.name())];
if (!ss) ss = make_intrusive<StyleSheetSettings>();
+4 -4
View File
@@ -195,10 +195,10 @@ public:
// --------------------------------------------------- : Special game stuff
String apprentice_location;
// --------------------------------------------------- : Internal settings
double internal_scale;
bool internal_image_extension;
// --------------------------------------------------- : Internal settings
double internal_scale;
bool internal_image_extension;
// --------------------------------------------------- : Update checking
#if USE_OLD_STYLE_UPDATE_CHECKER
+6 -11
View File
@@ -38,13 +38,13 @@ StyleSheetP StyleSheet::byGameAndName(const Game& game, const String& name) {
}
} catch (PackageNotFoundError& e) {
queue_message(MESSAGE_ERROR, _("Missing stylesheet: ") + full_name);
// This causes a freeze when the set contains two cards that use the same missing StyleSheet, and the second one has styling_data
// Also, it's probably better to ask the user for an alternative for each missing StyleSheet individually
// This causes a freeze when the set contains two cards that use the same missing StyleSheet, and the second one has styling_data
// Also, it's probably better to ask the user for an alternative for each missing StyleSheet individually
//if (stylesheet_for_reading()) {
// // we already have a stylesheet higher up, so just return a null pointer
// return StyleSheetP();
//}
//}
// load an alternative stylesheet
StyleSheetP ss = select_stylesheet(game, name);
@@ -103,7 +103,7 @@ IMPLEMENT_REFLECTION(StyleSheet) {
REFLECT(card_width);
REFLECT(card_height);
REFLECT(card_dpi);
REFLECT(card_background);
REFLECT(card_background);
REFLECT(card_regions);
REFLECT(init_script);
// styling
@@ -122,12 +122,7 @@ IMPLEMENT_REFLECTION(StyleSheet) {
// extra card fields
REFLECT(extra_card_fields);
REFLECT_IF_READING {
if (extra_card_style.init(extra_card_fields)) {
// if a value is not editable, don't save it
FOR_EACH(f, extra_card_fields) {
if (!f->editable) f->save_value = false;
}
}
extra_card_style.init(extra_card_fields);
}
REFLECT(extra_card_style);
}
+6 -6
View File
@@ -11,16 +11,16 @@
#include <util/prec.hpp>
#include <util/io/package.hpp>
#include <util/real_point.hpp>
#include <script/scriptable.hpp>
// This isn't strictly needed for this file,
// but CardRegion needs to be referenced from _somewhere_ in the codebase for compilation reasons.
// Eventually if somewhere else is using the type then this can be removed.
#include <script/scriptable.hpp>
// This isn't strictly needed for this file,
// but CardRegion needs to be referenced from _somewhere_ in the codebase for compilation reasons.
// Eventually if somewhere else is using the type then this can be removed.
#include <data/card_region.hpp>
DECLARE_POINTER_TYPE(Game);
DECLARE_POINTER_TYPE(StyleSheet);
DECLARE_POINTER_TYPE(Field);
DECLARE_POINTER_TYPE(Style);
DECLARE_POINTER_TYPE(Style);
DECLARE_POINTER_TYPE(CardRegion);
// ----------------------------------------------------------------------------- : StyleSheet
@@ -38,7 +38,7 @@ public:
double card_width; ///< The width of a card in pixels
double card_height; ///< The height of a card in pixels
double card_dpi; ///< The resolution of a card in dots per inch
Color card_background; ///< The background color of cards
Color card_background; ///< The background color of cards
vector<CardRegionP> card_regions;
/// The styling for card fields
/** The indices should correspond to the card_fields in the Game */
+6 -3
View File
@@ -394,7 +394,7 @@ void SymbolFont::getCharInfo(RotatedDC& dc, double font_size, const SplitSymbols
RealSize SymbolFont::symbolSize(double font_size, const DrawableSymbol& sym) {
if (sym.symbol) {
return add_diagonal(sym.symbol->size(*this, font_size), spacing);
return add_diagonal(sym.symbol->size(*this, font_size), spacingSize(font_size));
} else {
return defaultSymbolSize(font_size);
}
@@ -403,12 +403,15 @@ RealSize SymbolFont::symbolSize(double font_size, const DrawableSymbol& sym) {
RealSize SymbolFont::defaultSymbolSize(double font_size) {
SymbolInFont* def = defaultSymbol();
if (def) {
return add_diagonal(def->size(*this, font_size), spacing);
return add_diagonal(def->size(*this, font_size), spacingSize(font_size));
} else {
return add_diagonal(RealSize(1,1), spacing);
return add_diagonal(RealSize(1,1), spacingSize(font_size));
}
}
RealSize SymbolFont::spacingSize(double font_size) {
return RealSize(spacing.width * font_size / 15.0, spacing.height * font_size / 15.0);
}
// ----------------------------------------------------------------------------- : InsertSymbolMenu
+4 -1
View File
@@ -84,7 +84,7 @@ public:
private:
double img_size; ///< Font size that the images use
RealSize spacing; ///< Spacing between sybmols (for the default font size)
RealSize spacing; ///< Spacing between sybmols, in pixels, for a font size of 15
// writing text
bool scale_text; ///< Should text be scaled down to fit in a symbol?
InsertSymbolMenuP insert_symbol_menu;
@@ -107,6 +107,9 @@ public:
/// The default size of symbols, including spacing
RealSize defaultSymbolSize(double font_size);
/// The spacing between symbols, accounting for font size
RealSize SymbolFont::spacingSize(double font_size);
DECLARE_REFLECTION();
};