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
+3 -2
View File
@@ -1,6 +1,7 @@
cmake_minimum_required(VERSION 3.13)
project(magicseteditor VERSION 2.5.8)
project(magicseteditor VERSION 2.6.0)
add_definitions(-DUNOFFICIAL_BUILD)
set(CMAKE_CXX_STANDARD 17)
@@ -28,7 +29,7 @@ endif()
# You will most likely get a message about being unable to open hunspell-1.7.lib because pkgconf forgets to add the actual path to
# HUNSPELL_LIBRARIES. If so, uncomment the below line and point it to the correct vcpkg root folder/library.
#set(HUNSPELL_LIBRARIES "C:\\PATH\\TO\\ROOT\\vcpkg\\installed\\${VCPKG_TARGET_TRIPLET}\\lib\\hunspell-1.7.lib")
set(HUNSPELL_LIBRARIES "C:\\src\\vcpkg\\installed\\${VCPKG_TARGET_TRIPLET}\\lib\\hunspell-1.7.lib")
message("-- Does this have a full path? If not, and it's just a file name, it's broken: Found Hunspell at ${HUNSPELL_LIBRARIES}")
include_directories("${PROJECT_BINARY_DIR}/src")
+6 -4
View File
@@ -5,11 +5,13 @@ Function: crop
Shrink an image by cutting off some of the image, starting at the position denoted by the offsets. The resulting image size is specified in the parameters.
Resulting image can be bigger than the original, if offset_x or offset_y are negative, or if width or height are bigger than the original width and height.
--Parameters--
! Parameter Type Description
| @input@ [[type:image]] Image to enlarge
| @height@ [[type:double]] Height of the resulting image
| @width@ [[type:double]] Width of the resulting image
| @offset_x@ [[type:double]] Offset of crop, horizontally
| @offset_y@ [[type:double]] Offset of crop, vertically
| @height@ [[type:double]] Height of the resulting image, in pixels
| @width@ [[type:double]] Width of the resulting image, in pixels
| @offset_x@ [[type:double]] Offset of crop, horizontally, in pixels
| @offset_y@ [[type:double]] Offset of crop, vertically, in pixels
+11
View File
@@ -0,0 +1,11 @@
Function: dimensions_of
--Usage--
> dimensions_of(input: image)
Returns an array containing the width and height of the image in pixels.
--Parameters--
! Parameter Type Description
| @input@ [[type:image]] Image to whos dimensions we want.
+13
View File
@@ -0,0 +1,13 @@
Function: get_card_from_link
--Usage--
> get_card_from_link(card: card, "link type")
Inspects a given [[type:card]]'s links to find one of the given type, and returns the linked card.
Returns nil if no card was found.
--Parameters--
! Parameter Type Description
| @input@ [[type:string]] The type of link we want to find.
| @card@ [[type:card]] The card whose links we'll inspect.
| @set@ [[type:set]] The set in which to look. This can be omited since 'set' is a predefined variable.
+12
View File
@@ -0,0 +1,12 @@
Function: get_card_from_uid
--Usage--
> get_card_from_uid(input: "uid")
Returns the [[type:card]] with the given uid inside the set.
Returns nil if no card was found.
--Parameters--
! Parameter Type Description
| @input@ [[type:string]] The uid of the card we want to retrieve.
| @set@ [[type:set]] The set in which to look. This can be omited since 'set' is a predefined variable.
+2 -2
View File
@@ -5,12 +5,12 @@ Function: get_card_styling
Get the styling data of a [[type:card]].
This is for use in exporter scripts. In card scripts, use the "styling" predefined variable instead.
This is for use in exporter scripts. In card scripts, use the 'styling' predefined variable instead.
--Parameters--
! Parameter Type Description
| @input@ [[type:card]] The card you want to retrieve the styling data from.
| @set@ [[type:set]] The set the card belongs to. In an exporter script, this can be omited since "set" is a predefined variable.
| @set@ [[type:set]] The set the card belongs to. In an exporter script, this can be omited since 'set' is a predefined variable.
--Examples--
> # Retrieve the value "is foil" from the card's styling options
+6
View File
@@ -0,0 +1,6 @@
Function: get_mse_locale
--Usage--
> get_mse_locale()
Returns the name of the currently selected locale folder.
+14
View File
@@ -0,0 +1,14 @@
Function: has_link
--Usage--
> has_link(card: card, "link type")
Inspects a given [[type:card]]'s links to find one of the given type.
Returns true if such a link was found, false otherwise.
Note that this function does not check if the linked card exists in the set. For that, use get_card_from_link.
--Parameters--
! Parameter Type Description
| @input@ [[type:string]] The type of link we want to find.
| @card@ [[type:card]] The card whose links we'll inspect.
+6
View File
@@ -97,6 +97,8 @@ These functions are built into the program, other [[type:function]]s can be defi
| [[fun:flip_vertical]] Flip an image vertically.
| [[fun:rotate_image]] Rotate an image.
| [[fun:drop_shadow]] Add a drop shadow to an image.
| [[fun:insert_image]] Insert an image inside another.
| [[fun:dimensions_of]] Get the width and height of an image.
| [[fun:symbol_variation]] Render a variation of a [[type:symbol]].
| [[fun:import_image]] Load an image from outside the data folder.
| [[fun:built_in_image]] Return an image built into the program.
@@ -106,6 +108,9 @@ These functions are built into the program, other [[type:function]]s can be defi
| [[fun:add_card_to_set]] Add a [[type:card]] to a [[type:set]].
| [[fun:get_card_styling]] Get the styling data of a [[type:card]].
| [[fun:get_card_stylesheet]] Get the stylesheet of a [[type:card]].
| [[fun:get_card_from_uid]] Find the [[type:card]] with the given uid.
| [[fun:get_card_from_link]] Find a [[type:card]] that has the given link type to the given [[type:card]].
| [[fun:has_link]] Determine if the given the given [[type:card]] has a link of the given type.
! HTML export <<<
| [[fun:to_html]] Convert [[type:tagged text]] to html.
@@ -118,6 +123,7 @@ These functions are built into the program, other [[type:function]]s can be defi
! Other functions <<<
| [[fun:get_mse_version]] Get the MSE app version.
| [[fun:get_mse_locale]] Get the name of the currently selected locale.
| [[fun:get_mse_path]] Get the MSE app folder absolute path.
| [[fun:trace]] Output a message for debugging purposes.
| [[fun:assert]] Check a condition for debugging purposes.
+17
View File
@@ -0,0 +1,17 @@
Function: insert_image
--Usage--
> insert_image(base_image: image, inserted_image: image, offset_x: coordinate, offset_y: coordinate, background_color: color)
Insert an image inside another image.
The inserted image can be put outside the bounds of the base image. The resulting image will be widened accordingly.
--Parameters--
! Parameter Type Description
| @base_image@ [[type:image]] Image that serves as the canvas
| @inserted_image@ [[type:image]] Image inserted on top of the base image
| @offset_x@ [[type:double]] Offset of insertion, horizontally, in pixels
| @offset_y@ [[type:double]] Offset of insertion, vertically, in pixels
| @background_color@ [[type:color]] Background color, optional, defaults to transparent
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

+2
View File
@@ -47,6 +47,8 @@ tool/no_auto IMAGE "tool/no_auto.png"
tool/card_add IMAGE "tool/card_add.png"
tool/card_add_multiple IMAGE "tool/card_add_multiple.png"
tool/card_del IMAGE "tool/card_del.png"
tool/card_link IMAGE "tool/card_link.png"
tool/card_copy IMAGE "tool/card_copy.png"
tool/card_rotate IMAGE "tool/card_rotate.png"
tool/card_rotate_0 IMAGE "tool/card_rotate_0.png"
tool/card_rotate_90 IMAGE "tool/card_rotate_90.png"
+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,9 +37,52 @@ String AddCardAction::getName(bool to_undo) const {
}
void AddCardAction::perform(bool to_undo) {
action.perform(set.cards, 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
@@ -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) {
+32 -2
View File
@@ -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
View File
@@ -55,6 +55,7 @@ public:
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
+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);
}
+1
View File
@@ -101,6 +101,7 @@ 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, 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);
+52
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>
@@ -100,6 +101,57 @@ Bitmap export_bitmap(const SetP& set, const CardP& card, const double zoom, cons
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
+1
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);
+1 -1
View File
@@ -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
@@ -51,7 +52,6 @@ public:
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
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
+1 -6
View File
@@ -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 -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();
};
+1 -1
View File
@@ -621,7 +621,7 @@ Image PackagedImage::generate(const Options& opt) const {
if (img.HasMask()) img.InitAlpha(); // we can't handle masks
return img;
} else {
throw ScriptError(_("Unable to load image '") + filename + _("' from '" + opt.package->name() + _("'")));
throw ScriptError(_("Unable to load image '") + filename + _("' from '") + opt.package->name() + _("'"));
}
}
bool PackagedImage::operator == (const GeneratedImage& that) const {
+111
View File
@@ -0,0 +1,111 @@
//+----------------------------------------------------------------------------+
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
//| Copyright: (C) Twan van Laarhoven and the other MSE developers |
//| License: GNU General Public License 2 or later (see file COPYING) |
//+----------------------------------------------------------------------------+
// ----------------------------------------------------------------------------- : Includes
#include <util/prec.hpp>
#include <data/game.hpp>
#include <gui/card_link_window.hpp>
#include <gui/control/select_card_list.hpp>
#include <util/window_id.hpp>
#include <data/action/set.hpp>
#include <wx/statline.h>
// ----------------------------------------------------------------------------- : ExportCardSelectionChoice
CardLinkWindow::CardLinkWindow(Window* parent, const SetP& set, const CardP& selected_card, bool sizer)
: wxDialog(parent, wxID_ANY, _TITLE_("link cards"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
, set(set), selected_card(selected_card)
{
// init controls
selected_relation = new wxTextCtrl(this, wxID_ANY, wxEmptyString);
linked_relation = new wxTextCtrl(this, wxID_ANY, wxEmptyString);
relation_type = new wxChoice(this, ID_CARD_LINK_TYPE, wxDefaultPosition, wxDefaultSize, 0, nullptr);
relation_type->Clear();
FOR_EACH(link, set->game->card_links) {
relation_type->Append(link);
}
relation_type->Append(_LABEL_("custom link"));
relation_type->SetSelection(0);
setRelationType();
list = new SelectCardList(this, wxID_ANY);
list->setSet(set);
list->selectNone();
sel_none = new wxButton(this, ID_SELECT_NONE, _BUTTON_("select none"));
// init sizers
if (sizer) {
wxSizer* s = new wxBoxSizer(wxVERTICAL);
s->Add(new wxStaticText(this, -1, _LABEL_("linked cards relation")), 0, wxALL, 8);
s->Add(relation_type, 0, wxEXPAND | (wxALL & ~wxTOP), 8);
s->Add(new wxStaticText(this, -1, _(" ") + _LABEL_("selected card")), 0, wxALL, 4);
s->Add(selected_relation, 0, wxEXPAND | (wxALL & ~wxTOP), 8);
s->Add(new wxStaticText(this, -1, _(" ") + _LABEL_("linked cards")), 0, wxALL, 4);
s->Add(linked_relation, 0, wxEXPAND | (wxALL & ~wxTOP), 8);
s->Add(new wxStaticText(this, wxID_ANY, _LABEL_("select linked cards")), 0, wxALL & ~wxBOTTOM, 8);
s->Add(list, 1, wxEXPAND | wxALL, 8);
wxSizer* s2 = new wxBoxSizer(wxHORIZONTAL);
s2->Add(sel_none, 0, wxEXPAND | wxRIGHT, 8);
s2->Add(CreateButtonSizer(wxOK | wxCANCEL), 1, wxEXPAND, 8);
s->Add(s2, 0, wxEXPAND | (wxALL & ~wxTOP), 8);
s->SetSizeHints(this);
SetSizer(s);
SetSize(600,500);
}
}
bool CardLinkWindow::isSelected(const CardP& card) const {
return list->isSelected(card);
}
void CardLinkWindow::getSelection(vector<CardP>& out) const {
list->getSelection(out);
}
void CardLinkWindow::setSelection(const vector<CardP>& cards) {
list->setSelection(cards);
}
void CardLinkWindow::setRelationType() {
int sel = relation_type->GetSelection();
if (sel == relation_type->GetCount() - 1) { // Custom type
selected_relation->ChangeValue(_LABEL_("custom link selected"));
selected_relation->Enable();
linked_relation->ChangeValue(_LABEL_("custom link linked"));
linked_relation->Enable();
}
else {
String relation = relation_type->GetString(sel);
int delimiter_pos = relation.find("//");
selected_relation->ChangeValue(relation.substr(0, delimiter_pos).Trim().Trim(false));
selected_relation->Enable(false);
linked_relation->ChangeValue(delimiter_pos + 2 < relation.Length() ? relation.substr(delimiter_pos + 2).Trim().Trim(false) : _LABEL_("custom link undefined"));
linked_relation->Enable(false);
}
}
void CardLinkWindow::onSelectNone(wxCommandEvent&) {
list->selectNone();
}
void CardLinkWindow::onRelationTypeChange(wxCommandEvent&) {
setRelationType();
}
void CardLinkWindow::onOk(wxCommandEvent&) {
// Perform the linking
// The selected_card is the one selected on the main cards tab
// The linked_cards are the ones selected in this dialogue window
vector<CardP> linked_cards;
getSelection(linked_cards);
set->actions.addAction(make_unique<LinkCardsAction>(*set, selected_card, linked_cards, selected_relation->GetValue(), linked_relation->GetValue()));
// Done
EndModal(wxID_OK);
}
BEGIN_EVENT_TABLE(CardLinkWindow, wxDialog)
EVT_BUTTON (ID_SELECT_NONE, CardLinkWindow::onSelectNone)
EVT_BUTTON (wxID_OK, CardLinkWindow::onOk)
EVT_CHOICE (ID_CARD_LINK_TYPE, CardLinkWindow::onRelationTypeChange)
END_EVENT_TABLE ()
+52
View File
@@ -0,0 +1,52 @@
//+----------------------------------------------------------------------------+
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
//| Copyright: (C) Twan van Laarhoven and the other MSE developers |
//| License: GNU General Public License 2 or later (see file COPYING) |
//+----------------------------------------------------------------------------+
#pragma once
// ----------------------------------------------------------------------------- : Includes
#include <util/prec.hpp>
DECLARE_POINTER_TYPE(Set);
DECLARE_POINTER_TYPE(Card);
DECLARE_POINTER_TYPE(ExportCardSelectionChoice);
class SelectCardList;
// ----------------------------------------------------------------------------- : CardLinkWindow
/// A window for selecting a subset of the cards from a set,
/** and selecting a link relation type.
/** this is used when linking cards
*/
class CardLinkWindow : public wxDialog {
public:
CardLinkWindow(Window* parent, const SetP& set, const CardP& selected_card, bool sizer=true);
/// Is the given card selected?
bool isSelected(const CardP& card) const;
/// Get a list of all selected cards
void getSelection(vector<CardP>& out) const;
/// Change which cards are selected
void setSelection(const vector<CardP>& cards);
/// Change the type of link relation
void setRelationType();
protected:
DECLARE_EVENT_TABLE();
wxChoice* relation_type;
wxTextCtrl* selected_relation, *linked_relation;
SelectCardList* list;
SetP set;
CardP selected_card;
wxButton* sel_none;
void onRelationTypeChange(wxCommandEvent&);
void onOk(wxCommandEvent&);
void onSelectNone(wxCommandEvent&);
};
+23
View File
@@ -9,6 +9,7 @@
#include <util/prec.hpp>
#include <gui/control/card_editor.hpp>
#include <gui/value/editor.hpp>
#include <gui/set/cards_panel.hpp>
#include <gui/util.hpp>
#include <data/field.hpp>
#include <data/stylesheet.hpp>
@@ -360,6 +361,16 @@ void DataEditor::onMotion(wxMouseEvent& ev) {
}
}
void DataEditor::onMouseEnter(wxMouseEvent& ev) {
ev.Skip();
if (GetId() == ID_CARD_LINK_EDITOR) {
CardsPanel* panel = dynamic_cast<CardsPanel*> (GetParent());
if (panel) {
panel->refreshCard(card);
}
}
}
void DataEditor::onMouseLeave(wxMouseEvent& ev) {
// on mouse leave for editor
if (hovered_viewer) {
@@ -462,6 +473,13 @@ void DataEditor::onChar(wxKeyEvent& ev) {
} else {
ev.Skip();
}
if (GetId() == ID_CARD_LINK_EDITOR) {
CardsPanel* panel = dynamic_cast<CardsPanel*> (GetParent());
if (panel) {
panel->refreshCard(card);
}
}
}
// ----------------------------------------------------------------------------- : Menu events
@@ -503,6 +521,10 @@ void DataEditor::onFocus(wxFocusEvent& ev) {
selectFirst();
}
}
CardsPanel* panel = dynamic_cast<CardsPanel*> (GetParent());
if (panel) {
panel->setFocusedEditor(this);
}
}
void DataEditor::onLoseFocus(wxFocusEvent& ev) {
if (current_editor) {
@@ -520,6 +542,7 @@ BEGIN_EVENT_TABLE(DataEditor, CardViewer)
EVT_RIGHT_DOWN (DataEditor::onRightDown)
EVT_MOTION (DataEditor::onMotion)
EVT_MOUSEWHEEL (DataEditor::onMouseWheel)
EVT_ENTER_WINDOW (DataEditor::onMouseEnter)
EVT_LEAVE_WINDOW (DataEditor::onMouseLeave)
EVT_CONTEXT_MENU (DataEditor::onContextMenu)
EVT_MENU (wxID_ANY, DataEditor::onMenu)
+1
View File
@@ -111,6 +111,7 @@ private:
void onRightDown (wxMouseEvent&);
void onMotion (wxMouseEvent&);
void onMouseWheel(wxMouseEvent&);
void onMouseEnter(wxMouseEvent&);
void onMouseLeave(wxMouseEvent&);
void onLoseCapture(wxMouseCaptureLostEvent&);
+48
View File
@@ -10,6 +10,7 @@
#include <gui/control/card_list.hpp>
#include <gui/control/card_list_column_select.hpp>
#include <gui/set/window.hpp> // for sorting all cardlists in a window
#include <gui/card_link_window.hpp>
#include <gui/util.hpp>
#include <gui/add_csv_window.hpp>
#include <gui/add_json_window.hpp>
@@ -25,6 +26,7 @@
#include <data/action/value.hpp>
#include <util/window_id.hpp>
#include <wx/clipbrd.h>
#include <unordered_set>
DECLARE_POINTER_TYPE(ChoiceValue);
@@ -157,6 +159,31 @@ bool CardListBase::doCopy() {
wxTheClipboard->Close();
return ok;
}
bool CardListBase::doCopyCardAndLinkedCards() {
if (!canCopy()) return false;
vector<CardP> cards_selected;
getSelection(cards_selected);
if (cards_selected.size() < 1) return false;
if (!wxTheClipboard->Open()) return false;
vector<CardP> cards_to_copy;
unordered_set<CardP> cards_already_added;
FOR_EACH(card, cards_selected) {
if (cards_already_added.find(card) == cards_already_added.end()) {
cards_to_copy.push_back(card);
cards_already_added.insert(card);
}
vector<pair<CardP, String>> linked_cards = card->getLinkedCards(*set);
FOR_EACH(linked_card, linked_cards) {
if (cards_already_added.find(linked_card.first) == cards_already_added.end()) {
cards_to_copy.push_back(linked_card.first);
cards_already_added.insert(linked_card.first);
}
}
}
bool ok = wxTheClipboard->SetData(new CardsOnClipboard(set, cards_to_copy)); // ignore result
wxTheClipboard->Close();
return ok;
}
bool CardListBase::doPaste() {
// get data
if (!canPaste()) return false;
@@ -183,6 +210,25 @@ bool CardListBase::doDelete() {
return true;
}
// --------------------------------------------------- : CardListBase : Card linking
bool CardListBase::canLink() const {
vector<CardP> selected_cards;
getSelection(selected_cards);
return selected_cards.size() == 1;
}
bool CardListBase::doLink() {
CardLinkWindow wnd(this, set, getCard());
if (wnd.ShowModal() == wxID_OK) {
// The actual linking is done in this window's onOk function
return true;
}
return false;
}
bool CardListBase::doUnlink(CardP unlinked_card) {
set->actions.addAction(make_unique<UnlinkCardsAction>(*set, getCard(), unlinked_card));
return true;
}
bool CardListBase::doAddCSV() {
AddCSVWindow wnd(this, set, true);
if (wnd.ShowModal() == wxID_OK) {
@@ -412,10 +458,12 @@ void CardListBase::onContextMenu(wxContextMenuEvent&) {
wxMenu m;
add_menu_item_tr(&m, wxID_CUT, "cut", "cut_card");
add_menu_item_tr(&m, wxID_COPY, "copy", "copy_card");
add_menu_item_tr(&m, ID_CARD_AND_LINK_COPY, "card_copy", "copy card and links");
add_menu_item_tr(&m, wxID_PASTE, "paste", "paste_card");
m.AppendSeparator();
add_menu_item_tr(&m, ID_CARD_ADD, "card_add", "add card");
add_menu_item_tr(&m, ID_CARD_REMOVE, "card_del", "remove card");
add_menu_item_tr(&m, ID_CARD_LINK, "card_link", "link card");
PopupMenu(&m);
}
}
+9 -2
View File
@@ -65,7 +65,7 @@ public:
// --------------------------------------------------- : Selection
inline CardP getCard() const { return static_pointer_cast<Card>(selected_item); }
inline void setCard(const CardP& card) { selectItem(card, true, false); }
inline void setCard(const CardP& card, bool event = false) { selectItem(card, true, event); }
// --------------------------------------------------- : Clipboard
@@ -75,11 +75,18 @@ public:
bool canDelete() const override;
// Try to perform a clipboard operation, return success
bool doCopy() override;
bool doCopyCardAndLinkedCards();
bool doPaste() override;
bool doDelete() override;
bool doAddCSV();
bool doAddJSON();
// --------------------------------------------------- : Card linking
bool canLink() const;
bool doLink();
bool doUnlink(CardP unlinked_card);
// --------------------------------------------------- : Set actions
void onBeforeChangeSet() override;
@@ -107,7 +114,7 @@ protected:
/// Send an 'item selected' event for the currently selected item (selected_item)
void sendEvent() override { sendEvent(EVENT_CARD_SELECT); }
void sendEvent(int type = EVENT_CARD_SELECT);
void sendEvent(int type);
/// Compare cards
bool compareItems(void* a, void* b) const override;
+23 -2
View File
@@ -8,10 +8,13 @@
#include <util/prec.hpp>
#include <gui/control/card_viewer.hpp>
#include <gui/control/image_card_list.hpp>
#include <gui/set/cards_panel.hpp>
#include <data/stylesheet.hpp>
#include <data/settings.hpp>
#include <render/value/viewer.hpp>
#include <wx/dcbuffer.h>
#include <util/window_id.hpp>
// ----------------------------------------------------------------------------- : Events
@@ -31,7 +34,11 @@ wxSize CardViewer::DoGetBestSize() const {
if (set) {
if (!stylesheet) stylesheet = set->stylesheet;
StyleSheetSettings& ss = settings.stylesheetSettingsFor(*stylesheet);
wxSize size(int(stylesheet->card_width * (150.0 / stylesheet->card_dpi) * ss.card_zoom()), int(stylesheet->card_height * (150.0 / stylesheet->card_dpi) * ss.card_zoom()));
double dpi_factor = stylesheet->card_dpi <= 150.0 ? 1.0 : 150.0 / stylesheet->card_dpi;
double width = stylesheet->card_width * dpi_factor * ss.card_zoom();
double height = stylesheet->card_height * dpi_factor * ss.card_zoom();
double link_factor = GetId() == ID_CARD_LINK_VIEWER ? (height * 0.5 - 41.0) / height : GetId() == ID_CARD_LINK_EDITOR ? (height * 0.97 - 41.0) / height : 1.0; // Subtract 41 pixels for the link title
wxSize size(int(link_factor * width), int(link_factor * height));
if (is_sideways(deg_to_rad(ss.card_angle()))) swap(size.x, size.y);
return size + ws - cs;
}
@@ -104,6 +111,16 @@ void CardViewer::onPaint(wxPaintEvent&) {
}
}
void CardViewer::onClick(wxMouseEvent& ev) {
ev.Skip(); // allow DataEditor::onLeftDown to process this event as well
if (GetId() == ID_CARD_LINK_VIEWER) {
CardsPanel* panel = dynamic_cast<CardsPanel*> (GetParent());
if (panel) {
panel->setCard(getCard(), true);
}
}
}
void CardViewer::drawViewer(RotatedDC& dc, ValueViewer& v) {
if (shouldDraw(v)) v.draw(dc);
}
@@ -150,11 +167,15 @@ Rotation CardViewer::getRotation() const {
StyleSheetSettings& ss = settings.stylesheetSettingsFor(*stylesheet);
int dx = CanScroll(wxHORIZONTAL) ? GetScrollPos(wxHORIZONTAL) : 0;
int dy = CanScroll(wxVERTICAL) ? GetScrollPos(wxVERTICAL) : 0;
return Rotation(deg_to_rad(ss.card_angle()), stylesheet->getCardRect().move(-dx,-dy,0,0), (150.0 / stylesheet->card_dpi) * ss.card_zoom(), 1.0, ROTATION_ATTACH_TOP_LEFT);
double dpi_factor = stylesheet->card_dpi <= 150.0 ? 1.0 : 150.0 / stylesheet->card_dpi;
double height = stylesheet->card_height * dpi_factor * ss.card_zoom();
double link_factor = GetId() == ID_CARD_LINK_VIEWER ? (height * 0.5 - 41.0) / height : GetId() == ID_CARD_LINK_EDITOR ? (height * 0.97 - 41.0) / height : 1.0; // Subtract 41 pixels for the link title
return Rotation(deg_to_rad(ss.card_angle()), stylesheet->getCardRect().move(-dx,-dy,0,0), link_factor * dpi_factor * ss.card_zoom(), 1.0, ROTATION_ATTACH_TOP_LEFT);
}
// ----------------------------------------------------------------------------- : Event table
BEGIN_EVENT_TABLE(CardViewer, wxControl)
EVT_PAINT(CardViewer::onPaint)
EVT_LEFT_DOWN(CardViewer::onClick)
END_EVENT_TABLE ()
+2
View File
@@ -56,6 +56,8 @@ private:
void onPaint(wxPaintEvent&);
void onClick(wxMouseEvent&);
Bitmap buffer; ///< Off-screen buffer we draw to
bool up_to_date; ///< Is the buffer up to date?
+9 -1
View File
@@ -79,7 +79,7 @@ void ItemList::selectItem(const VoidP& item, bool focus, bool event) {
focusNone();
}
selected_item = item;
if (event) sendEvent();
if (event) sendEvent(); // sending an event will trigger a UI update
findSelectedItemPos();
if (focus) focusSelectedItem();
}
@@ -111,6 +111,14 @@ void ItemList::findSelectedItemPos() {
}
}
}
long ItemList::findGivenItemPos(const VoidP& item) {
long count = GetItemCount();
for (long pos = 0; pos < count; ++pos) {
if (getItem(pos) == item) {
return pos;
}
}
}
void ItemList::focusSelectedItem(bool force_focus) {
if (GetItemCount() > 0) {
if (selected_item_pos == -1 || (size_t)selected_item_pos > sorted_list.size()) {
+2
View File
@@ -42,6 +42,8 @@ public:
void selectFirst();
/// Select all items
void doSelectAll();
/// Find the position for a given item
long findGivenItemPos(const VoidP& item);
// --------------------------------------------------- : Clipboard
+75 -8
View File
@@ -15,13 +15,16 @@
#include <wx/spinctrl.h>
#include <wx/dcbuffer.h>
map<String, String> ImageSliceWindow::previously_used_settings_path;
map<pair<String, String>, pair<wxRect, int>> ImageSliceWindow::previously_used_settings_value;
// ----------------------------------------------------------------------------- : ImageSlice
ImageSlice::ImageSlice(const Image& source, const wxSize& target_size)
: source(source), target_size(target_size)
ImageSlice::ImageSlice(const Image& source, const String& source_path, const String& card_name, const wxSize& target_size)
: source(source), source_path(source_path), card_name(card_name), target_size(target_size)
, selection(0, 0, source.GetWidth(), source.GetHeight())
, allow_outside(false), aspect_fixed(true)
, sharpen(false), sharpen_amount(25)
, sharpen(false), sharpen_amount(0)
{}
void ImageSlice::constrain(PreferedProperty prefer) {
@@ -93,20 +96,31 @@ DEFINE_EVENT_TYPE(EVENT_SLICE_CHANGED);
// ----------------------------------------------------------------------------- : ImageSliceWindow
ImageSliceWindow::ImageSliceWindow(Window* parent, const Image& source, const wxSize& target_size, const AlphaMask& mask)
ImageSliceWindow::ImageSliceWindow(Window* parent, const Image& source, const String& filename, const String& cardname, const wxSize& target_size, const AlphaMask& mask)
: wxDialog(parent,wxID_ANY,_TITLE_("slice image"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxFULL_REPAINT_ON_RESIZE)
, slice(source, target_size)
, slice(source, filename, cardname, target_size)
, initialized(false)
{
// init slice
pair<String, String> settings_entry = { filename, cardname };
if (previously_used_settings_value.find(settings_entry) != previously_used_settings_value.end()) {
//slice.allow_outside = true; this currrently crashes
slice.aspect_fixed = false;
slice.sharpen = true;
slice.sharpen_amount = previously_used_settings_value[settings_entry].second;
slice.selection = previously_used_settings_value[settings_entry].first;
slice.constrain();
}
else {
slice.constrain();
slice.centerSelection();
}
// init controls
const wxPoint defPos = wxDefaultPosition;
const wxSize spinSize(80,-1);
selector = new ImageSliceSelector(this, ID_SELECTOR, slice);
preview = new ImageSlicePreview (this, ID_PREVIEW, slice, mask);
preview = new ImageSlicePreview (this, ID_PREVIEW, slice, mask, 0);
String sizes[] = { _LABEL_("original size")
, _LABEL_("size to fit")
@@ -136,6 +150,13 @@ ImageSliceWindow::ImageSliceWindow(Window* parent, const Image& source, const wx
// allowOutside= new CheckBox(&this, idSliceAllowOutside, _("Allow selection outside source"))
// bgColor = new ColorSelector(&this, wxID_ANY)
String grids[] = { _LABEL_("none")
, _LABEL_("grid halves")
, _LABEL_("grid thirds")
, _LABEL_("grid fourths")
, _LABEL_("grid fifths") };
grid = new wxRadioBox(this, ID_GRID, _LABEL_("grid"), defPos, wxDefaultSize, 5, grids, 1);
// init sizers
wxSizer* s = new wxBoxSizer(wxVERTICAL);
// top row: image editors
@@ -206,6 +227,8 @@ ImageSliceWindow::ImageSliceWindow(Window* parent, const Image& source, const wx
sB->Add(sharpen_amount, 0, wxEXPAND | wxALL, 4);
s5->Add(sB, 0, wxEXPAND | wxALL, 4);
s5->AddStretchSpacer(1);
s5->Add(grid, 0, wxEXPAND | wxALL, 4);
s5->AddStretchSpacer(1);
s->Add(s5, 0, wxEXPAND);
s->Add(CreateButtonSizer(wxOK | wxCANCEL), 0, wxEXPAND | wxALL, 8);
s->SetSizeHints(this);
@@ -220,7 +243,10 @@ void ImageSliceWindow::onOk(wxCommandEvent&) {
}
Image ImageSliceWindow::getImage(double scale) const {
return slice.getSlice(scale);
Image img = slice.getSlice(scale);
previously_used_settings_path[slice.card_name] = slice.source_path;
previously_used_settings_value[{ slice.source_path, slice.card_name }] = { slice.selection, slice.sharpen_amount };
return img;
}
// ----------------------------------------------------------------------------- : ImageSliceWindow : Controls
@@ -251,6 +277,12 @@ void ImageSliceWindow::onChangeSize(wxCommandEvent&) {
}
}
void ImageSliceWindow::onChangeGrid(wxCommandEvent&) {
if (!initialized) return;
preview->grid = grid->GetSelection();
preview->update();
}
void ImageSliceWindow::onChangeLeft(wxCommandEvent&) {
if (!initialized) return;
slice.selection.x = left->GetValue();
@@ -386,6 +418,7 @@ void ImageSliceWindow::updateControls() {
BEGIN_EVENT_TABLE(ImageSliceWindow, wxDialog)
EVT_BUTTON (wxID_OK, ImageSliceWindow::onOk)
EVT_RADIOBOX (ID_SIZE, ImageSliceWindow::onChangeSize)
EVT_RADIOBOX (ID_GRID, ImageSliceWindow::onChangeGrid)
EVT_TEXT (ID_LEFT, ImageSliceWindow::onChangeLeft)
EVT_TEXT (ID_TOP, ImageSliceWindow::onChangeTop)
EVT_TEXT (ID_WIDTH, ImageSliceWindow::onChangeWidth)
@@ -409,10 +442,11 @@ END_EVENT_TABLE ()
// ----------------------------------------------------------------------------- : ImageSlicePreview
ImageSlicePreview::ImageSlicePreview(Window* parent, int id, ImageSlice& slice, const AlphaMask& mask)
ImageSlicePreview::ImageSlicePreview(Window* parent, int id, ImageSlice& slice, const AlphaMask& mask, const int grid)
: wxControl(parent, id, wxDefaultPosition, wxDefaultSize, wxBORDER_THEME)
, slice(slice)
, mask(mask)
, grid(grid)
, mouse_down(false)
{
SetBackgroundStyle(wxBG_STYLE_PAINT);
@@ -469,6 +503,39 @@ void ImageSlicePreview::draw(DC& dc) {
}
if (bitmap.Ok()) {
dc.DrawBitmap(bitmap, 0, 0);
if (grid == 1) {
wxSize size = dc.GetSize();
dc.SetPen(*wxRED_PEN);
dc.DrawLine(size.x * 1 / 2, 0, size.x * 1 / 2, size.y);
dc.DrawLine(0, size.y * 1 / 2, size.x, size.y * 1 / 2);
} else if (grid == 2) {
wxSize size = dc.GetSize();
dc.SetPen(*wxRED_PEN);
dc.DrawLine(size.x * 1 / 3, 0, size.x * 1 / 3, size.y);
dc.DrawLine(size.x * 2 / 3, 0, size.x * 2 / 3, size.y);
dc.DrawLine(0, size.y * 1 / 3, size.x, size.y * 1 / 3);
dc.DrawLine(0, size.y * 2 / 3, size.x, size.y * 2 / 3);
} else if (grid == 3) {
wxSize size = dc.GetSize();
dc.SetPen(*wxRED_PEN);
dc.DrawLine(size.x * 1 / 4, 0, size.x * 1 / 4, size.y);
dc.DrawLine(size.x * 2 / 4, 0, size.x * 2 / 4, size.y);
dc.DrawLine(size.x * 3 / 4, 0, size.x * 3 / 4, size.y);
dc.DrawLine(0, size.y * 1 / 4, size.x, size.y * 1 / 4);
dc.DrawLine(0, size.y * 2 / 4, size.x, size.y * 2 / 4);
dc.DrawLine(0, size.y * 3 / 4, size.x, size.y * 3 / 4);
} else if (grid == 4) {
wxSize size = dc.GetSize();
dc.SetPen(*wxRED_PEN);
dc.DrawLine(size.x * 1 / 5, 0, size.x * 1 / 5, size.y);
dc.DrawLine(size.x * 2 / 5, 0, size.x * 2 / 5, size.y);
dc.DrawLine(size.x * 3 / 5, 0, size.x * 3 / 5, size.y);
dc.DrawLine(size.x * 4 / 5, 0, size.x * 4 / 5, size.y);
dc.DrawLine(0, size.y * 1 / 5, size.x, size.y * 1 / 5);
dc.DrawLine(0, size.y * 2 / 5, size.x, size.y * 2 / 5);
dc.DrawLine(0, size.y * 3 / 5, size.x, size.y * 3 / 5);
dc.DrawLine(0, size.y * 4 / 5, size.x, size.y * 4 / 5);
}
}
}
+17 -6
View File
@@ -28,14 +28,17 @@ enum PreferedProperty
/// A slice of an image, i.e. a selected rectangle
class ImageSlice {
public:
ImageSlice(const Image& source, const wxSize& target_size);
ImageSlice(const Image& source, const String& source_path, const String& card_name, const wxSize& target_size);
Image source; ///< The source image
String source_path; ///< The filename of the source image (only used to find previously used settings)
String card_name; ///< The identification of the card we're on (only used to find previously used settings)
wxSize target_size; ///< Size of the target image
wxRect selection; ///< Area to slice from source
Color background; ///< Color for areas outside the source image
wxRect selection; ///< Area to slect from source
bool allow_outside;
bool allow_outside; ///< Allow the slice to extend outside the source image?
bool aspect_fixed; ///< Aspect ratio lock?
// Filters
bool sharpen;
int sharpen_amount;
@@ -62,11 +65,16 @@ public:
/// Dialog for selecting a slice of an image
class ImageSliceWindow : public wxDialog {
public:
ImageSliceWindow(Window* parent, const Image& source, const wxSize& target_size, const AlphaMask& target_mask);
ImageSliceWindow(Window* parent, const Image& source, const String& filename, const String& cardname, const wxSize& target_size, const AlphaMask& target_mask);
/// Return the sliced image
Image getImage(double scale) const;
// --------------------------------------------------- : Previously Used Settings
static map<String, String> previously_used_settings_path; // map from cardname to filename
static map<pair<String, String>, pair<wxRect, int>> previously_used_settings_value; // map from filename+cardname pair to settings
// --------------------------------------------------- : Data
private:
// The slice we are extracting
@@ -74,7 +82,7 @@ private:
// Gui items
ImageSlicePreview* preview;
ImageSliceSelector* selector;
wxRadioBox* size;
wxRadioBox* size, *grid;
wxSpinCtrl* top, *left, *width, *height;
wxCheckBox* fix_aspect;
wxSpinCtrl* zoom, *zoom_x, *zoom_y;
@@ -91,6 +99,7 @@ private:
void onSize (wxSizeEvent&);
void onChangeSize (wxCommandEvent&);
void onChangeGrid (wxCommandEvent&);
void onChangeLeft (wxCommandEvent&);
void onChangeTop (wxCommandEvent&);
void onChangeWidth (wxCommandEvent&);
@@ -120,11 +129,13 @@ private:
/// A preview of the sliced image
class ImageSlicePreview : public wxControl {
public:
ImageSlicePreview(Window* parent, int id, ImageSlice& slice, const AlphaMask& mask);
ImageSlicePreview(Window* parent, int id, ImageSlice& slice, const AlphaMask& mask, const int grid);
/// Notify that the slice was updated
void update();
int grid;
// --------------------------------------------------- : Data
private:
Bitmap bitmap;
+5 -5
View File
@@ -227,14 +227,14 @@ DisplayPreferencesPage::DisplayPreferencesPage(Window* parent)
non_normal_export->SetValue(!settings.default_stylesheet_settings.card_normal_export());
zoom_int = static_cast<int>(settings.default_stylesheet_settings.card_zoom() * 100);
zoom->SetValue(String::Format(_("%d%%"),zoom_int));
int choices[] = { 50,66,75,100,120,150,175,200 };
for (unsigned int i = 0 ; i < sizeof(choices)/sizeof(choices[0]) ; ++i) {
zoom->Append(String::Format(_("%d%%"),choices[i]));
int zoom_choices[] = { 50,66,75,80,100,120,125,150,175,200 };
for (unsigned int i = 0 ; i < sizeof(zoom_choices)/sizeof(zoom_choices[0]) ; ++i) {
zoom->Append(String::Format(_("%d%%"), zoom_choices[i]));
}
export_zoom_int = static_cast<int>(settings.default_stylesheet_settings.export_zoom() * 100);
export_zoom->SetValue(String::Format(_("%d%%"), export_zoom_int));
int export_choices[] = { 50,66,75,100,120,150,175,200 };
int export_choices[] = { 50,66,75,80,100,120,125,150,175,200 };
for (unsigned int i = 0; i < sizeof(export_choices) / sizeof(export_choices[0]); ++i) {
export_zoom->Append(String::Format(_("%d%%"), export_choices[i]));
}
@@ -328,7 +328,7 @@ InternalPreferencesPage::InternalPreferencesPage(Window* parent) : PreferencesPa
internal_scale_int = static_cast<int>(settings.internal_scale * 100);
internal_scale->SetValue(String::Format(_("%d%%"), internal_scale_int));
int choices[] = { 100,200,300,400 };
int choices[] = { 100,120,125,150,175,200 };
for (unsigned int i = 0; i < sizeof(choices) / sizeof(choices[0]); ++i) {
internal_scale->Append(String::Format(_("%d%%"), choices[i]));
}
+192 -10
View File
@@ -25,6 +25,7 @@
#include <util/tagged_string.hpp>
#include <util/window_id.hpp>
#include <wx/splitter.h>
#include <wx/gbsizer.h>
// ----------------------------------------------------------------------------- : CardsPanel
@@ -33,6 +34,21 @@ CardsPanel::CardsPanel(Window* parent, int id)
{
// init controls
editor = new CardEditor(this, ID_EDITOR);
link_editor = new CardEditor(this, ID_CARD_LINK_EDITOR);
focused_editor = editor;
link_viewer_1 = new CardViewer(this, ID_CARD_LINK_VIEWER);
link_viewer_2 = new CardViewer(this, ID_CARD_LINK_VIEWER);
link_viewer_3 = new CardViewer(this, ID_CARD_LINK_VIEWER);
link_viewer_4 = new CardViewer(this, ID_CARD_LINK_VIEWER);
link_relation_1 = new wxStaticText(this, ID_CARD_LINK_RELATION_1, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxST_ELLIPSIZE_END);
link_relation_2 = new wxStaticText(this, ID_CARD_LINK_RELATION_2, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxST_ELLIPSIZE_END);
link_relation_3 = new wxStaticText(this, ID_CARD_LINK_RELATION_3, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxST_ELLIPSIZE_END);
link_relation_4 = new wxStaticText(this, ID_CARD_LINK_RELATION_4, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxST_ELLIPSIZE_END);
link_select = new wxButton(this, ID_CARD_LINK_SELECT, _BUTTON_("link select"), wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT);
link_unlink_1 = new wxButton(this, ID_CARD_LINK_UNLINK_1, _BUTTON_("unlink"), wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT);
link_unlink_2 = new wxButton(this, ID_CARD_LINK_UNLINK_2, _BUTTON_("unlink"), wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT);
link_unlink_3 = new wxButton(this, ID_CARD_LINK_UNLINK_3, _BUTTON_("unlink"), wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT);
link_unlink_4 = new wxButton(this, ID_CARD_LINK_UNLINK_4, _BUTTON_("unlink"), wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT);
splitter = new wxSplitterWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
card_list = new FilteredImageCardList(splitter, ID_CARD_LIST);
nodes_panel = new wxPanel(splitter, wxID_ANY);
@@ -41,6 +57,12 @@ CardsPanel::CardsPanel(Window* parent, int id)
collapse_notes->SetExtraStyle(wxWS_EX_PROCESS_UI_UPDATES);
filter = nullptr;
editor->next_in_tab_order = card_list;
wxFont font = link_relation_1->GetFont();
font.SetWeight(wxFONTWEIGHT_BOLD);
link_relation_1->SetFont(font);
link_relation_2->SetFont(font);
link_relation_3->SetFont(font);
link_relation_4->SetFont(font);
// init sizer for notes panel
wxSizer* sn = new wxBoxSizer(wxVERTICAL);
wxSizer* sc = new wxBoxSizer(wxHORIZONTAL);
@@ -54,10 +76,46 @@ CardsPanel::CardsPanel(Window* parent, int id)
splitter->SetSashGravity(1.0);
splitter->SplitHorizontally(card_list, nodes_panel, -40);
notes_below_editor = false;
// init sizer
wxSizer* s = new wxBoxSizer(wxHORIZONTAL);
s_left = new wxBoxSizer(wxVERTICAL);
s_left->Add(editor);
// init sizer for editors and viewers
wxSizer* s = new wxBoxSizer(wxHORIZONTAL); // Global Sizer
s_left = new wxBoxSizer(wxVERTICAL); // Sizer for the selected card, and it's linked cards
wxSizer* card_and_link = new wxBoxSizer(wxHORIZONTAL);
s_left->Add(card_and_link);
card_and_link->Add(editor);
wxGridBagSizer* link_boxes = new wxGridBagSizer(); // Sizer for the linked cards
card_and_link->Add(link_boxes, 0, wxLEFT, 2);
link_box_1 = new wxStaticBoxSizer(wxVERTICAL, this); // Box around the first linked card, it's relation, and buttons to select and unlink
link_boxes->Add(link_box_1, wxGBPosition(0, 0), wxGBSpan(1, 1));
link_box_2 = new wxStaticBoxSizer(wxVERTICAL, this); // Box around the second linked card, it's relation, and a button to unlink
link_boxes->Add(link_box_2, wxGBPosition(1, 0), wxGBSpan(1, 1));
link_box_3 = new wxStaticBoxSizer(wxVERTICAL, this); // Box around the third linked card, it's relation, and a button to unlink
link_boxes->Add(link_box_3, wxGBPosition(0, 1), wxGBSpan(1, 1));
link_box_4 = new wxStaticBoxSizer(wxVERTICAL, this); // Box around the fourth linked card, it's relation, and a button to unlink
link_boxes->Add(link_box_4, wxGBPosition(1, 1), wxGBSpan(1, 1));
wxGridBagSizer* link_grid_1 = new wxGridBagSizer(); // Sizer for the first linked card, with it's relation, and a button to unlink
link_box_1->Add(link_grid_1);
wxGridBagSizer* link_grid_2 = new wxGridBagSizer();
link_box_2->Add(link_grid_2);
wxGridBagSizer* link_grid_3 = new wxGridBagSizer();
link_box_3->Add(link_grid_3);
wxGridBagSizer* link_grid_4 = new wxGridBagSizer();
link_box_4->Add(link_grid_4);
wxSizer* link_grid_1_buttons = new wxBoxSizer(wxHORIZONTAL);
link_grid_1->Add(link_relation_1, wxGBPosition(0, 0), wxGBSpan(1, 1), wxALIGN_CENTER_VERTICAL | wxLEFT, 4);
link_grid_1->Add(link_viewer_1, wxGBPosition(1, 0), wxGBSpan(1, 2));
link_grid_1->Add(link_editor, wxGBPosition(2, 0), wxGBSpan(1, 2));
link_grid_1->Add(link_grid_1_buttons, wxGBPosition(0, 1), wxGBSpan(1, 1), wxALIGN_RIGHT);
link_grid_1_buttons->Add(link_select);
link_grid_1_buttons->Add(link_unlink_1);
link_grid_2->Add(link_relation_2, wxGBPosition(0, 0), wxGBSpan(1, 1), wxALIGN_CENTER_VERTICAL | wxLEFT, 4);
link_grid_2->Add(link_unlink_2, wxGBPosition(0, 1), wxGBSpan(1, 1), wxALIGN_RIGHT);
link_grid_2->Add(link_viewer_2, wxGBPosition(1, 0), wxGBSpan(1, 2));
link_grid_3->Add(link_relation_3, wxGBPosition(0, 0), wxGBSpan(1, 1), wxALIGN_CENTER_VERTICAL | wxLEFT, 4);
link_grid_3->Add(link_unlink_3, wxGBPosition(0, 1), wxGBSpan(1, 1), wxALIGN_RIGHT);
link_grid_3->Add(link_viewer_3, wxGBPosition(1, 0), wxGBSpan(1, 2));
link_grid_4->Add(link_relation_4, wxGBPosition(0, 0), wxGBSpan(1, 1), wxALIGN_CENTER_VERTICAL | wxLEFT, 4);
link_grid_4->Add(link_unlink_4, wxGBPosition(0, 1), wxGBSpan(1, 1), wxALIGN_RIGHT);
link_grid_4->Add(link_viewer_4, wxGBPosition(1, 0), wxGBSpan(1, 2));
s->Add(s_left, 0, wxEXPAND | wxRIGHT, 2);
s->Add(splitter, 1, wxEXPAND);
s->SetSizeHints(this);
@@ -77,6 +135,8 @@ CardsPanel::CardsPanel(Window* parent, int id)
add_menu_item(menuCard, ID_CARD_ADD_JSON, "card_add_multiple", _MENU_("add card json") + _(" "), _HELP_("add card json"));
add_menu_item_tr(menuCard, ID_CARD_ADD, "card_add", "add_card");
add_menu_item(menuCard, ID_CARD_REMOVE, "card_del", _MENU_("remove card")+_(" "), _HELP_("remove card"));
add_menu_item(menuCard, ID_CARD_LINK, "card_link", _MENU_("link card") + _(" "), _HELP_("link card"));
add_menu_item(menuCard, ID_CARD_AND_LINK_COPY, "card_copy", _MENU_("copy card and links") + _(" "), _HELP_("copy card and links"));
menuCard->AppendSeparator();
auto menuRotate = new wxMenu();
add_menu_item_tr(menuRotate, ID_CARD_ROTATE_0, "card_rotate_0", "rotate_0", wxITEM_CHECK);
@@ -177,6 +237,11 @@ CardsPanel::~CardsPanel() {
void CardsPanel::onChangeSet() {
editor->setSet(set);
link_editor->setSet(set);
link_viewer_1->setSet(set);
link_viewer_2->setSet(set);
link_viewer_3->setSet(set);
link_viewer_4->setSet(set);
notes->setSet(set);
card_list->setSet(set);
@@ -226,6 +291,7 @@ void CardsPanel::initUI(wxToolBar* tb, wxMenuBar* mb) {
toolAddCard = add_tool_tr(tb, ID_CARD_ADD, "card_add", "add_card", false, wxITEM_DROPDOWN);
tb->SetDropdownMenu(ID_CARD_ADD, makeAddCardsSubmenu(true));
add_tool_tr(tb, ID_CARD_REMOVE, "card_del", "remove_card");
add_tool_tr(tb, ID_CARD_LINK, "card_link", "link_card");
tb->AddSeparator();
add_tool_tr(tb, ID_CARD_ROTATE, "card_rotate", "rotate_card", false, wxITEM_DROPDOWN);
auto menuRotate = new wxMenu();
@@ -258,6 +324,7 @@ void CardsPanel::destroyUI(wxToolBar* tb, wxMenuBar* mb) {
tb->DeleteTool(ID_FORMAT_REMINDER);
tb->DeleteTool(ID_CARD_ADD);
tb->DeleteTool(ID_CARD_REMOVE);
tb->DeleteTool(ID_CARD_LINK);
tb->DeleteTool(ID_CARD_ROTATE);
tb->DeleteTool(ID_CARD_COUNTER);
// remember the value in the filter control, because the card list remains filtered
@@ -293,10 +360,15 @@ void CardsPanel::onUpdateUI(wxUpdateUIEvent& ev) {
break;
}
case ID_CARD_REMOVE: ev.Enable(card_list->canDelete()); break;
case ID_CARD_LINK: ev.Enable(card_list->canLink()); break;
case ID_CARD_AND_LINK_COPY: ev.Enable(card_list->canCopy()); break;
case ID_FORMAT_BOLD: case ID_FORMAT_ITALIC: case ID_FORMAT_UNDERLINE: case ID_FORMAT_SYMBOL: case ID_FORMAT_REMINDER: {
if (focused_control(this) == ID_EDITOR) {
ev.Enable(editor->canFormat(ev.GetId()));
ev.Check (editor->hasFormat(ev.GetId()));
} else if (focused_control(this) == ID_CARD_LINK_EDITOR) {
ev.Enable(link_editor->canFormat(ev.GetId()));
ev.Check (link_editor->hasFormat(ev.GetId()));
} else {
ev.Enable(false);
ev.Check(false);
@@ -313,7 +385,7 @@ void CardsPanel::onUpdateUI(wxUpdateUIEvent& ev) {
case ID_INSERT_SYMBOL: ev.Enable(false); break;
#else
case ID_INSERT_SYMBOL: {
wxMenu* menu = editor->getMenu(ID_INSERT_SYMBOL);
wxMenu* menu = focused_editor->getMenu(ID_INSERT_SYMBOL);
ev.Enable(menu);
break;
}
@@ -324,7 +396,7 @@ void CardsPanel::onUpdateUI(wxUpdateUIEvent& ev) {
void CardsPanel::onMenuOpen(wxMenuEvent& ev) {
if (ev.GetMenu() != menuFormat) return;
wxMenu* menu = editor->getMenu(ID_INSERT_SYMBOL);
wxMenu* menu = focused_editor->getMenu(ID_INSERT_SYMBOL);
if (insertSymbolMenu->GetSubMenu() != menu || (menu && menu->GetParent() != menuFormat)) {
// re-add the menu
menuFormat->Remove(ID_INSERT_SYMBOL);
@@ -358,6 +430,27 @@ void CardsPanel::onCommand(int id) {
case ID_CARD_REMOVE:
card_list->doDelete();
break;
case ID_CARD_LINK:
card_list->doLink();
setCard(card_list->getCard(), true);
break;
case ID_CARD_LINK_UNLINK_1: case ID_CARD_LINK_UNLINK_2: case ID_CARD_LINK_UNLINK_3: case ID_CARD_LINK_UNLINK_4: {
card_list->doUnlink((
id == ID_CARD_LINK_UNLINK_1 ? link_viewer_1
: id == ID_CARD_LINK_UNLINK_2 ? link_viewer_2
: id == ID_CARD_LINK_UNLINK_3 ? link_viewer_3
: link_viewer_4
)->getCard());
setCard(card_list->getCard(), true);
break;
}
case ID_CARD_LINK_SELECT: {
setCard(link_viewer_1->getCard(), true);
break;
}
case ID_CARD_AND_LINK_COPY:
card_list->doCopyCardAndLinkedCards();
break;
case ID_CARD_ROTATE:
case ID_CARD_ROTATE_0: case ID_CARD_ROTATE_90: case ID_CARD_ROTATE_180: case ID_CARD_ROTATE_270: {
StyleSheetSettings& ss = settings.stylesheetSettingsFor(set->stylesheetFor(card_list->getCard()));
@@ -378,6 +471,9 @@ void CardsPanel::onCommand(int id) {
if (focused_control(this) == ID_EDITOR) {
editor->doFormat(id);
}
else if (focused_control(this) == ID_CARD_LINK_EDITOR) {
link_editor->doFormat(id);
}
break;
}
case ID_COLLAPSE_NOTES: {
@@ -399,7 +495,7 @@ void CardsPanel::onCommand(int id) {
default: {
if (id >= ID_INSERT_SYMBOL_MENU_MIN && id <= ID_INSERT_SYMBOL_MENU_MAX) {
// pass on to editor
editor->onCommand(id);
focused_editor->onCommand(id);
} else if (id >= ID_ADD_CARDS_MENU_MIN && id <= ID_ADD_CARDS_MENU_MAX) {
// add multiple cards
AddCardsScriptP script = set->game->add_cards_scripts.at(id - ID_ADD_CARDS_MENU_MIN);
@@ -421,6 +517,7 @@ bool CardsPanel::wantsToHandle(const Action&, bool undone) const {
#define CUT_COPY_PASTE(op,return) \
int id = focused_control(this); \
if (id == ID_EDITOR) { return editor->op(); } \
else if (id == ID_CARD_LINK_EDITOR) { return link_editor->op(); } \
else if (id == ID_CARD_LIST) { return card_list->op(); } \
else if (id == ID_NOTES) { return notes->op(); } \
else { return false; }
@@ -435,6 +532,7 @@ bool CardsPanel::canPaste() const {
if (card_list->canPaste()) return true;
int id = focused_control(this);
if (id == ID_EDITOR) return editor->canPaste();
else if (id == ID_CARD_LINK_EDITOR) return link_editor->canPaste();
else if (id == ID_NOTES) return notes->canPaste();
else return false;
}
@@ -444,6 +542,7 @@ void CardsPanel::doPaste() {
} else {
int id = focused_control(this);
if (id == ID_EDITOR) editor->doPaste();
else if (id == ID_CARD_LINK_EDITOR) link_editor->doPaste();
else if (id == ID_NOTES) notes->doPaste();
}
}
@@ -465,7 +564,7 @@ public:
SearchFindInfo(CardsPanel& panel, wxFindReplaceData& what) : FindInfo(what), panel(panel) {}
bool handle(const CardP& card, const TextValueP& value, size_t pos, bool was_selection) override {
// Select the card
panel.card_list->setCard(card);
panel.setCard(card, true);
return true;
}
private:
@@ -477,7 +576,7 @@ public:
ReplaceFindInfo(CardsPanel& panel, wxFindReplaceData& what) : FindInfo(what), panel(panel) {}
bool handle(const CardP& card, const TextValueP& value, size_t pos, bool was_selection) override {
// Select the card
panel.card_list->setCard(card);
panel.setCard(card, true);
// Replace
if (was_selection) {
panel.editor->insert(escape(what.GetReplaceString()), _("Replace"));
@@ -512,10 +611,12 @@ bool CardsPanel::search(FindInfo& find, bool from_start) {
if (include) {
editor->setCard(card);
if (editor->search(find, from_start || card != current)) {
return true; // done
// found a card, call handle
return true;
}
}
}
// didn't find anything, put editor back in its previous state
editor->setCard(current);
return false;
}
@@ -527,9 +628,76 @@ CardP CardsPanel::selectedCard() const {
}
void CardsPanel::selectCard(const CardP& card) {
if (!set) return; // we want onChangeSet first
card_list->setCard(card);
editor->setCard(card);
vector<pair<CardP, String>> linked_cards = card->getLinkedCards(*set);
int count = linked_cards.size();
if (count >= 1) {
link_box_1->Show(true);
link_editor->setCard(linked_cards[0].first);
link_viewer_1->setCard(linked_cards[0].first);
link_relation_1->SetLabel(linked_cards[0].second);
if (count == 1) {
link_editor->Show(true);
link_viewer_1->Show(false);
link_select->Show(true);
link_editor->InvalidateBestSize();
link_relation_1->SetMaxSize(wxSize(link_editor->GetSize().x - link_unlink_1->GetSize().x, -1));
} else {
link_editor->Show(false);
link_viewer_1->Show(true);
link_select->Show(false);
link_viewer_1->InvalidateBestSize();
link_relation_1->SetMaxSize(wxSize(link_viewer_1->GetSize().x - link_unlink_1->GetSize().x, -1));
}
link_relation_1->InvalidateBestSize();
} else {
link_box_1->Show(false);
link_editor->setCard(card);
link_viewer_1->setCard(card);
//link_relation_1->SetLabel(wxEmptyString);
}
if (count >= 2) {
link_box_2->Show(true);
link_viewer_2->setCard(linked_cards[1].first);
link_relation_2->SetLabel(linked_cards[1].second);
link_relation_2->SetMaxSize(wxSize(link_viewer_2->GetSize().x - link_unlink_2->GetSize().x, -1));
link_relation_2->InvalidateBestSize();
} else {
link_box_2->Show(false);
link_viewer_2->setCard(card);
//link_relation_2->SetLabel(wxEmptyString);
}
if (count >= 3) {
link_box_3->Show(true);
link_viewer_3->setCard(linked_cards[2].first);
link_relation_3->SetLabel(linked_cards[2].second);
link_relation_3->SetMaxSize(wxSize(link_viewer_3->GetSize().x - link_unlink_3->GetSize().x, -1));
link_relation_3->InvalidateBestSize();
} else {
link_box_3->Show(false);
link_viewer_3->setCard(card);
//link_relation_3->SetLabel(wxEmptyString);
}
if (count >= 4) {
link_box_4->Show(true);
link_viewer_4->setCard(linked_cards[3].first);
link_relation_4->SetLabel(linked_cards[3].second);
link_relation_4->SetMaxSize(wxSize(link_viewer_4->GetSize().x - link_unlink_4->GetSize().x, -1));
link_relation_4->InvalidateBestSize();
} else {
link_box_4->Show(false);
link_viewer_4->setCard(card);
//link_relation_4->SetLabel(wxEmptyString);
}
if (count >= 5) {
queue_message(MESSAGE_WARNING, "DEBUG More than 4 linked cards found for card: " + card->identification());
}
notes->setValue(card ? &card->notes : nullptr);
Layout();
updateNotesPosition();
}
@@ -539,6 +707,20 @@ void CardsPanel::selectFirstCard() {
card_list->selectFirst();
}
void CardsPanel::setCard(const CardP& card, bool event) {
if (!set) return; // we want onChangeSet first
card_list->setCard(card, event);
}
void CardsPanel::refreshCard(const CardP& card) {
if (!set) return; // we want onChangeSet first
card_list->RefreshItem(card_list->findGivenItemPos(card));
}
void CardsPanel::getCardLists(vector<CardListBase*>& out) {
out.push_back(card_list);
}
void CardsPanel::setFocusedEditor(DataEditor* editor) {
focused_editor = editor;
}
+12 -1
View File
@@ -15,6 +15,9 @@ class wxSplitterWindow;
class FilteredImageCardList;
class DataEditor;
class TextCtrl;
class CardViewer;
class wxSizer;
class wxButton;
class HoverButton;
class FindInfo;
class FilterCtrl;
@@ -74,17 +77,25 @@ public:
CardP selectedCard() const override;
void selectCard(const CardP& card) override;
void selectFirstCard() override;
void setCard(const CardP& card, bool event = false);
void refreshCard(const CardP& card);
void getCardLists(vector<CardListBase*>& out) override;
void setFocusedEditor(DataEditor* editor);
private:
// --------------------------------------------------- : Controls
wxSizer* s_left;
wxSplitterWindow* splitter;
DataEditor* editor;
DataEditor* editor, *link_editor, *focused_editor;
FilteredImageCardList* card_list;
wxPanel* nodes_panel;
TextCtrl* notes;
wxSizer* link_box_1, *link_box_2, *link_box_3, *link_box_4;
wxStaticText* link_relation_1, *link_relation_2, *link_relation_3, *link_relation_4;
CardViewer* link_viewer_1, *link_viewer_2, *link_viewer_3, *link_viewer_4;
wxButton* link_unlink_1, *link_unlink_2, *link_unlink_3, *link_unlink_4, *link_select;
HoverButton* collapse_notes;
FilterCtrl* filter;
String filter_value; // value of filter, need separate variable because the control is destroyed
+20 -5
View File
@@ -11,6 +11,7 @@
#include <gui/image_slice_window.hpp>
#include <data/format/clipboard.hpp>
#include <data/action/value.hpp>
#include <data/card.hpp>
#include <wx/clipbrd.h>
#include <gui/util.hpp>
@@ -19,7 +20,19 @@
IMPLEMENT_VALUE_EDITOR(Image) {}
bool ImageValueEditor::onLeftDClick(const RealPoint&, wxMouseEvent&) {
String filename = wxFileSelector(_("Open image file"), settings.default_image_dir, _(""), _(""),
String directory = settings.default_image_dir;
String filename = _("");
CardP card = parent.getCard();
String cardname = card ? card->identification() : _("clipboard");
if (ImageSliceWindow::previously_used_settings_path.find(cardname) != ImageSliceWindow::previously_used_settings_path.end()) {
String filepath = ImageSliceWindow::previously_used_settings_path[cardname];
size_t pos = filepath.rfind(wxFileName::GetPathSeparator());
if (pos != String::npos) {
directory = filepath.substr(0, pos+1);
filename = filepath.substr(pos+1);
}
}
filename = wxFileSelector(_("Open image file"), directory, filename, _(""),
_("All images|*.bmp;*.jpg;*.jpeg;*.png;*.gif;*.tif;*.tiff|Windows bitmaps (*.bmp)|*.bmp|JPEG images (*.jpg;*.jpeg)|*.jpg;*.jpeg|PNG images (*.png)|*.png|GIF images (*.gif)|*.gif|TIFF images (*.tif;*.tiff)|*.tif;*.tiff"),
wxFD_OPEN, wxGetTopLevelParent(&editor()));
if (!filename.empty()) {
@@ -29,19 +42,19 @@ bool ImageValueEditor::onLeftDClick(const RealPoint&, wxMouseEvent&) {
wxLogNull noLog;
image = wxImage(filename);
}
sliceImage(image);
sliceImage(image, filename, cardname);
}
return true;
}
void ImageValueEditor::sliceImage(const Image& image) {
void ImageValueEditor::sliceImage(const Image& image, const String& filename, const String& cardname) {
if (!image.Ok()) return;
// mask
GeneratedImage::Options options((int)style().width, (int)style().height, &parent.getStylePackage(), &parent.getLocalPackage());
AlphaMask mask;
style().mask.getNoCache(options,mask);
// slice
ImageSliceWindow s(wxGetTopLevelParent(&editor()), image, style().getSize(), mask);
ImageSliceWindow s(wxGetTopLevelParent(&editor()), image, filename, cardname, style().getSize(), mask);
// clicked ok?
if (s.ShowModal() == wxID_OK) {
// store the image into the set
@@ -88,7 +101,9 @@ bool ImageValueEditor::doPaste() {
wxTheClipboard->Close();
if (!ok) return false;
// slice
sliceImage(data.GetBitmap().ConvertToImage());
CardP card = parent.getCard();
String cardname = card ? card->identification() : _("clipboard");
sliceImage(data.GetBitmap().ConvertToImage(), _("clipboard"), cardname);
return true;
}
+2 -2
View File
@@ -32,7 +32,7 @@ public:
bool onChar(wxKeyEvent&) override;
private:
// Open the image slice window showing the give image
void sliceImage(const Image&);
// Open the image slice window showing the given image
void sliceImage(const Image&, const String& filename, const String& cardname);
};
+4 -3
View File
@@ -49,9 +49,10 @@ bool prepare_choice_viewer(RotatedDC& dc, ValueViewer& viewer, ChoiceStyle& styl
RealSize size;
img.generateCached(img_options, &style.mask, &combine, &bitmap, &image, &size);
// store content properties
if (style.content_width != size.width || style.content_height != size.height) {
style.content_width = size.width / dc.getZoom();
style.content_height = size.height / dc.getZoom();
double zoom = dc.getZoom();
if (style.content_width != size.width / zoom || style.content_height != size.height / zoom) {
style.content_width = size.width / zoom;
style.content_height = size.height / zoom;
return true;
}
}
+47 -1
View File
@@ -26,7 +26,6 @@
#include <wx/wfstream.h>
#include <boost/json.hpp>
// ----------------------------------------------------------------------------- : Debugging
SCRIPT_FUNCTION(get_mse_version) {
@@ -40,6 +39,10 @@ SCRIPT_FUNCTION(get_mse_path) {
SCRIPT_RETURN(app_folder);
}
SCRIPT_FUNCTION(get_mse_locale) {
SCRIPT_RETURN(settings.locale);
}
SCRIPT_FUNCTION(trace) {
SCRIPT_PARAM_C(String, input);
#if defined(_DEBUG) && 0
@@ -752,6 +755,45 @@ SCRIPT_FUNCTION(get_card_stylesheet) {
throw ScriptError(_("invalid set or card argument"));
}
SCRIPT_FUNCTION(get_card_from_uid) {
SCRIPT_PARAM_C(Set*, set);
SCRIPT_PARAM_C(String, input);
FOR_EACH(other_card, set->cards) {
if (other_card->uid == input) SCRIPT_RETURN(other_card);
}
return script_nil;
}
SCRIPT_FUNCTION(get_card_from_link) {
SCRIPT_PARAM_C(Set*, set);
SCRIPT_PARAM_C(CardP, card);
SCRIPT_PARAM_C(String, input);
String trimmed_input = input.Trim().Trim(false);
String uid = card->linked_relation_1 == trimmed_input ? card->linked_card_1 :
card->linked_relation_2 == trimmed_input ? card->linked_card_2 :
card->linked_relation_3 == trimmed_input ? card->linked_card_3 :
card->linked_relation_4 == trimmed_input ? card->linked_card_4 :
wxEmptyString;
if (uid == wxEmptyString) return script_nil;
FOR_EACH(other_card, set->cards) {
if (other_card->uid == uid) SCRIPT_RETURN(other_card);
}
return script_nil;
}
SCRIPT_FUNCTION(has_link) {
SCRIPT_PARAM_C(CardP, card);
SCRIPT_PARAM_C(String, input);
String trimmed_input = input.Trim().Trim(false);
if (
card->linked_relation_1 == trimmed_input ||
card->linked_relation_2 == trimmed_input ||
card->linked_relation_3 == trimmed_input ||
card->linked_relation_4 == trimmed_input
) SCRIPT_RETURN(true);
SCRIPT_RETURN(false);
}
// ----------------------------------------------------------------------------- : Keywords
@@ -826,6 +868,7 @@ SCRIPT_FUNCTION(rule) {
void init_script_basic_functions(Context& ctx) {
// debugging
ctx.setVariable(_("get_mse_version"), script_get_mse_version);
ctx.setVariable(_("get_mse_locale"), script_get_mse_locale);
ctx.setVariable(_("get_mse_path"), script_get_mse_path);
ctx.setVariable(_("trace"), script_trace);
ctx.setVariable(_("warning"), script_warning);
@@ -891,6 +934,9 @@ void init_script_basic_functions(Context& ctx) {
ctx.setVariable(_("random_shuffle"), script_random_shuffle);
ctx.setVariable(_("random_select"), script_random_select);
ctx.setVariable(_("random_select_many"), script_random_select_many);
ctx.setVariable(_("get_card_from_uid"), script_get_card_from_uid);
ctx.setVariable(_("get_card_from_link"), script_get_card_from_link);
ctx.setVariable(_("has_link"), script_has_link);
// keyword
ctx.setVariable(_("expand_keywords"), script_expand_keywords);
ctx.setVariable(_("expand_keywords_rule"), make_intrusive<ScriptRule>(script_expand_keywords));
+11
View File
@@ -70,6 +70,16 @@ SCRIPT_FUNCTION(height_of) {
SCRIPT_RETURN(image.GetHeight());
}
SCRIPT_FUNCTION(dimensions_of) {
SCRIPT_PARAM(Set*, set);
SCRIPT_PARAM(GeneratedImageP, input);
Image image = input->generate(GeneratedImage::Options(0, 0, set->stylesheet.get()));
ScriptCustomCollectionP ret(new ScriptCustomCollection());
ret->value.push_back(to_script(image.GetWidth()));
ret->value.push_back(to_script(image.GetHeight()));
return ret;
}
SCRIPT_FUNCTION(insert_image) {
SCRIPT_PARAM(GeneratedImageP, base_image);
SCRIPT_PARAM(GeneratedImageP, inserted_image);
@@ -277,6 +287,7 @@ void init_script_image_functions(Context& ctx) {
ctx.setVariable(_("to_card_image"), script_to_card_image);
ctx.setVariable(_("width_of"), script_width_of);
ctx.setVariable(_("height_of"), script_height_of);
ctx.setVariable(_("dimensions_of"), script_dimensions_of);
ctx.setVariable(_("linear_blend"), script_linear_blend);
ctx.setVariable(_("masked_blend"), script_masked_blend);
ctx.setVariable(_("combine_blend"), script_combine_blend);
+31
View File
@@ -0,0 +1,31 @@
//+----------------------------------------------------------------------------+
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
//| Copyright: (C) Twan van Laarhoven and the other MSE developers |
//| License: GNU General Public License 2 or later (see file COPYING) |
//+----------------------------------------------------------------------------+
#pragma once
// ----------------------------------------------------------------------------- : Includes
#include <random>
#include <sstream>
// ----------------------------------------------------------------------------- : UID
static std::random_device rd; // Get true random number generator
static std::mt19937_64 gen((static_cast<uint64_t>(rd()) << 32) ^ rd()); // Bitwise XOR two outputs to seed pseudo random number generator
static std::uniform_int_distribution<> dis(0, 9);
// Generate a string consisting of 32 uniformly random digits.
static String generate_uid() {
std::stringstream ss;
int i;
ss << std::hex;
for (i = 0; i < 32; i++) {
ss << dis(gen);
};
//return ss.str();
String wxString(ss.str().c_str(), wxConvUTF8);
return wxString;
}
+15
View File
@@ -108,6 +108,8 @@ enum ChildMenuID {
ID_CARD_ROTATE_270,
// CardList
ID_SELECT_COLUMNS,
ID_CARD_LINK,
ID_CARD_AND_LINK_COPY,
ID_CARD_ADD_CSV,
ID_CARD_ADD_CSV_SEP,
ID_CARD_ADD_CSV_BROWSE,
@@ -192,6 +194,12 @@ enum ChildMenuID {
ID_COLLAPSE_NOTES = 8001,
ID_CARD_FILTER,
ID_CARD_COUNTER,
ID_CARD_LINK_TYPE,
ID_CARD_LINK_SELECT,
ID_CARD_LINK_UNLINK_1,
ID_CARD_LINK_UNLINK_2,
ID_CARD_LINK_UNLINK_3,
ID_CARD_LINK_UNLINK_4,
// Style panel
ID_STYLE_USE_FOR_ALL = 8011,
@@ -279,6 +287,7 @@ enum ControlID {
ID_PREVIEW,
ID_SELECTOR,
ID_SIZE,
ID_GRID,
ID_LEFT,
ID_TOP,
ID_WIDTH,
@@ -309,5 +318,11 @@ enum ControlID {
ID_ADD_ITEM,
ID_REMOVE_ITEM,
ID_DEFAULTS,
ID_CARD_LINK_EDITOR,
ID_CARD_LINK_VIEWER,
ID_CARD_LINK_RELATION_1,
ID_CARD_LINK_RELATION_2,
ID_CARD_LINK_RELATION_3,
ID_CARD_LINK_RELATION_4,
};
@@ -93,14 +93,19 @@ $built_in_functions = array(
'flip_vertical' =>'',
'rotate' =>'',
'drop_shadow' =>'',
'insert_image' =>'',
'dimensions_of' =>'',
'symbol_variation' =>'',
'import_image' =>'',
'built_in_image' =>'',
// cards
'new_card' =>'',
'add_card_to_set' =>'',
'has_link' =>'',
'get_card_from_link' =>'',
'get_card_from_uid' =>'',
'get_card_styling' =>'',
'get_card_stylesheet' =>'',
'add_card_to_set' =>'',
// html export
'to_html' =>'',
'symbols_to_html' =>'',
@@ -111,6 +116,7 @@ $built_in_functions = array(
'write_set_file' =>'',
// other
'get_mse_version' =>'',
'get_mse_locale' =>'',
'get_mse_path' =>'',
'trace' =>'',
'assert' =>'',