mirror of
https://github.com/amyinspace/MagicSetEditor2.git
synced 2026-06-10 04:57:00 -04:00
Added word lists for choosing things like card type;
Added 'in_place' pattern to spec_sort git-svn-id: svn://svn.code.sf.net/p/magicseteditor/code/trunk@616 0fc631ac-6414-0410-93d0-97cfa31319b6
This commit is contained in:
@@ -35,6 +35,9 @@ Parts
|
||||
| @"pattern(.z. xyz)"@ @"yzz"@ Selects all things that match the pattern, where @"."@ is a wildcard.
|
||||
The things matching the wildcards are then sorted using the given pattern (separated by a space), and subsituted back in.
|
||||
So in <tt>"zzyyxxy"</tt> the pattern matches <tt>"<b>zzy</b>xxy"</tt> with wildcards <tt>"zy"</tt> these sort as <tt>"yz"</tt> and in the pattern this becomes <tt>"yzz"</tt>.
|
||||
| @"in_place(yz)"@ @"yyyzxxz"@ Sort everything with the given pattern, and insert the remaining characters in their orignal places.<br/>
|
||||
For example, in @"zzyyxxy"@ we match <tt>y</tt> and <tt>z</tt>, leaving @"....xx."@.
|
||||
The result of the pattern is @"yyyzz"@, subsituting that back in gives @"yyyzxxz"@.
|
||||
|
||||
The parts are read from left to right, each part that matches something removes it from the input, so for example
|
||||
> sort_text(order: "xx")
|
||||
|
||||
@@ -40,6 +40,7 @@ Such a package contains a [[file:format|data file]] called <tt>game</tt> that ha
|
||||
| @keyword modes@ [[type:list]] of [[type:keyword mode]]s Choices for the 'mode' property of keywords.
|
||||
| @keyword parameter types@ [[type:list]] of [[type:keyword param type]]s Types of parameters available to keywords.
|
||||
| @keywords@ [[type:list]] of [[type:keyword]]s Standard keywords for this game.
|
||||
| @word lists@ [[type:list]] of [[type:word list]]s Word lists that can be used by text fields.
|
||||
|
||||
--Examples--
|
||||
Look at the game files in the standard MSE distribution for examples.
|
||||
|
||||
@@ -37,6 +37,8 @@ This is written as the character with code 1 in files.
|
||||
Inserting this tag manually will confuse that function!<br/>
|
||||
This tag can never be selected, and its contents can not be edited.
|
||||
| @"<sep-soft>"@ Like @"<sep>"@, only hidden. This is inserted by [[fun:combined_editor]]
|
||||
| @"<word-list-?>"@ Indicate that the text inside the tag should be selected from a [[type:word list]].
|
||||
The <tt>?</tt> must be the name of a word list in the game.
|
||||
| any other tag Other tags are ignored.
|
||||
|
||||
--Related functions--
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
Data type: word list
|
||||
|
||||
A list of words. Used for drop down lists in the text editor, for example for card types.
|
||||
|
||||
--Properties--
|
||||
! Property Type Default Description
|
||||
| @name@ [[type:string]] ''Required'' Name of this word list, refered to using a @"<word-list-...>"@ tag.
|
||||
| @words@ [[type:list]] of [[type:word list word]]s ''Required'' The words in the list
|
||||
|
||||
--Example--
|
||||
>word list:
|
||||
> name: type
|
||||
> word: Creature
|
||||
> word: Spell
|
||||
> word: Artifact
|
||||
This can be used with for example:
|
||||
> @"<word-list-type>Creature</word-list-type>"@
|
||||
Which gives the creature choice, and that can be changed with a drop down list.
|
||||
@@ -0,0 +1,18 @@
|
||||
Data type: word list word
|
||||
|
||||
A word in a [[type:word list]].
|
||||
|
||||
--Properties--
|
||||
! Property Type Default Description
|
||||
| @name@ [[type:string]] ''Required'' The word
|
||||
| @line below@ [[type:boolean]] @false@ Display a line below this item in the list?
|
||||
| @words@ [[type:list]] of [[type:word list word]]s A submenu
|
||||
|
||||
A word can also be given in a short form, in that case only the name is specified.
|
||||
|
||||
--Example--
|
||||
In short form:
|
||||
>word: xyz
|
||||
Is the same as:
|
||||
>word:
|
||||
> name: xyz
|
||||
+2
-1
@@ -12,6 +12,7 @@
|
||||
#include <data/keyword.hpp>
|
||||
#include <data/statistics.hpp>
|
||||
#include <data/pack.hpp>
|
||||
#include <data/word_list.hpp>
|
||||
#include <util/io/package_manager.hpp>
|
||||
#include <script/script.hpp>
|
||||
|
||||
@@ -56,7 +57,7 @@ IMPLEMENT_REFLECTION(Game) {
|
||||
REFLECT(keyword_modes);
|
||||
REFLECT(keyword_parameter_types);
|
||||
REFLECT_NO_SCRIPT(keywords);
|
||||
// REFLECT(word_lists);
|
||||
REFLECT(word_lists);
|
||||
}
|
||||
|
||||
void Game::validate(Version v) {
|
||||
|
||||
@@ -24,6 +24,7 @@ DECLARE_POINTER_TYPE(PackType);
|
||||
DECLARE_POINTER_TYPE(KeywordParam);
|
||||
DECLARE_POINTER_TYPE(KeywordMode);
|
||||
DECLARE_POINTER_TYPE(Keyword);
|
||||
DECLARE_POINTER_TYPE(WordList);
|
||||
|
||||
// ----------------------------------------------------------------------------- : Game
|
||||
|
||||
@@ -43,6 +44,7 @@ class Game : public Packaged {
|
||||
vector<StatsDimensionP> statistics_dimensions; ///< (Additional) statistics dimensions
|
||||
vector<StatsCategoryP> statistics_categories; ///< (Additional) statistics categories
|
||||
vector<PackTypeP> pack_types; ///< Types of random card packs to generate
|
||||
vector<WordListP> word_lists; ///< Word lists for editing with a drop down list
|
||||
|
||||
bool has_keywords; ///< Does this game use keywords?
|
||||
OptionalScript keyword_match_script; ///< For the keyword editor
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
//+----------------------------------------------------------------------------+
|
||||
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
|
||||
//| Copyright: (C) 2001 - 2007 Twan van Laarhoven |
|
||||
//| License: GNU General Public License 2 or later (see file COPYING) |
|
||||
//+----------------------------------------------------------------------------+
|
||||
|
||||
// ----------------------------------------------------------------------------- : Includes
|
||||
|
||||
#include <data/word_list.hpp>
|
||||
|
||||
// ----------------------------------------------------------------------------- : WordList
|
||||
|
||||
WordListWord::WordListWord()
|
||||
: line_below(false)
|
||||
, is_prefix(false)
|
||||
{}
|
||||
|
||||
IMPLEMENT_REFLECTION_NO_SCRIPT(WordListWord) {
|
||||
if (line_below || is_prefix || isGroup() || (tag.reading() && tag.isComplex())) {
|
||||
// complex value
|
||||
REFLECT(name);
|
||||
REFLECT(line_below);
|
||||
REFLECT(is_prefix);
|
||||
REFLECT(words);
|
||||
} else {
|
||||
REFLECT_NAMELESS(name);
|
||||
}
|
||||
}
|
||||
|
||||
IMPLEMENT_REFLECTION_NO_SCRIPT(WordList) {
|
||||
REFLECT(name);
|
||||
REFLECT(words);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
//+----------------------------------------------------------------------------+
|
||||
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
|
||||
//| Copyright: (C) 2001 - 2007 Twan van Laarhoven |
|
||||
//| License: GNU General Public License 2 or later (see file COPYING) |
|
||||
//+----------------------------------------------------------------------------+
|
||||
|
||||
#ifndef HEADER_DATA_WORD_LIST
|
||||
#define HEADER_DATA_WORD_LIST
|
||||
|
||||
// ----------------------------------------------------------------------------- : Includes
|
||||
|
||||
#include <util/prec.hpp>
|
||||
#include <util/reflect.hpp>
|
||||
|
||||
DECLARE_POINTER_TYPE(WordListWord);
|
||||
DECLARE_POINTER_TYPE(WordList);
|
||||
|
||||
// ----------------------------------------------------------------------------- : WordList
|
||||
|
||||
/// A word in a WordList
|
||||
class WordListWord : public IntrusivePtrBase<WordListWord> {
|
||||
public:
|
||||
WordListWord();
|
||||
|
||||
String name; ///< Name of the list / the word
|
||||
bool line_below; ///< Line below in the list?
|
||||
bool is_prefix; ///< Is this a prefix before other words?
|
||||
vector<WordListWordP> words; ///< Sublist
|
||||
|
||||
inline bool isGroup() const { return !words.empty(); }
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
/// A list of words for a drop down box
|
||||
class WordList : public WordListWord {
|
||||
public:
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
#endif
|
||||
@@ -217,7 +217,7 @@ void DataEditor::onMotion(wxMouseEvent& ev) {
|
||||
wxFrame* frame = dynamic_cast<wxFrame*>( wxGetTopLevelParent(this) );
|
||||
FOR_EACH_EDITOR_REVERSE { // find high z index fields first
|
||||
if (v->containsPoint(pos) && v->getField()->editable) {
|
||||
wxCursor c = e->cursor();
|
||||
wxCursor c = e->cursor(pos);
|
||||
if (c.Ok()) SetCursor(c);
|
||||
else SetCursor(wxCURSOR_ARROW);
|
||||
if (frame) frame->SetStatusText(v->getField()->description);
|
||||
|
||||
+29
-18
@@ -37,13 +37,9 @@ class DropDownHider : public wxEvtHandler {
|
||||
// don't just use ev.Skip(), because this event handler will be removed by hiding,
|
||||
// so there will be no next handler to skip to
|
||||
wxEvtHandler* nh = GetNextHandler();
|
||||
list.hide(false);
|
||||
if (nh) nh->ProcessEvent(ev);
|
||||
list.hide(false);
|
||||
return false;
|
||||
} else if (t == wxEVT_MOTION) {
|
||||
// send along all motion events
|
||||
list.ProcessEvent(ev);
|
||||
return wxEvtHandler::ProcessEvent(ev);
|
||||
} else {
|
||||
// if (t !=10093 && t !=10098 && t !=10097 && t !=10099 && t !=10004 && t !=10062
|
||||
// && t !=10025 && t !=10035 && t !=10034 && t !=10036 && t !=10042 && t !=10119)
|
||||
@@ -142,25 +138,31 @@ void DropDownList::show(bool in_place, wxPoint pos) {
|
||||
if (selected_item == NO_SELECTION && itemCount() > 0) selected_item = 0; // select first item by default
|
||||
mouse_down = false;
|
||||
Window::Show();
|
||||
if (!parent_menu && GetParent()->HasCapture()) {
|
||||
// release capture on parent
|
||||
GetParent()->ReleaseMouse();
|
||||
}
|
||||
// fix drop down arrow
|
||||
redrawArrowOnParent();
|
||||
}
|
||||
|
||||
void DropDownList::hide(bool event, bool allow_veto) {
|
||||
// hide?
|
||||
bool keep_open = event && allow_veto && stayOpen();
|
||||
if (keep_open) {
|
||||
Refresh(false);
|
||||
} else {
|
||||
// hide root
|
||||
DropDownList* root = this;
|
||||
while (root->parent_menu) {
|
||||
root = root->parent_menu;
|
||||
}
|
||||
root->realHide();
|
||||
}
|
||||
// send event
|
||||
if (event && selected_item != NO_SELECTION && itemEnabled(selected_item)) {
|
||||
bool close = select(selected_item);
|
||||
if (allow_veto && !close) {
|
||||
Refresh(false);
|
||||
return;
|
||||
}
|
||||
select(selected_item);
|
||||
}
|
||||
// hide root
|
||||
DropDownList* root = this;
|
||||
while (root->parent_menu) {
|
||||
root = root->parent_menu;
|
||||
}
|
||||
root->realHide();
|
||||
}
|
||||
|
||||
void DropDownList::realHide() {
|
||||
@@ -292,7 +294,13 @@ void DropDownList::drawItem(DC& dc, int y, size_t item) {
|
||||
|
||||
// ----------------------------------------------------------------------------- : DropDownList : Events
|
||||
|
||||
void DropDownList::onLeftDown(wxMouseEvent&) {
|
||||
void DropDownList::onLeftDown(wxMouseEvent& ev) {
|
||||
wxSize cs = GetClientSize();
|
||||
if (ev.GetX() < 0 || ev.GetX() >= cs.x || ev.GetY() < marginH || ev.GetY() >= cs.y) {
|
||||
hide(false);
|
||||
ev.Skip();
|
||||
return;
|
||||
}
|
||||
mouse_down = true; // prevent closing on mouseup of the click that opened the window
|
||||
}
|
||||
|
||||
@@ -309,7 +317,10 @@ void DropDownList::onMotion(wxMouseEvent& ev) {
|
||||
// size
|
||||
wxSize cs = GetClientSize();
|
||||
// find selected item
|
||||
if (ev.GetX() < marginW || ev.GetX() + marginW >= cs.GetWidth() || ev.GetY() < marginH || ev.GetY() + marginH >= cs.GetHeight()) return;
|
||||
if (ev.GetX() < marginW || ev.GetX() + marginW >= cs.GetWidth() || ev.GetY() < marginH || ev.GetY() + marginH >= cs.GetHeight()) {
|
||||
ev.Skip();
|
||||
return;
|
||||
}
|
||||
int startY = marginH;
|
||||
size_t count = itemCount();
|
||||
for (size_t i = 0 ; i < count ; ++i) {
|
||||
|
||||
@@ -48,11 +48,11 @@ class DropDownList : public wxPopupWindow {
|
||||
static const size_t NO_SELECTION = (size_t)-1;
|
||||
|
||||
/// Signal that the list is closed and something is selected
|
||||
/** Returns true if the event was handled and the list should be hidden,
|
||||
* false keeps the list open. */
|
||||
virtual bool select(size_t selection) = 0;
|
||||
virtual void select(size_t selection) = 0;
|
||||
/// When the list is being opened, what should be selected?
|
||||
virtual size_t selection() const = 0;
|
||||
/** Should the list stay open after selecting something? */
|
||||
virtual bool stayOpen() const { return false; }
|
||||
|
||||
// --------------------------------------------------- : Item information
|
||||
/// Number of items
|
||||
@@ -60,7 +60,7 @@ class DropDownList : public wxPopupWindow {
|
||||
/// Text of an item
|
||||
virtual String itemText(size_t item) const = 0;
|
||||
/// Draw an icon at the specified location
|
||||
virtual void drawIcon(DC& dc, int x, int y, size_t item, bool selected) const = 0;
|
||||
virtual void drawIcon(DC& dc, int x, int y, size_t item, bool selected) const {}
|
||||
/// Is there a line below an item?
|
||||
virtual bool lineBelow(size_t item) const { return false; }
|
||||
/// Should the item be highlighted?
|
||||
|
||||
@@ -231,14 +231,13 @@ void DropDownChoiceList::onShow() {
|
||||
generateThumbnailImages();
|
||||
}
|
||||
|
||||
bool DropDownChoiceList::select(size_t item) {
|
||||
void DropDownChoiceList::select(size_t item) {
|
||||
if (isFieldDefault(item)) {
|
||||
dynamic_cast<ChoiceValueEditor&>(cve).change( Defaultable<String>() );
|
||||
} else {
|
||||
ChoiceField::ChoiceP choice = getChoice(item);
|
||||
dynamic_cast<ChoiceValueEditor&>(cve).change( field().choices->choiceName(choice->first_id) );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t DropDownChoiceList::selection() const {
|
||||
|
||||
@@ -96,7 +96,7 @@ class DropDownChoiceList : public DropDownChoiceListBase {
|
||||
|
||||
protected:
|
||||
virtual void onShow();
|
||||
virtual bool select(size_t item);
|
||||
virtual void select(size_t item);
|
||||
virtual size_t selection() const;
|
||||
virtual DropDownList* createSubMenu(ChoiceField::ChoiceP group) const;
|
||||
};
|
||||
|
||||
@@ -27,7 +27,7 @@ class DropDownColorList : public DropDownList {
|
||||
virtual String itemText(size_t item) const;
|
||||
virtual void drawIcon(DC& dc, int x, int y, size_t item, bool selected) const;
|
||||
|
||||
virtual bool select(size_t item);
|
||||
virtual void select(size_t item);
|
||||
virtual size_t selection() const;
|
||||
|
||||
private:
|
||||
@@ -112,7 +112,7 @@ size_t DropDownColorList::selection() const {
|
||||
}
|
||||
return selection;
|
||||
}
|
||||
bool DropDownColorList::select(size_t item) {
|
||||
void DropDownColorList::select(size_t item) {
|
||||
if (isDefault(item)) {
|
||||
cve.change( Defaultable<Color>());
|
||||
} else if (isCustom(item)) {
|
||||
@@ -120,7 +120,6 @@ bool DropDownColorList::select(size_t item) {
|
||||
} else {
|
||||
cve.change(field().choices[item - hasDefault()]->color);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : ColorValueEditor
|
||||
|
||||
@@ -113,7 +113,7 @@ class ValueEditor {
|
||||
// --------------------------------------------------- : Other
|
||||
|
||||
/// The cursor type to use when the mouse is over this control
|
||||
virtual wxCursor cursor() const { return wxCursor(); }
|
||||
virtual wxCursor cursor(const RealPoint& pos) const { return wxCursor(); }
|
||||
/// Determines prefered size in the native look, update the style
|
||||
virtual void determineSize(bool force_fit = false) {}
|
||||
/// Should a label and control border be drawn in the native look?
|
||||
|
||||
@@ -20,13 +20,15 @@ class DropDownMultipleChoiceList : public DropDownChoiceListBase {
|
||||
|
||||
protected:
|
||||
virtual void onShow();
|
||||
virtual bool select(size_t item);
|
||||
virtual void select(size_t item);
|
||||
virtual size_t selection() const;
|
||||
virtual bool stayOpen() const { return true; }
|
||||
virtual DropDownList* createSubMenu(ChoiceField::ChoiceP group) const;
|
||||
virtual void drawIcon(DC& dc, int x, int y, size_t item, bool selected) const;
|
||||
|
||||
virtual void onMotion(wxMouseEvent&);
|
||||
virtual void onMouseLeave(wxMouseEvent&);
|
||||
private:
|
||||
DECLARE_EVENT_TABLE();
|
||||
bool kept_open; ///< Was the list kept open after selecting a choice, if so, be eager to close it
|
||||
};
|
||||
|
||||
@@ -38,7 +40,7 @@ DropDownMultipleChoiceList::DropDownMultipleChoiceList
|
||||
icon_size.width += 16;
|
||||
}
|
||||
|
||||
bool DropDownMultipleChoiceList::select(size_t item) {
|
||||
void DropDownMultipleChoiceList::select(size_t item) {
|
||||
MultipleChoiceValueEditor& mcve = dynamic_cast<MultipleChoiceValueEditor&>(cve);
|
||||
if (isFieldDefault(item)) {
|
||||
mcve.toggleDefault();
|
||||
@@ -49,7 +51,6 @@ bool DropDownMultipleChoiceList::select(size_t item) {
|
||||
// keep the box open
|
||||
DropDownChoiceListBase::onShow(); // update 'enabled'
|
||||
kept_open = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void DropDownMultipleChoiceList::drawIcon(DC& dc, int x, int y, size_t item, bool selected) const {
|
||||
@@ -92,16 +93,21 @@ DropDownList* DropDownMultipleChoiceList::createSubMenu(ChoiceField::ChoiceP gro
|
||||
return new DropDownMultipleChoiceList(const_cast<DropDownMultipleChoiceList*>(this), true, cve, group);
|
||||
}
|
||||
|
||||
void DropDownMultipleChoiceList::onMotion(wxMouseEvent& ev) {
|
||||
void DropDownMultipleChoiceList::onMouseLeave(wxMouseEvent& ev) {
|
||||
if (kept_open) {
|
||||
wxSize cs = GetClientSize();
|
||||
if (ev.GetX() < 0 || ev.GetY() < 0 || ev.GetX() >= cs.x || ev.GetY() >= cs.y) {
|
||||
hide(false); // outside box; hide it
|
||||
ev.Skip();
|
||||
return;
|
||||
}
|
||||
}
|
||||
DropDownChoiceListBase::onMotion(ev);
|
||||
}
|
||||
|
||||
BEGIN_EVENT_TABLE(DropDownMultipleChoiceList, DropDownChoiceListBase)
|
||||
EVT_LEAVE_WINDOW(DropDownMultipleChoiceList::onMouseLeave)
|
||||
END_EVENT_TABLE()
|
||||
|
||||
// ----------------------------------------------------------------------------- : MultipleChoiceValueEditor
|
||||
|
||||
IMPLEMENT_VALUE_EDITOR(MultipleChoice) {}
|
||||
|
||||
+215
-5
@@ -9,6 +9,9 @@
|
||||
#include <gui/value/text.hpp>
|
||||
#include <gui/icon_menu.hpp>
|
||||
#include <gui/util.hpp>
|
||||
#include <gui/drop_down_list.hpp>
|
||||
#include <data/word_list.hpp>
|
||||
#include <data/game.hpp>
|
||||
#include <data/action/value.hpp>
|
||||
#include <util/tagged_string.hpp>
|
||||
#include <util/find_replace.hpp>
|
||||
@@ -16,6 +19,10 @@
|
||||
#include <wx/clipbrd.h>
|
||||
#include <wx/caret.h>
|
||||
|
||||
DECLARE_SHARED_POINTER_TYPE(DropDownList);
|
||||
DECLARE_TYPEOF_COLLECTION(WordListP);
|
||||
DECLARE_TYPEOF_COLLECTION(WordListPosP);
|
||||
|
||||
// ----------------------------------------------------------------------------- : TextValueEditorScrollBar
|
||||
|
||||
/// A scrollbar to scroll a TextValueEditor
|
||||
@@ -54,12 +61,97 @@ END_EVENT_TABLE ()
|
||||
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------- : WordListPos
|
||||
|
||||
class WordListPos : public IntrusivePtrBase<WordListPos> {
|
||||
public:
|
||||
WordListPos(size_t start, size_t end, WordListP word_list)
|
||||
: start(start), end(end)
|
||||
, rect(-1,-1,-1,-1)
|
||||
, word_list(word_list)
|
||||
, active(false)
|
||||
{}
|
||||
|
||||
const size_t start, end; ///< Start and ending indices
|
||||
RealRect rect; ///< Rectangle around word list text
|
||||
WordListP word_list; ///< Word list to use
|
||||
bool active; ///< Is the list dropped down right now?
|
||||
};
|
||||
|
||||
class DropDownWordList : public DropDownList {
|
||||
public:
|
||||
DropDownWordList(Window* parent, bool is_submenu, TextValueEditor& tve, const WordListPosP& pos, const WordListWordP& list);
|
||||
|
||||
void setWords(const WordListPosP& pos2);
|
||||
|
||||
protected:
|
||||
virtual void onShow();
|
||||
virtual size_t itemCount() const { return words->words.size(); }
|
||||
virtual bool lineBelow(size_t item) const { return words->words[item]->line_below; }
|
||||
virtual String itemText(size_t item) const { return words->words[item]->name; }
|
||||
virtual DropDownList* submenu(size_t item) const;
|
||||
virtual size_t selection() const;
|
||||
virtual void select(size_t item);
|
||||
private:
|
||||
TextValueEditor& tve;
|
||||
WordListPosP pos;
|
||||
mutable vector<DropDownListP> submenus;
|
||||
WordListWordP words; ///< The words we are listing
|
||||
};
|
||||
|
||||
|
||||
DropDownWordList::DropDownWordList(Window* parent, bool is_submenu, TextValueEditor& tve, const WordListPosP& pos, const WordListWordP& words)
|
||||
: DropDownList(parent, is_submenu, is_submenu ? nullptr : &tve)
|
||||
, tve(tve), pos(pos)
|
||||
, words(words)
|
||||
{
|
||||
item_size.height = max(16., item_size.height);
|
||||
}
|
||||
|
||||
void DropDownWordList::setWords(const WordListPosP& pos2) {
|
||||
if (words != pos2->word_list) {
|
||||
// switch to different list
|
||||
submenus.clear();
|
||||
}
|
||||
pos = pos2;
|
||||
words = pos2->word_list;
|
||||
}
|
||||
|
||||
void DropDownWordList::onShow() {
|
||||
pos->active = true;
|
||||
}
|
||||
|
||||
DropDownList* DropDownWordList::submenu(size_t item) const {
|
||||
if (item >= submenus.size()) submenus.resize(item + 1);
|
||||
if (submenus[item]) return submenus[item].get();
|
||||
WordListWordP word = words->words[item];
|
||||
if (word->isGroup()) {
|
||||
// create submenu
|
||||
submenus[item].reset(new DropDownWordList(const_cast<DropDownWordList*>(this), true, tve, pos, word));
|
||||
}
|
||||
return submenus[item].get();
|
||||
}
|
||||
|
||||
|
||||
size_t DropDownWordList::selection() const {
|
||||
// TODO: find selection
|
||||
return NO_SELECTION;
|
||||
}
|
||||
|
||||
void DropDownWordList::select(size_t item) {
|
||||
pos->active = false;
|
||||
tve.selection_start_i = pos->start;
|
||||
tve.selection_end_i = pos->end;
|
||||
tve.fixSelection(TYPE_INDEX);
|
||||
tve.replaceSelection(words->words[item]->name, _ACTION_("?????"));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : TextValueEditor
|
||||
|
||||
IMPLEMENT_VALUE_EDITOR(Text)
|
||||
, selection_start (0), selection_end (0)
|
||||
, selection_start_i(0), selection_end_i(0)
|
||||
, select_words(false)
|
||||
, selecting(false), select_words(false)
|
||||
, scrollbar(nullptr), scroll_with_cursor(false)
|
||||
{
|
||||
if (viewer.nativeLook() && field().multi_line) {
|
||||
@@ -75,16 +167,33 @@ TextValueEditor::~TextValueEditor() {
|
||||
|
||||
bool TextValueEditor::onLeftDown(const RealPoint& pos, wxMouseEvent& ev) {
|
||||
select_words = false;
|
||||
moveSelection(TYPE_INDEX, v.indexAt(style().getRotation().trInv(pos)), !ev.ShiftDown(), MOVE_MID);
|
||||
RealPoint pos2 = style().getRotation().trInv(pos);
|
||||
// on word list dropdown button?
|
||||
WordListPosP wl_pos = findWordList(pos2);
|
||||
if (wl_pos) {
|
||||
// show dropdown
|
||||
if (drop_down) {
|
||||
drop_down->setWords(wl_pos);
|
||||
} else {
|
||||
drop_down.reset(new DropDownWordList(&editor(), false, *this, wl_pos, wl_pos->word_list));
|
||||
}
|
||||
drop_down->show(false, wxPoint(0,0)); // TODO: position?
|
||||
} else {
|
||||
// no, select text
|
||||
selecting = true;
|
||||
moveSelection(TYPE_INDEX, v.indexAt(pos2), !ev.ShiftDown(), MOVE_MID);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool TextValueEditor::onLeftUp(const RealPoint& pos, wxMouseEvent&) {
|
||||
// TODO: lookup position of click?
|
||||
selecting = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TextValueEditor::onMotion(const RealPoint& pos, wxMouseEvent& ev) {
|
||||
if (ev.LeftIsDown()) {
|
||||
if (dropDownShown()) return false;
|
||||
if (ev.LeftIsDown() && selecting) {
|
||||
size_t index = v.indexAt(style().getRotation().trInv(pos));
|
||||
if (select_words) {
|
||||
// on the left, swap start and end
|
||||
@@ -126,6 +235,10 @@ bool TextValueEditor::onRightDown(const RealPoint& pos, wxMouseEvent& ev) {
|
||||
// ----------------------------------------------------------------------------- : Keyboard
|
||||
|
||||
bool TextValueEditor::onChar(wxKeyEvent& ev) {
|
||||
if (dropDownShown()) {
|
||||
// forward to drop down list
|
||||
return drop_down->onCharInParent(ev);
|
||||
}
|
||||
if (ev.AltDown()) return false;
|
||||
fixSelection();
|
||||
switch (ev.GetKeyCode()) {
|
||||
@@ -293,6 +406,8 @@ void TextValueEditor::draw(RotatedDC& dc) {
|
||||
prepareDrawScrollbar(dc);
|
||||
// draw text
|
||||
TextValueViewer::draw(dc);
|
||||
// draw word list thingamajigies
|
||||
drawWordListIndicators(dc);
|
||||
// draw selection
|
||||
if (isCurrent()) {
|
||||
v.drawSelection(dc, style(), selection_start_i, selection_end_i);
|
||||
@@ -306,8 +421,11 @@ void TextValueEditor::draw(RotatedDC& dc) {
|
||||
|
||||
wxCursor rotated_ibeam;
|
||||
|
||||
wxCursor TextValueEditor::cursor() const {
|
||||
if (viewer.getRotation().sideways() ^ style().getRotation().sideways()) { // 90 or 270 degrees
|
||||
wxCursor TextValueEditor::cursor(const RealPoint& pos) const {
|
||||
RealPoint pos2 = style().getRotation().trInv(pos);
|
||||
if (findWordList(pos2)) {
|
||||
return wxCursor();
|
||||
} else if (viewer.getRotation().sideways() ^ style().getRotation().sideways()) { // 90 or 270 degrees
|
||||
if (!rotated_ibeam.Ok()) {
|
||||
rotated_ibeam = wxCursor(load_resource_cursor(_("rot_text")));
|
||||
}
|
||||
@@ -321,10 +439,12 @@ void TextValueEditor::onValueChange() {
|
||||
TextValueViewer::onValueChange();
|
||||
selection_start = selection_end = 0;
|
||||
selection_start_i = selection_end_i = 0;
|
||||
findWordLists();
|
||||
}
|
||||
|
||||
void TextValueEditor::onAction(const Action& action, bool undone) {
|
||||
TextValueViewer::onValueChange();
|
||||
findWordLists();
|
||||
TYPE_CASE(action, TextValueAction) {
|
||||
selection_start = action.selection_start;
|
||||
selection_end = action.selection_end;
|
||||
@@ -439,6 +559,11 @@ void TextValueEditor::doFormat(int type) {
|
||||
// ----------------------------------------------------------------------------- : Selection
|
||||
|
||||
void TextValueEditor::showCaret() {
|
||||
if (dropDownShown()) {
|
||||
wxCaret* caret = editor().GetCaret();
|
||||
if (caret && caret->IsVisible()) caret->Hide();
|
||||
return;
|
||||
}
|
||||
// Rotation
|
||||
Rotation rot(viewer.getRotation());
|
||||
Rotater rot2(rot, style().getRotation());
|
||||
@@ -855,3 +980,88 @@ void TextValueEditor::prepareDrawScrollbar(RotatedDC& dc) {
|
||||
style().width.mutate() += scrollbar_width;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Word lists
|
||||
|
||||
bool TextValueEditor::dropDownShown() {
|
||||
return drop_down && drop_down->IsShown();
|
||||
}
|
||||
|
||||
void TextValueEditor::findWordLists() {
|
||||
word_lists.clear();
|
||||
// for each word list...
|
||||
const String& str = value().value();
|
||||
size_t pos = str.find(_("<word-list-"));
|
||||
while (pos != String::npos) {
|
||||
size_t type_end = str.find_first_of(_('>'), pos);
|
||||
size_t end = match_close_tag_end(str, pos);
|
||||
if (type_end == String::npos || end == String::npos) return;
|
||||
String name = str.substr(pos + 11, type_end - pos - 11);
|
||||
WordListP word_list;
|
||||
// find word list type
|
||||
FOR_EACH(wl, getSet().game->word_lists) {
|
||||
if (wl->name == name) {
|
||||
word_list = wl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!word_list) {
|
||||
throw Error(_ERROR_1_("word list type not found", name));
|
||||
}
|
||||
// add to word_lists
|
||||
word_lists.push_back(new_intrusive3<WordListPos>(pos, end, word_list));
|
||||
// next
|
||||
pos = str.find(_("<word-list-"), end);
|
||||
}
|
||||
}
|
||||
|
||||
void TextValueEditor::drawWordListIndicators(RotatedDC& dc) {
|
||||
bool current = isCurrent();
|
||||
FOR_EACH(wl, word_lists) {
|
||||
RealRect& r = wl->rect;
|
||||
if (r.height < 0) {
|
||||
// find the rectangle for this indicator
|
||||
RealRect start = v.charRect(wl->start);
|
||||
RealRect end = v.charRect(wl->end);
|
||||
r.x = start.x;
|
||||
r.y = start.y;
|
||||
r.width = end.right() - start.left() + 0.5;
|
||||
r.height = end.bottom() - start.top();
|
||||
}
|
||||
// draw background
|
||||
if (current && wl->active) {
|
||||
dc.SetPen (Color(0, 128,255));
|
||||
dc.SetBrush(Color(128,192,255));
|
||||
} else if (current) {
|
||||
dc.SetPen (Color(64, 160,255));
|
||||
dc.SetBrush(Color(160,208,255));
|
||||
} else {
|
||||
dc.SetPen (Color(128,128,128));
|
||||
dc.SetBrush(Color(192,192,192));
|
||||
}
|
||||
dc.DrawRectangle(RealRect(style().left + r.right(), style().top + r.top(), 9, r.height));
|
||||
// draw foreground
|
||||
/*
|
||||
dc.SetPen (*wxTRANSPARENT_PEN);
|
||||
dc.SetBrush(*wxBLACK_BRUSH);
|
||||
wxPoint poly[] = {dc.tr(RealPoint(0,0)), dc.tr(RealPoint(5,0)), dc.tr(RealPoint(3,2))};
|
||||
dc.getDC().DrawPolygon(3, poly, style().left + r.right() + 2, style().top + r.bottom() - 5);
|
||||
*/
|
||||
dc.SetPen (*wxBLACK_PEN);
|
||||
double x = style().left + r.right(), y = style().top + r.bottom();
|
||||
dc.DrawLine(RealPoint(x + 4, y - 3), RealPoint(x + 5, y - 3));
|
||||
dc.DrawLine(RealPoint(x + 3, y - 4), RealPoint(x + 6, y - 4));
|
||||
dc.DrawLine(RealPoint(x + 2, y - 5), RealPoint(x + 7, y - 5));
|
||||
}
|
||||
}
|
||||
|
||||
WordListPosP TextValueEditor::findWordList(const RealPoint& pos) const {
|
||||
FOR_EACH_CONST(wl, word_lists) {
|
||||
const RealRect& r = wl->rect;
|
||||
if (pos.x >= r.right() && pos.x < r.right() + 9 &&
|
||||
pos.y >= r.top() && pos.y < r.bottom()) {
|
||||
return wl;
|
||||
}
|
||||
}
|
||||
return WordListPosP();
|
||||
}
|
||||
|
||||
+21
-1
@@ -15,6 +15,8 @@
|
||||
#include <render/value/text.hpp>
|
||||
|
||||
class TextValueEditorScrollBar;
|
||||
DECLARE_POINTER_TYPE(WordListPos);
|
||||
DECLARE_SHARED_POINTER_TYPE(DropDownWordList);
|
||||
|
||||
// ----------------------------------------------------------------------------- : TextValueEditor
|
||||
|
||||
@@ -85,7 +87,7 @@ class TextValueEditor : public TextValueViewer, public ValueEditor {
|
||||
|
||||
// --------------------------------------------------- : Other
|
||||
|
||||
virtual wxCursor cursor() const;
|
||||
virtual wxCursor cursor(const RealPoint& pos) const;
|
||||
virtual void determineSize(bool force_fit = false);
|
||||
virtual void onShow(bool);
|
||||
virtual void draw(RotatedDC&);
|
||||
@@ -94,9 +96,11 @@ class TextValueEditor : public TextValueViewer, public ValueEditor {
|
||||
private:
|
||||
size_t selection_start, selection_end; ///< Cursor position/selection (if any), cursor positions
|
||||
size_t selection_start_i, selection_end_i; ///< Cursor position/selection, character indices
|
||||
bool selecting; ///< Selecting text?
|
||||
bool select_words; ///< Select whole words when dragging the mouse?
|
||||
TextValueEditorScrollBar* scrollbar; ///< Scrollbar for multiline fields in native look
|
||||
bool scroll_with_cursor; ///< When the cursor moves, should the scrollposition change?
|
||||
vector<WordListPosP> word_lists; ///< Word lists in the text
|
||||
|
||||
// --------------------------------------------------- : Selection / movement
|
||||
|
||||
@@ -147,6 +151,22 @@ class TextValueEditor : public TextValueViewer, public ValueEditor {
|
||||
bool ensureCaretVisible();
|
||||
/// Prepare for drawing if there is a scrollbar
|
||||
void prepareDrawScrollbar(RotatedDC& dc);
|
||||
|
||||
// --------------------------------------------------- : Word lists
|
||||
|
||||
friend class DropDownWordList;
|
||||
DropDownWordListP drop_down;
|
||||
bool dropDownShown();
|
||||
|
||||
/// Find all word lists in the current value
|
||||
void findWordLists();
|
||||
/// Draw word list indicators
|
||||
void drawWordListIndicators(RotatedDC& dc);
|
||||
/// Find a WordListPos under the mouse cursor (if any), pos is in internal coordinates
|
||||
WordListPosP findWordList(const RealPoint& pos) const;
|
||||
/// Show a word list drop down menu
|
||||
void wordListDropDown(const WordListPosP& pos);
|
||||
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
@@ -1891,6 +1891,12 @@
|
||||
<File
|
||||
RelativePath=".\data\symbol_font.hpp">
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\data\word_list.cpp">
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\data\word_list.hpp">
|
||||
</File>
|
||||
</Filter>
|
||||
<Filter
|
||||
Name="base"
|
||||
|
||||
@@ -112,7 +112,7 @@ class SimpleTextElement : public TextElement {
|
||||
/// A text element that uses a normal font
|
||||
class FontTextElement : public SimpleTextElement {
|
||||
public:
|
||||
FontTextElement(const String& text, size_t start ,size_t end, const FontP& font, DrawWhat draw_as, LineBreak break_style)
|
||||
FontTextElement(const String& text, size_t start, size_t end, const FontP& font, DrawWhat draw_as, LineBreak break_style)
|
||||
: SimpleTextElement(text, start, end)
|
||||
, font(font), draw_as(draw_as), break_style(break_style)
|
||||
{}
|
||||
@@ -130,7 +130,7 @@ class FontTextElement : public SimpleTextElement {
|
||||
/// A text element that uses a symbol font
|
||||
class SymbolTextElement : public SimpleTextElement {
|
||||
public:
|
||||
SymbolTextElement(const String& text, size_t start ,size_t end, const SymbolFontRef& font, Context* ctx)
|
||||
SymbolTextElement(const String& text, size_t start, size_t end, const SymbolFontRef& font, Context* ctx)
|
||||
: SimpleTextElement(text, start, end)
|
||||
, font(font), ctx(*ctx)
|
||||
{}
|
||||
@@ -149,7 +149,7 @@ class SymbolTextElement : public SimpleTextElement {
|
||||
/// A TextElement consisting of sub elements
|
||||
class CompoundTextElement : public TextElement {
|
||||
public:
|
||||
CompoundTextElement(const String& text, size_t start ,size_t end) : TextElement(text, start, end) {}
|
||||
CompoundTextElement(const String& text, size_t start, size_t end) : TextElement(text, start, end) {}
|
||||
|
||||
virtual void draw (RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end) const;
|
||||
virtual void getCharInfo(RotatedDC& dc, double scale, vector<CharInfo>& out) const;
|
||||
@@ -162,7 +162,7 @@ class CompoundTextElement : public TextElement {
|
||||
/// A TextElement drawn using a grey background
|
||||
class AtomTextElement : public CompoundTextElement {
|
||||
public:
|
||||
AtomTextElement(const String& text, size_t start ,size_t end) : CompoundTextElement(text, start, end) {}
|
||||
AtomTextElement(const String& text, size_t start, size_t end) : CompoundTextElement(text, start, end) {}
|
||||
|
||||
virtual void draw(RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end) const;
|
||||
};
|
||||
@@ -170,7 +170,7 @@ class AtomTextElement : public CompoundTextElement {
|
||||
/// A TextElement drawn using a red wavy underline
|
||||
class ErrorTextElement : public CompoundTextElement {
|
||||
public:
|
||||
ErrorTextElement(const String& text, size_t start ,size_t end) : CompoundTextElement(text, start, end) {}
|
||||
ErrorTextElement(const String& text, size_t start, size_t end) : CompoundTextElement(text, start, end) {}
|
||||
|
||||
virtual void draw(RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end) const;
|
||||
};
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
DECLARE_TYPEOF_COLLECTION(FieldP);
|
||||
DECLARE_TYPEOF_COLLECTION(TextValue*);
|
||||
DECLARE_TYPEOF_COLLECTION(String);
|
||||
DECLARE_TYPEOF_COLLECTION(pair<String COMMA bool>);
|
||||
DECLARE_TYPEOF_COLLECTION(ChoiceField::ChoiceP);
|
||||
|
||||
// ----------------------------------------------------------------------------- : Combined editor
|
||||
@@ -53,41 +54,43 @@ SCRIPT_FUNCTION_WITH_DEP(combined_editor) {
|
||||
}
|
||||
// split the value
|
||||
SCRIPT_PARAM(String, value);
|
||||
vector<String> value_parts;
|
||||
vector<pair<String,bool> > value_parts; // (value part, is empty)
|
||||
size_t pos = value.find(_("<sep"));
|
||||
while (pos != String::npos) {
|
||||
value_parts.push_back(value.substr(0, pos));
|
||||
String part = value.substr(0, pos);
|
||||
value_parts.push_back(make_pair(part, false));
|
||||
value = value.substr(min(match_close_tag_end(value,pos), value.size()));
|
||||
pos = value.find(_("<sep"));
|
||||
}
|
||||
value_parts.push_back(value);
|
||||
value_parts.push_back(make_pair(value, false));
|
||||
value_parts.resize(values.size()); // TODO: what if there are more value_parts than values?
|
||||
// update the values if our input value is newer?
|
||||
Age new_value_update = last_update_age();
|
||||
FOR_EACH_2(v, values, nv, value_parts) {
|
||||
if (v->value() != nv && v->last_update < new_value_update) {
|
||||
// TODO : type over
|
||||
v->value.assign(nv);
|
||||
if (v->value() != nv.first && v->last_update < new_value_update) {
|
||||
v->value.assign(nv.first);
|
||||
v->update(ctx);
|
||||
}
|
||||
nv = v->value();
|
||||
nv.first = v->value();
|
||||
nv.second = index_to_untagged(nv.first, nv.first.size()) == 0;
|
||||
}
|
||||
// options
|
||||
SCRIPT_PARAM_DEFAULT_N(bool, _("hide when empty"), hide_when_empty, false);
|
||||
SCRIPT_PARAM_DEFAULT_N(bool, _("soft before empty"), soft_before_empty, false);
|
||||
// recombine the parts
|
||||
String new_value = value_parts.front();
|
||||
String new_value = value_parts.front().first;
|
||||
bool new_value_empty = value_parts.front().second;
|
||||
for (size_t i = 1 ; i < value_parts.size() ; ++i) {
|
||||
if (value_parts[i].empty() && new_value.empty() && hide_when_empty) {
|
||||
if (value_parts[i].second && new_value_empty && hide_when_empty) {
|
||||
// no separator
|
||||
} else if (value_parts[i].empty() && soft_before_empty) {
|
||||
} else if (value_parts[i].second && soft_before_empty) {
|
||||
// soft separator
|
||||
new_value += _("<sep-soft>") + separators[i - 1] + _("</sep-soft>");
|
||||
} else {
|
||||
// normal separator
|
||||
new_value += _("<sep>") + separators[i - 1] + _("</sep>");
|
||||
new_value += value_parts[i];
|
||||
}
|
||||
new_value += value_parts[i].first;
|
||||
}
|
||||
SCRIPT_RETURN(new_value);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
const Char REMOVED = _('\2');
|
||||
const Char PLACEHOLDER = _('\3');
|
||||
|
||||
String spec_sort(const String& spec, String& input, String& ret);
|
||||
|
||||
// ----------------------------------------------------------------------------- : Iterator for reading specs
|
||||
|
||||
/// Iterator over a sort specification (for spec_sort)
|
||||
@@ -213,6 +215,24 @@ void pattern_sort(const String& pattern, const String& spec, String& input, Stri
|
||||
}
|
||||
}
|
||||
|
||||
/// Sort things in place, keep the rest of the input
|
||||
void in_place_sort(const String& spec, String& input, String& ret) {
|
||||
String result;
|
||||
spec_sort(spec, input, result);
|
||||
// restore into the same order as in 'input'
|
||||
size_t pos_r = 0;
|
||||
FOR_EACH(c, input) {
|
||||
if (c == REMOVED) {
|
||||
if (pos_r < result.size()) {
|
||||
ret += result.GetChar(pos_r++);
|
||||
}
|
||||
} else {
|
||||
ret += c;
|
||||
}
|
||||
}
|
||||
input.clear(); // we ate all the input
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : spec_sort
|
||||
|
||||
String spec_sort(const String& spec, String& input, String& ret) {
|
||||
@@ -262,6 +282,11 @@ String spec_sort(const String& spec, String& input, String& ret) {
|
||||
// sort
|
||||
pattern_sort(pattern, sub_spec, input, ret);
|
||||
|
||||
} else if (it.keyword(_("in_place("))) { // recurse without pattern
|
||||
// read spec to apply to pattern
|
||||
String sub_spec = it.readRawParam(_(')'));
|
||||
in_place_sort(sub_spec, input, ret);
|
||||
|
||||
} else if (it.keyword(_("any()"))) { // remaining input
|
||||
FOR_EACH(d, input) {
|
||||
if (d != REMOVED) {
|
||||
|
||||
Reference in New Issue
Block a user