Commit a0390eff authored by twanvl's avatar twanvl

Thread for generating thumbnail images;

Used for card list;
Implemented reordering from card list
parent c9a49fc5
...@@ -26,12 +26,12 @@ DECLARE_TYPEOF_NO_REV(IndexMap_FieldP_ValueP); ...@@ -26,12 +26,12 @@ DECLARE_TYPEOF_NO_REV(IndexMap_FieldP_ValueP);
// ----------------------------------------------------------------------------- : Set // ----------------------------------------------------------------------------- : Set
Set::Set() Set::Set()
: script_manager(new ScriptManager(*this)) : script_manager(new SetScriptManager(*this))
{} {}
Set::Set(const GameP& game) Set::Set(const GameP& game)
: game(game) : game(game)
, script_manager(new ScriptManager(*this)) , script_manager(new SetScriptManager(*this))
{ {
data.init(game->set_fields); data.init(game->set_fields);
} }
...@@ -39,7 +39,7 @@ Set::Set(const GameP& game) ...@@ -39,7 +39,7 @@ Set::Set(const GameP& game)
Set::Set(const StyleSheetP& stylesheet) Set::Set(const StyleSheetP& stylesheet)
: stylesheet(stylesheet) : stylesheet(stylesheet)
, game(stylesheet->game) , game(stylesheet->game)
, script_manager(new ScriptManager(*this)) , script_manager(new SetScriptManager(*this))
{ {
data.init(game->set_fields); data.init(game->set_fields);
} }
......
...@@ -24,7 +24,8 @@ DECLARE_POINTER_TYPE(Field); ...@@ -24,7 +24,8 @@ DECLARE_POINTER_TYPE(Field);
DECLARE_POINTER_TYPE(Value); DECLARE_POINTER_TYPE(Value);
DECLARE_POINTER_TYPE(Keyword); DECLARE_POINTER_TYPE(Keyword);
DECLARE_INTRUSIVE_POINTER_TYPE(ScriptValue); DECLARE_INTRUSIVE_POINTER_TYPE(ScriptValue);
class ScriptManager; class SetScriptManager;
class SetScriptContext;
class Context; class Context;
class Dependency; class Dependency;
template <typename> class OrderCache; template <typename> class OrderCache;
...@@ -64,6 +65,12 @@ class Set : public Packaged { ...@@ -64,6 +65,12 @@ class Set : public Packaged {
Context& getContext(const CardP& card); Context& getContext(const CardP& card);
/// Update styles for a card /// Update styles for a card
void updateFor(const CardP& card); void updateFor(const CardP& card);
/// A context for performing scripts
/** Should only be used from the thumbnail thread! */
Context& getContextForThumbnails();
/// A context for performing scripts on a particular card
/** Should only be used from the thumbnail thread! */
Context& getContextForThumbnails(const CardP& card);
/// Stylesheet to use for a particular card /// Stylesheet to use for a particular card
/** card may be null */ /** card may be null */
...@@ -94,7 +101,9 @@ class Set : public Packaged { ...@@ -94,7 +101,9 @@ class Set : public Packaged {
DECLARE_REFLECTION(); DECLARE_REFLECTION();
private: private:
/// Object for managing and executing scripts /// Object for managing and executing scripts
scoped_ptr<ScriptManager> script_manager; scoped_ptr<SetScriptManager> script_manager;
/// Object for executing scripts from the thumbnail thread
scoped_ptr<SetScriptContext> thumbnail_script_context;
/// Cache of cards ordered by some criterion /// Cache of cards ordered by some criterion
map<ScriptValueP,OrderCacheP> order_cache; map<ScriptValueP,OrderCacheP> order_cache;
}; };
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include <data/set.hpp> #include <data/set.hpp>
#include <data/card.hpp> #include <data/card.hpp>
#include <data/settings.hpp> #include <data/settings.hpp>
#include <data/stylesheet.hpp>
#include <data/format/clipboard.hpp> #include <data/format/clipboard.hpp>
#include <data/action/set.hpp> #include <data/action/set.hpp>
#include <util/window_id.hpp> #include <util/window_id.hpp>
...@@ -24,6 +25,8 @@ DECLARE_TYPEOF_COLLECTION(FieldP); ...@@ -24,6 +25,8 @@ DECLARE_TYPEOF_COLLECTION(FieldP);
DECLARE_POINTER_TYPE(ChoiceValue); DECLARE_POINTER_TYPE(ChoiceValue);
typedef map<int,FieldP> map_int_FieldP; typedef map<int,FieldP> map_int_FieldP;
DECLARE_TYPEOF(map_int_FieldP); DECLARE_TYPEOF(map_int_FieldP);
typedef IndexMap<FieldP,StyleP> IndexMap_FieldP_StyleP;
DECLARE_TYPEOF_NO_REV(IndexMap_FieldP_StyleP);
// ----------------------------------------------------------------------------- : Events // ----------------------------------------------------------------------------- : Events
...@@ -85,7 +88,7 @@ void CardListBase::onAction(const Action& action, bool undone) { ...@@ -85,7 +88,7 @@ void CardListBase::onAction(const Action& action, bool undone) {
if (sort_criterium) return; // nothing changes for us if (sort_criterium) return; // nothing changes for us
if ((long)action.card_id1 == selected_card_pos || (long)action.card_id2 == selected_card_pos) { if ((long)action.card_id1 == selected_card_pos || (long)action.card_id2 == selected_card_pos) {
// Selected card has moved; also move in the sorted card list // Selected card has moved; also move in the sorted card list
swap(sorted_card_list[action.card_id1] ,sorted_card_list[action.card_id2]); swap(sorted_card_list[action.card_id1],sorted_card_list[action.card_id2]);
// reselect the current card, it has moved // reselect the current card, it has moved
selected_card_pos = (long)action.card_id1 == selected_card_pos ? (long)action.card_id2 : (long)action.card_id1; selected_card_pos = (long)action.card_id1 == selected_card_pos ? (long)action.card_id2 : (long)action.card_id1;
// select the right card // select the right card
...@@ -99,6 +102,9 @@ void CardListBase::onAction(const Action& action, bool undone) { ...@@ -99,6 +102,9 @@ void CardListBase::onAction(const Action& action, bool undone) {
const vector<CardP>& CardListBase::getCards() const { const vector<CardP>& CardListBase::getCards() const {
return set->cards; return set->cards;
} }
const CardP& CardListBase::getCard(long pos) const {
return sorted_card_list[pos];
}
// ----------------------------------------------------------------------------- : CardListBase : Selection // ----------------------------------------------------------------------------- : CardListBase : Selection
...@@ -135,7 +141,7 @@ void CardListBase::selectCardPos(long pos, bool focus) { ...@@ -135,7 +141,7 @@ void CardListBase::selectCardPos(long pos, bool focus) {
if (selected_card_pos == pos && !focus) return; // this card is already selected if (selected_card_pos == pos && !focus) return; // this card is already selected
if ((size_t)pos < sorted_card_list.size()) { if ((size_t)pos < sorted_card_list.size()) {
// only if there is something to select // only if there is something to select
selectCard(sorted_card_list[pos], false, true); selectCard(getCard(pos), false, true);
} else { } else {
selectCard(CardP(), false, true); selectCard(CardP(), false, true);
} }
...@@ -148,7 +154,7 @@ void CardListBase::findSelectedCardPos() { ...@@ -148,7 +154,7 @@ void CardListBase::findSelectedCardPos() {
long count = GetItemCount(); long count = GetItemCount();
selected_card_pos = -1; selected_card_pos = -1;
for (long pos = 0 ; pos < count ; ++pos) { for (long pos = 0 ; pos < count ; ++pos) {
if (sorted_card_list[pos] == selected_card) { if (getCard(pos) == selected_card) {
selected_card_pos = pos; selected_card_pos = pos;
break; break;
} }
...@@ -298,13 +304,12 @@ void CardListBase::refreshList() { ...@@ -298,13 +304,12 @@ void CardListBase::refreshList() {
} }
ChoiceStyleP CardListBase::findColorStyle() { ChoiceStyleP CardListBase::findColorStyle() {
/* FOR_EACH(s, set->default_stylesheet->card_style) { FOR_EACH(s, set->stylesheet->card_style) {
ChoiceStyleP cs = dynamic_cast<ChoiceStyleP>(s); ChoiceStyleP cs = dynamic_pointer_cast<ChoiceStyle>(s);
if (cs && cs->colors_card_list) { if (cs && cs->colors_card_list) {
return cs; return cs;
} }
} }
*/
return ChoiceStyleP(); return ChoiceStyleP();
} }
...@@ -338,7 +343,7 @@ String CardListBase::OnGetItemText(long pos, long col) const { ...@@ -338,7 +343,7 @@ String CardListBase::OnGetItemText(long pos, long col) const {
// wx may give us non existing columns! // wx may give us non existing columns!
return wxEmptyString; return wxEmptyString;
} }
ValueP val = sorted_card_list[pos]->data[column_fields[col]]; ValueP val = getCard(pos)->data[column_fields[col]];
if (val) return val->toString(); if (val) return val->toString();
else return wxEmptyString; else return wxEmptyString;
} }
...@@ -349,7 +354,7 @@ int CardListBase::OnGetItemImage(long pos) const { ...@@ -349,7 +354,7 @@ int CardListBase::OnGetItemImage(long pos) const {
wxListItemAttr* CardListBase::OnGetItemAttr(long pos) const { wxListItemAttr* CardListBase::OnGetItemAttr(long pos) const {
if (!color_style) return nullptr; if (!color_style) return nullptr;
ChoiceValueP val = static_pointer_cast<ChoiceValue>( sorted_card_list[pos]->data[color_style->fieldP]); ChoiceValueP val = static_pointer_cast<ChoiceValue>( getCard(pos)->data[color_style->fieldP]);
assert(val); assert(val);
item_attr.SetTextColour(color_style->choice_colors[val->value()]); // if it doesn't exist we get black item_attr.SetTextColour(color_style->choice_colors[val->value()]); // if it doesn't exist we get black
return &item_attr; return &item_attr;
...@@ -412,7 +417,20 @@ void CardListBase::onChar(wxKeyEvent& ev) { ...@@ -412,7 +417,20 @@ void CardListBase::onChar(wxKeyEvent& ev) {
} }
void CardListBase::onDrag(wxMouseEvent& ev) { void CardListBase::onDrag(wxMouseEvent& ev) {
// TODO if (ev.Dragging() && selected_card && !sort_criterium) {
// reorder card list
int flags;
long item = HitTest(ev.GetPosition(), flags);
if (flags & wxLIST_HITTEST_ONITEM) {
if (item > 0) EnsureVisible(item-1);
if (item < GetItemCount()-1) EnsureVisible(item+1);
findSelectedCardPos();
if (item != selected_card_pos) {
// move card in the set
set->actions.add(new ReorderCardsAction(*set, item, selected_card_pos));
}
}
}
} }
// ----------------------------------------------------------------------------- : CardListBase : Event table // ----------------------------------------------------------------------------- : CardListBase : Event table
......
...@@ -41,9 +41,6 @@ struct CardSelectEvent : public wxCommandEvent { ...@@ -41,9 +41,6 @@ struct CardSelectEvent : public wxCommandEvent {
* *
* Note: (long) pos refers to position in the sorted_card_list, * Note: (long) pos refers to position in the sorted_card_list,
* (size_t) index refers to the index in the actual card list (as returned by getCards). * (size_t) index refers to the index in the actual card list (as returned by getCards).
*
* This class is an abstract base class for card lists, derived classes must overload:
* - getCard(index)
*/ */
class CardListBase : public wxListView, public SetView { class CardListBase : public wxListView, public SetView {
public: public:
...@@ -84,24 +81,26 @@ class CardListBase : public wxListView, public SetView { ...@@ -84,24 +81,26 @@ class CardListBase : public wxListView, public SetView {
protected: protected:
/// What cards should be shown? /// What cards should be shown?
virtual const vector<CardP>& getCards() const; virtual const vector<CardP>& getCards() const;
/// Return the card at the given position in the sorted card list
const CardP& getCard(long pos) const;
/// Rebuild the card list (clear all vectors and fill them again) /// Rebuild the card list (clear all vectors and fill them again)
void rebuild(); void rebuild();
/// Do some additional updating before rebuilding the list /// Do some additional updating before rebuilding the list
virtual void onRebuild() {} virtual void onRebuild() {}
/// Can the card list be modified? /// Can the card list be modified?
virtual bool allowModify() const { return true; } virtual bool allowModify() const { return false; }
// --------------------------------------------------- : Item 'events' // --------------------------------------------------- : Item 'events'
/// Get the text of an item in a specific column /// Get the text of an item in a specific column
/** Overrides a function from wxListCtrl */ /** Overrides a function from wxListCtrl */
String OnGetItemText (long pos, long col) const; virtual String OnGetItemText (long pos, long col) const;
/// Get the image of an item, by default no image is used /// Get the image of an item, by default no image is used
/** Overrides a function from wxListCtrl */ /** Overrides a function from wxListCtrl */
int OnGetItemImage(long pos) const; virtual int OnGetItemImage(long pos) const;
/// Get the color for an item /// Get the color for an item
wxListItemAttr* OnGetItemAttr(long pos) const; virtual wxListItemAttr* OnGetItemAttr(long pos) const;
// --------------------------------------------------- : Data // --------------------------------------------------- : Data
private: private:
......
...@@ -20,8 +20,8 @@ const vector<CardP>& FilteredCardList::getCards() const { ...@@ -20,8 +20,8 @@ const vector<CardP>& FilteredCardList::getCards() const {
return cards; return cards;
} }
void FilteredCardList::setFilter(const CardListFilterP& filter_) { void FilteredCardList::setFilter(const CardListFilterP& filter) {
filter = filter_; this->filter = filter;
rebuild(); rebuild();
} }
......
...@@ -32,15 +32,13 @@ class FilteredCardList : public CardListBase { ...@@ -32,15 +32,13 @@ class FilteredCardList : public CardListBase {
FilteredCardList(Window* parent, int id, long style = 0); FilteredCardList(Window* parent, int id, long style = 0);
/// Change the filter to use /// Change the filter to use
void setFilter(const CardListFilterP& filter_); void setFilter(const CardListFilterP& filter);
protected: protected:
/// Get only the subset of the cards /// Get only the subset of the cards
virtual const vector<CardP>& getCards() const; virtual const vector<CardP>& getCards() const;
/// Rebuild the filtered card list /// Rebuild the filtered card list
virtual void onRebuild(); virtual void onRebuild();
// /// Don't reorder
// virtual void onDrag(wxMouseEvent& ev);
private: private:
CardListFilterP filter; ///< Filter with which this.cards is made CardListFilterP filter; ///< Filter with which this.cards is made
......
//+----------------------------------------------------------------------------+
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
//| Copyright: (C) 2001 - 2006 Twan van Laarhoven |
//| License: GNU General Public License 2 or later (see file COPYING) |
//+----------------------------------------------------------------------------+
// ----------------------------------------------------------------------------- : Includes
#include <gui/control/image_card_list.hpp>
#include <gui/thumbnail_thread.hpp>
#include <data/field/image.hpp>
#include <data/game.hpp>
#include <data/card.hpp>
#include <gfx/gfx.hpp>
DECLARE_TYPEOF_COLLECTION(FieldP);
// ----------------------------------------------------------------------------- : ImageCardList
ImageCardList::ImageCardList(Window* parent, int id, long additional_style)
: CardListBase(parent, id, additional_style)
{}
ImageCardList::~ImageCardList() {
thumbnail_thread.abort(this);
}
void ImageCardList::onRebuild() {
image_field = findImageField();
}
void ImageCardList::onBeforeChangeSet() {
CardListBase::onBeforeChangeSet();
// remove all but the first two (sort asc/desc) images from image list
wxImageList* il = GetImageList(wxIMAGE_LIST_SMALL);
while (il && il->GetImageCount() > 2) {
il->Remove(2);
}
thumbnail_thread.abort(this);
thumbnails.clear();
}
ImageFieldP ImageCardList::findImageField() {
FOR_EACH(f, set->game->card_fields) {
ImageFieldP imgf = dynamic_pointer_cast<ImageField>(f);
if (imgf) return imgf;
}
return ImageFieldP();
}
/// A request for a thumbnail of a card image
class CardThumbnailRequest : public ThumbnailRequest {
public:
CardThumbnailRequest(ImageCardList* parent, const String& filename)
: ThumbnailRequest(
parent,
_("card") + parent->set->absoluteFilename() + _("-") + filename,
wxDateTime::Now()) // TODO: Find mofication time of card image
, filename(filename)
{}
virtual Image generate() {
ImageCardList* parent = (ImageCardList*)owner;
Image image;
if (image.LoadFile(*parent->set->openIn(filename))) {
// two step anti aliased resampling
image.Rescale(36, 28); // step 1: no anti aliassing
Image image2(18, 14, false); // step 2: with anti aliassing
resample(image, image2);
return image2;
} else {
return Image();
}
}
virtual void store(const Image& img) {
// add finished bitmap to the imagelist
ImageCardList* parent = (ImageCardList*)owner;
if (img.Ok()) {
wxImageList* il = parent->GetImageList(wxIMAGE_LIST_SMALL);
int id = il->Add(wxBitmap(img));
parent->thumbnails.insert(make_pair(filename, id));
parent->Refresh(false);
}
}
private:
String filename;
};
int ImageCardList::OnGetItemImage(long pos) const {
if (image_field) {
// Image = thumbnail of first image field of card
ImageValue& val = static_cast<ImageValue&>(*getCard(pos)->data[image_field]);
if (!val.filename) return -1; // no image
// is there already a thumbnail?
map<String,int>::const_iterator it = thumbnails.find(val.filename);
if (it != thumbnails.end()) {
return it->second;
} else {
// request a thumbnail
thumbnail_thread.request(new_shared2<CardThumbnailRequest>(const_cast<ImageCardList*>(this), val.filename));
}
}
return -1;
}
void ImageCardList::onIdle(wxIdleEvent&) {
thumbnail_thread.done(this);
}
BEGIN_EVENT_TABLE(ImageCardList, CardListBase)
EVT_IDLE (ImageCardList::onIdle)
END_EVENT_TABLE ()
//+----------------------------------------------------------------------------+
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
//| Copyright: (C) 2001 - 2006 Twan van Laarhoven |
//| License: GNU General Public License 2 or later (see file COPYING) |
//+----------------------------------------------------------------------------+
#ifndef HEADER_GUI_CONTROL_IMAGE_CARD_LIST
#define HEADER_GUI_CONTROL_IMAGE_CARD_LIST
// ----------------------------------------------------------------------------- : Includes
#include <util/prec.hpp>
#include <gui/control/card_list.hpp>
DECLARE_POINTER_TYPE(ImageField);
// ----------------------------------------------------------------------------- : ImageCardList
/// A card list that allows the shows thumbnails of card images
/** This card list also allows the list to be modified */
class ImageCardList : public CardListBase {
public:
~ImageCardList();
ImageCardList(Window* parent, int id, long additional_style = 0);
protected:
virtual int OnGetItemImage(long pos) const;
virtual void onRebuild();
virtual void onBeforeChangeSet();
virtual bool allowModify() const { return true; }
private:
DECLARE_EVENT_TABLE();
void onIdle(wxIdleEvent&);
ImageFieldP image_field; ///< Field to use for card images
mutable map<String,int> thumbnails; ///< image thumbnails, based on image_field
ImageFieldP findImageField();
friend class CardThumbnailRequest;
};
// ----------------------------------------------------------------------------- : EOF
#endif
//+----------------------------------------------------------------------------+
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
//| Copyright: (C) 2001 - 2006 Twan van Laarhoven |
//| License: GNU General Public License 2 or later (see file COPYING) |
//+----------------------------------------------------------------------------+
// ----------------------------------------------------------------------------- : Includes
#include <gui/control/select_card_list.hpp>
// ----------------------------------------------------------------------------- : SelectCardList
// TODO
//+----------------------------------------------------------------------------+
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
//| Copyright: (C) 2001 - 2006 Twan van Laarhoven |
//| License: GNU General Public License 2 or later (see file COPYING) |
//+----------------------------------------------------------------------------+
#ifndef HEADER_GUI_CONTROL_SELECT_CARD_LIST
#define HEADER_GUI_CONTROL_SELECT_CARD_LIST
// ----------------------------------------------------------------------------- : Includes
#include <util/prec.hpp>
#include <gui/control/card_list.hpp>
// ----------------------------------------------------------------------------- : SelectCardList
/// A card list with check boxes
class SelectCardList : public CardListBase {
public:
SelectCardList(Window* parent, int id, long additional_style = 0);
/// Select all cards
void selectAll();
/// Deselect all cards
void selectNone();
/// Is the given card selected?
bool isSelected(const CardP& card) const;
protected:
int OnGetItemImage(long pos) const;
private:
DECLARE_EVENT_TABLE();
set<CardP> selected; ///< which cards are selected?
void toggle(const CardP& card);
void onKeyDown(wxKeyEvent&);
void onLeftDown(wxMouseEvent&);
};
// ----------------------------------------------------------------------------- : EOF
#endif
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
// ----------------------------------------------------------------------------- : Includes // ----------------------------------------------------------------------------- : Includes
#include <gui/set/cards_panel.hpp> #include <gui/set/cards_panel.hpp>
#include <gui/control/card_list.hpp> #include <gui/control/image_card_list.hpp>
#include <gui/control/card_editor.hpp> #include <gui/control/card_editor.hpp>
#include <gui/control/text_ctrl.hpp> #include <gui/control/text_ctrl.hpp>
#include <gui/icon_menu.hpp> #include <gui/icon_menu.hpp>
...@@ -29,8 +29,7 @@ CardsPanel::CardsPanel(Window* parent, int id) ...@@ -29,8 +29,7 @@ CardsPanel::CardsPanel(Window* parent, int id)
wxSplitterWindow* splitter; wxSplitterWindow* splitter;
editor = new CardEditor(this, ID_EDITOR); editor = new CardEditor(this, ID_EDITOR);
splitter = new wxSplitterWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0); splitter = new wxSplitterWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0);
// card_list = new EditCardList(splitter, ID_CARD_LIST); card_list = new ImageCardList(splitter, ID_CARD_LIST);
card_list = new CardListBase(splitter, ID_CARD_LIST);
notesP = new Panel(splitter, wxID_ANY); notesP = new Panel(splitter, wxID_ANY);
notes = new TextCtrl(notesP, ID_NOTES); notes = new TextCtrl(notesP, ID_NOTES);
// init sizer for notes panel // init sizer for notes panel
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
#include <gui/set/panel.hpp> #include <gui/set/panel.hpp>
class wxSplitterWindow; class wxSplitterWindow;
class CardListBase; class ImageCardList;
class DataEditor; class DataEditor;
class TextCtrl; class TextCtrl;
...@@ -93,7 +93,7 @@ class CardsPanel : public SetWindowPanel { ...@@ -93,7 +93,7 @@ class CardsPanel : public SetWindowPanel {
// --------------------------------------------------- : Controls // --------------------------------------------------- : Controls
wxSplitterWindow* splitter; wxSplitterWindow* splitter;
DataEditor* editor; DataEditor* editor;
CardListBase* card_list; ImageCardList* card_list;
TextCtrl* notes; TextCtrl* notes;
// --------------------------------------------------- : Menus & tools // --------------------------------------------------- : Menus & tools
......
//+----------------------------------------------------------------------------+
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
//| Copyright: (C) 2001 - 2006 Twan van Laarhoven |
//| License: GNU General Public License 2 or later (see file COPYING) |
//+----------------------------------------------------------------------------+
// ----------------------------------------------------------------------------- : Includes
#include <gui/thumbnail_thread.hpp>
#include <wx/thread.h>
typedef pair<ThumbnailRequestP,Image> pair_ThumbnailRequestP_Image;
DECLARE_TYPEOF_COLLECTION(pair_ThumbnailRequestP_Image);
// ----------------------------------------------------------------------------- : ThumbnailThreadWorker
class ThumbnailThreadWorker : public wxThread {
public:
ThumbnailThreadWorker(ThumbnailThread* parent);
virtual ExitCode Entry();
ThumbnailRequestP current; ///< Request we are working on
ThumbnailThread* parent;
bool stop; ///< Suspend computation
};
ThumbnailThreadWorker::ThumbnailThreadWorker(ThumbnailThread* parent)
: parent(parent)
, stop(false)
{}
wxThread::ExitCode ThumbnailThreadWorker::Entry() {
while (true) {
do {
Sleep(1);
if (TestDestroy()) return 0;
} while (stop);
// get a request
{
wxMutexLocker lock(parent->mutex);
if (parent->open_requests.empty()) {
parent->worker = nullptr;
return 0; // No more requests
}
current = parent->open_requests.front();
parent->open_requests.pop_front();
}
// perform request
if (TestDestroy()) return 0;
Image img = current->generate();
if (TestDestroy()) return 0;
// store result
{
wxMutexLocker lock(parent->mutex);
parent->closed_requests.push_back(make_pair(current,img));
current.reset();
parent->completed.Signal();
}
}
}
bool operator < (const ThumbnailRequestP& a, const ThumbnailRequestP& b) {
if (a->owner < b->owner) return true;
if (a->owner > b->owner) return false;
return a->cache_name < b->cache_name;
}
// ----------------------------------------------------------------------------- : ThumbnailThread
ThumbnailThread thumbnail_thread;
ThumbnailThread::ThumbnailThread()
: completed(mutex)
, worker(nullptr)
{}
ThumbnailThread::~ThumbnailThread() {
abortAll();
}
String user_settings_dir();
String image_cache_dir() {
String dir = user_settings_dir() + _("/cache");
if (!wxDirExists(dir)) wxMkDir(dir);
return dir + _("/");
}
String ThumbnailThread::safeFilename(const String& str) {
String ret; ret.reserve(str.size());
FOR_EACH_CONST(c, str) {
if (isAlnum(c)) {
ret += c;
} else if (c==_(' ') || c==_('-')) {
ret += _('-');
} else {
ret += _('_');
}
}
return ret;
}
void ThumbnailThread::request(const ThumbnailRequestP& request) {
assert(wxThread::IsMain());
// Is the request in progress?
if (request_names.find(request) != request_names.end()) {
return;
}
request_names.insert(request);
// Is the image in the cache?
String filename = image_cache_dir() + safeFilename(request->cache_name) + _(".png");
wxFileName fn(filename);
if (fn.FileExists()) {
wxDateTime modified;
if (fn.GetTimes(0, &modified, 0) && modified >= request->modified) {
// yes it is
Image img(filename);
request->store(img);
return;
}
}
// request generation
{
wxMutexLocker lock(mutex);
open_requests.push_back(request);
}
// is there a worker?
if (!worker) {
worker = new ThumbnailThreadWorker(this);
worker->Create();
worker->Run();
}
}
bool ThumbnailThread::done(void* owner) {
assert(wxThread::IsMain());
// find finished requests
vector<pair<ThumbnailRequestP,Image> > finished;
{
wxMutexLocker lock(mutex);
for (size_t i = 0 ; i < closed_requests.size() ; ) {
if (closed_requests[i].first->owner == owner) {
// move to finished list
finished.push_back(closed_requests[i]);
closed_requests.erase(closed_requests.begin() + i, closed_requests.begin() + i + 1);
} else {
++i;
}
}
}
// store them
FOR_EACH(r, finished) {
// store image
r.first->store(r.second);
// store in cache
String filename = image_cache_dir() + safeFilename(r.first->cache_name) + _(".png");
r.second.SaveFile(filename, wxBITMAP_TYPE_PNG);
// set modification time
wxFileName fn(filename);
fn.SetTimes(0, &r.first->modified, 0);
// remove from name list
request_names.erase(r.first);
}
return !finished.empty();
}
void ThumbnailThread::abort(void* owner) {
assert(wxThread::IsMain());
mutex.Lock();
if (worker && worker->current->owner == owner) {
// a request for this owner is in progress, wait until it is done
worker->stop = true;
completed.Wait();
mutex.Lock();
worker->stop = false;
}
// remove open requests for this owner
for (size_t i = 0 ; i < open_requests.size() ; ) {
if (open_requests[i]->owner == owner) {
// remove
open_requests.erase(open_requests.begin() + i, open_requests.begin() + i + 1);
request_names.erase(open_requests[i]);
} else {
++i;
}
}
// remove closed requests for this owner
for (size_t i = 0 ; i < closed_requests.size() ; ) {
if (closed_requests[i].first->owner == owner) {
// remove
closed_requests.erase(closed_requests.begin() + i, closed_requests.begin() + i + 1);
request_names.erase(closed_requests[i].first);
} else {
++i;
}
}
mutex.Unlock();
}
void ThumbnailThread::abortAll() {
assert(wxThread::IsMain());
mutex.Lock();
open_requests.clear();
closed_requests.clear();
request_names.clear();
if (worker) {
// a request is in progress, wait until it is done, killing the worker
completed.Wait();
} else {
mutex.Unlock();
}
}
//+----------------------------------------------------------------------------+
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
//| Copyright: (C) 2001 - 2006 Twan van Laarhoven |
//| License: GNU General Public License 2 or later (see file COPYING) |
//+----------------------------------------------------------------------------+
#ifndef HEADER_GUI_THUMBNAIL_THREAD
#define HEADER_GUI_THUMBNAIL_THREAD
// ----------------------------------------------------------------------------- : Includes
#include <util/prec.hpp>
#include <wx/datetime.h>
#include <wx/filename.h>
#include <queue>
DECLARE_POINTER_TYPE(ThumbnailRequest);
// ----------------------------------------------------------------------------- : ThumbnailRequest
/// A request for some kind of thumbnail
class ThumbnailRequest {
public:
ThumbnailRequest(void* owner, const String& cache_name, const wxDateTime& modified)
: owner(owner), cache_name(cache_name), modified(modified) {}
/// Generate the thumbnail, called in another thread
virtual Image generate() = 0;
/// Store the thumbnail, called from the main thread
virtual void store(const Image&) = 0;
/// Object that requested the thumbnail
void* const owner;
/// Name under which this object will be stored in the image cache
String cache_name;
/// Modification time for the object of which the thumnail is generated
wxDateTime modified;
};
// ----------------------------------------------------------------------------- : ThumbnailThread
/// A (generic) class that generates thumbnails in another thread
/** All requests have an 'owner', the object that requested the thumbnail.
* This object should regularly call "done(this)".
* Multiple requests can be open at the same time.
* Thumbnails are cached, and need not be generated in a thread
*/
class ThumbnailThread {
public:
ThumbnailThread();
~ThumbnailThread();
/// Request a thumbnail, it may be store()d immediatly if the thumbnail is cached
void request(const ThumbnailRequestP& request);
/// Is one or more thumbnail for the given owner finished?
/** If so, call their store() functions */
bool done(void* owner);
/// Abort all thumbnail requests for the given owner
void abort(void* owner);
/// Abort all computations
void abortAll();
private:
wxMutex mutex; ///< Mutex used by the worker when accessing the request lists or the thread pointer
wxCondition completed; ///< Event signaled when a request is completed
deque<ThumbnailRequestP> open_requests; ///< Requests on which work hasn't finished
vector<pair<ThumbnailRequestP,Image> > closed_requests; ///< Requests for which work is completed
set<ThumbnailRequestP> request_names; ///< Requests that haven't been stored yet, to prevent duplicates
friend class ThumbnailThreadWorker;
ThumbnailThreadWorker* worker; ///< The worker thread. invariant: no requests ==> worker==nullptr
/// A name that is safe to use as a filename, for the cache
static String safeFilename(const String& str);
};
/// The global thumbnail generator thread
extern ThumbnailThread thumbnail_thread;
// ----------------------------------------------------------------------------- : EOF
#endif
...@@ -573,6 +573,12 @@ ...@@ -573,6 +573,12 @@
<File <File
RelativePath=".\gui\control\graph.hpp"> RelativePath=".\gui\control\graph.hpp">
</File> </File>
<File
RelativePath=".\gui\control\image_card_list.cpp">
</File>
<File
RelativePath=".\gui\control\image_card_list.hpp">
</File>
<File <File
RelativePath=".\gui\control\native_look_editor.cpp"> RelativePath=".\gui\control\native_look_editor.cpp">
</File> </File>
...@@ -585,6 +591,12 @@ ...@@ -585,6 +591,12 @@
<File <File
RelativePath=".\gui\control\package_list.hpp"> RelativePath=".\gui\control\package_list.hpp">
</File> </File>
<File
RelativePath=".\gui\control\select_card_list.cpp">
</File>
<File
RelativePath=".\gui\control\select_card_list.hpp">
</File>
<File <File
RelativePath=".\gui\control\text_ctrl.cpp"> RelativePath=".\gui\control\text_ctrl.cpp">
</File> </File>
...@@ -851,6 +863,12 @@ ...@@ -851,6 +863,12 @@
<File <File
RelativePath=".\gui\preferences_window.hpp"> RelativePath=".\gui\preferences_window.hpp">
</File> </File>
<File
RelativePath=".\gui\thumbnail_thread.cpp">
</File>
<File
RelativePath=".\gui\thumbnail_thread.hpp">
</File>
<File <File
RelativePath=".\gui\update_checker.cpp"> RelativePath=".\gui\update_checker.cpp">
</File> </File>
......
...@@ -30,26 +30,20 @@ DECLARE_TYPEOF_NO_REV(IndexMap_FieldP_ValueP); ...@@ -30,26 +30,20 @@ DECLARE_TYPEOF_NO_REV(IndexMap_FieldP_ValueP);
void init_script_functions(Context& ctx); void init_script_functions(Context& ctx);
void init_script_image_functions(Context& ctx); void init_script_image_functions(Context& ctx);
// ----------------------------------------------------------------------------- : ScriptManager : initialization // ----------------------------------------------------------------------------- : SetScriptContext : initialization
ScriptManager::ScriptManager(Set& set) SetScriptContext::SetScriptContext(Set& set)
: set(set) : set(set)
{ {}
// add as an action listener for the set, so we receive actions
set.actions.addListener(this);
}
ScriptManager::~ScriptManager() { SetScriptContext::~SetScriptContext() {
set.actions.removeListener(this); // destroy contexts
// destroy context
FOR_EACH(sc, contexts) { FOR_EACH(sc, contexts) {
delete sc.second; delete sc.second;
} }
} }
Context& ScriptManager::getContext(const StyleSheetP& stylesheet) { Context& SetScriptContext::getContext(const StyleSheetP& stylesheet) {
assert(wxThread::IsMain()); // only use our contexts from the main thread
Contexts::iterator it = contexts.find(stylesheet.get()); Contexts::iterator it = contexts.find(stylesheet.get());
if (it != contexts.end()) { if (it != contexts.end()) {
return *it->second; // we already have a context return *it->second; // we already have a context
...@@ -67,23 +61,11 @@ Context& ScriptManager::getContext(const StyleSheetP& stylesheet) { ...@@ -67,23 +61,11 @@ Context& ScriptManager::getContext(const StyleSheetP& stylesheet) {
ctx->setVariable(_("stylesheet"), toScript(stylesheet)); ctx->setVariable(_("stylesheet"), toScript(stylesheet));
ctx->setVariable(_("card"), set.cards.empty() ? script_nil : toScript(set.cards.front())); // dummy value ctx->setVariable(_("card"), set.cards.empty() ? script_nil : toScript(set.cards.front())); // dummy value
ctx->setVariable(_("styling"), toScript(&set.stylingDataFor(*stylesheet))); ctx->setVariable(_("styling"), toScript(&set.stylingDataFor(*stylesheet)));
try { onInit(stylesheet, ctx);
// perform init scripts, don't use a scope, variables stay bound in the context
set.game ->init_script.invoke(*ctx, false);
stylesheet->init_script.invoke(*ctx, false);
// find script dependencies
initDependencies(*ctx, *set.game);
initDependencies(*ctx, *stylesheet);
// apply scripts to everything
updateAll();
} catch (Error e) {
handle_error(e, false, false);
}
// initialize dependencies
return *ctx; return *ctx;
} }
} }
Context& ScriptManager::getContext(const CardP& card) { Context& SetScriptContext::getContext(const CardP& card) {
Context& ctx = getContext(set.stylesheetFor(card)); Context& ctx = getContext(set.stylesheetFor(card));
if (card) { if (card) {
ctx.setVariable(_("card"), toScript(card)); ctx.setVariable(_("card"), toScript(card));
...@@ -93,7 +75,37 @@ Context& ScriptManager::getContext(const CardP& card) { ...@@ -93,7 +75,37 @@ Context& ScriptManager::getContext(const CardP& card) {
return ctx; return ctx;
} }
void ScriptManager::initDependencies(Context& ctx, Game& game) { // ----------------------------------------------------------------------------- : SetScriptManager : initialization
SetScriptManager::SetScriptManager(Set& set)
: SetScriptContext(set)
{
// add as an action listener for the set, so we receive actions
set.actions.addListener(this);
}
SetScriptManager::~SetScriptManager() {
set.actions.removeListener(this);
}
void SetScriptManager::onInit(const StyleSheetP& stylesheet, Context* ctx) {
assert(wxThread::IsMain());
// initialize dependencies
try {
// perform init scripts, don't use a scope, variables stay bound in the context
set.game ->init_script.invoke(*ctx, false);
stylesheet->init_script.invoke(*ctx, false);
// find script dependencies
initDependencies(*ctx, *set.game);
initDependencies(*ctx, *stylesheet);
// apply scripts to everything
updateAll();
} catch (Error e) {
handle_error(e, false, false);
}
}
void SetScriptManager::initDependencies(Context& ctx, Game& game) {
if (game.dependencies_initialized) return; if (game.dependencies_initialized) return;
game.dependencies_initialized = true; game.dependencies_initialized = true;
// find dependencies of card fields // find dependencies of card fields
...@@ -107,7 +119,7 @@ void ScriptManager::initDependencies(Context& ctx, Game& game) { ...@@ -107,7 +119,7 @@ void ScriptManager::initDependencies(Context& ctx, Game& game) {
} }
void ScriptManager::initDependencies(Context& ctx, StyleSheet& stylesheet) { void SetScriptManager::initDependencies(Context& ctx, StyleSheet& stylesheet) {
if (stylesheet.dependencies_initialized) return; if (stylesheet.dependencies_initialized) return;
stylesheet.dependencies_initialized = true; stylesheet.dependencies_initialized = true;
// find dependencies of choice images and other style stuff // find dependencies of choice images and other style stuff
...@@ -118,7 +130,7 @@ void ScriptManager::initDependencies(Context& ctx, StyleSheet& stylesheet) { ...@@ -118,7 +130,7 @@ void ScriptManager::initDependencies(Context& ctx, StyleSheet& stylesheet) {
// ----------------------------------------------------------------------------- : ScriptManager : updating // ----------------------------------------------------------------------------- : ScriptManager : updating
void ScriptManager::onAction(const Action& action, bool undone) { void SetScriptManager::onAction(const Action& action, bool undone) {
TYPE_CASE(action, ValueAction) { TYPE_CASE(action, ValueAction) {
// find the affected card // find the affected card
FOR_EACH(card, set.cards) { FOR_EACH(card, set.cards) {
...@@ -134,7 +146,7 @@ void ScriptManager::onAction(const Action& action, bool undone) { ...@@ -134,7 +146,7 @@ void ScriptManager::onAction(const Action& action, bool undone) {
} }
} }
void ScriptManager::updateStyles(const CardP& card) { void SetScriptManager::updateStyles(const CardP& card) {
// lastUpdatedCard = card; // lastUpdatedCard = card;
StyleSheetP stylesheet = set.stylesheetFor(card); StyleSheetP stylesheet = set.stylesheetFor(card);
Context& ctx = getContext(card); Context& ctx = getContext(card);
...@@ -148,7 +160,7 @@ void ScriptManager::updateStyles(const CardP& card) { ...@@ -148,7 +160,7 @@ void ScriptManager::updateStyles(const CardP& card) {
} }
} }
void ScriptManager::updateValue(Value& value, const CardP& card) { void SetScriptManager::updateValue(Value& value, const CardP& card) {
Age starting_age; // the start of the update process Age starting_age; // the start of the update process
deque<ToUpdate> to_update; deque<ToUpdate> to_update;
// execute script for initial changed value // execute script for initial changed value
...@@ -158,7 +170,7 @@ void ScriptManager::updateValue(Value& value, const CardP& card) { ...@@ -158,7 +170,7 @@ void ScriptManager::updateValue(Value& value, const CardP& card) {
updateRecursive(to_update, starting_age); updateRecursive(to_update, starting_age);
} }
void ScriptManager::updateAll() { void SetScriptManager::updateAll() {
// update set data // update set data
Context& ctx = getContext(set.stylesheet); Context& ctx = getContext(set.stylesheet);
FOR_EACH(v, set.data) { FOR_EACH(v, set.data) {
...@@ -175,14 +187,14 @@ void ScriptManager::updateAll() { ...@@ -175,14 +187,14 @@ void ScriptManager::updateAll() {
updateAllDependend(set.game->dependent_scripts_cards); updateAllDependend(set.game->dependent_scripts_cards);
} }
void ScriptManager::updateAllDependend(const vector<Dependency>& dependent_scripts) { void SetScriptManager::updateAllDependend(const vector<Dependency>& dependent_scripts) {
deque<ToUpdate> to_update; deque<ToUpdate> to_update;
Age starting_age; Age starting_age;
alsoUpdate(to_update, dependent_scripts, CardP()); alsoUpdate(to_update, dependent_scripts, CardP());
updateRecursive(to_update, starting_age); updateRecursive(to_update, starting_age);
} }
void ScriptManager::updateRecursive(deque<ToUpdate>& to_update, Age starting_age) { void SetScriptManager::updateRecursive(deque<ToUpdate>& to_update, Age starting_age) {
// set->order_cache.clear(); // clear caches before evaluating a round of scripts // set->order_cache.clear(); // clear caches before evaluating a round of scripts
while (!to_update.empty()) { while (!to_update.empty()) {
updateToUpdate(to_update.front(), to_update, starting_age); updateToUpdate(to_update.front(), to_update, starting_age);
...@@ -190,7 +202,7 @@ void ScriptManager::updateRecursive(deque<ToUpdate>& to_update, Age starting_age ...@@ -190,7 +202,7 @@ void ScriptManager::updateRecursive(deque<ToUpdate>& to_update, Age starting_age
} }
} }
void ScriptManager::updateToUpdate(const ToUpdate& u, deque<ToUpdate>& to_update, Age starting_age) { void SetScriptManager::updateToUpdate(const ToUpdate& u, deque<ToUpdate>& to_update, Age starting_age) {
Age age = u.value->last_script_update; Age age = u.value->last_script_update;
if (starting_age < age) return; // this value was already updated if (starting_age < age) return; // this value was already updated
Context& ctx = getContext(u.card); Context& ctx = getContext(u.card);
...@@ -203,7 +215,7 @@ void ScriptManager::updateToUpdate(const ToUpdate& u, deque<ToUpdate>& to_update ...@@ -203,7 +215,7 @@ void ScriptManager::updateToUpdate(const ToUpdate& u, deque<ToUpdate>& to_update
} }
} }
void ScriptManager::alsoUpdate(deque<ToUpdate>& to_update, const vector<Dependency>& deps, const CardP& card) { void SetScriptManager::alsoUpdate(deque<ToUpdate>& to_update, const vector<Dependency>& deps, const CardP& card) {
FOR_EACH_CONST(d, deps) { FOR_EACH_CONST(d, deps) {
switch (d.type) { switch (d.type) {
case DEP_SET_FIELD: { case DEP_SET_FIELD: {
...@@ -217,16 +229,24 @@ void ScriptManager::alsoUpdate(deque<ToUpdate>& to_update, const vector<Dependen ...@@ -217,16 +229,24 @@ void ScriptManager::alsoUpdate(deque<ToUpdate>& to_update, const vector<Dependen
} }
break; break;
} case DEP_CARDS_FIELD: { } case DEP_CARDS_FIELD: {
// TODO // something invalidates a card value for all cards, so all cards need updating
FOR_EACH(card, set.cards) {
ValueP value = card->data.at(d.index);
to_update.push_back(ToUpdate(value.get(), card));
}
break; break;
} case DEP_STYLE: { } case DEP_STYLE: {
// TODO // TODO
break; break;
} case DEP_CARD_COPY_DEP: { } case DEP_CARD_COPY_DEP: {
// TODO // propagate dependencies from another field
FieldP f = set.game->card_fields[d.index];
alsoUpdate(to_update, f->dependent_scripts, card);
break; break;
} case DEP_SET_COPY_DEP: { } case DEP_SET_COPY_DEP: {
// TODO // propagate dependencies from another field
FieldP f = set.game->set_fields[d.index];
alsoUpdate(to_update, f->dependent_scripts, card);
break; break;
} default: } default:
assert(false); assert(false);
...@@ -269,4 +289,4 @@ void ScriptManager::alsoUpdate(deque<ToUpdate>& to_update, const vector<Dependen ...@@ -269,4 +289,4 @@ void ScriptManager::alsoUpdate(deque<ToUpdate>& to_update, const vector<Dependen
assert(false); // only setField, cardField and cardsField should be possible assert(false); // only setField, cardField and cardsField should be possible
}*/ }*/
} }
} }
\ No newline at end of file
...@@ -22,7 +22,29 @@ DECLARE_POINTER_TYPE(Game); ...@@ -22,7 +22,29 @@ DECLARE_POINTER_TYPE(Game);
DECLARE_POINTER_TYPE(StyleSheet); DECLARE_POINTER_TYPE(StyleSheet);
DECLARE_POINTER_TYPE(Card); DECLARE_POINTER_TYPE(Card);
// ----------------------------------------------------------------------------- : ScriptManager // ----------------------------------------------------------------------------- : SetScriptContext
/// Manager of the script context for a set
class SetScriptContext {
public:
SetScriptContext(Set& set);
~SetScriptContext();
/// Get a context to use for the set, for a given stylesheet
Context& getContext(const StyleSheetP&);
/// Get a context to use for the set, for a given card
Context& getContext(const CardP&);
protected:
Set& set; ///< Set for which we are managing scripts
map<const StyleSheet*,Context*> contexts; ///< Context for evaluating scripts that use a given stylesheet
/// Called when a new context for a stylesheet is initialized
virtual void onInit(const StyleSheetP& stylesheet, Context* ctx) {}
};
// ----------------------------------------------------------------------------- : SetScriptManager
/// Manager of the script context for a set, keeps scripts up to date /// Manager of the script context for a set, keeps scripts up to date
/** Whenever there is an action all necessary scripts are executed. /** Whenever there is an action all necessary scripts are executed.
...@@ -31,22 +53,16 @@ DECLARE_POINTER_TYPE(Card); ...@@ -31,22 +53,16 @@ DECLARE_POINTER_TYPE(Card);
* The context contains a normal pointer to the set, not a shared_ptr, because the set * The context contains a normal pointer to the set, not a shared_ptr, because the set
* itself owns this object. * itself owns this object.
*/ */
class ScriptManager : public ActionListener { class SetScriptManager : public SetScriptContext, public ActionListener {
public: public:
ScriptManager(Set& set); SetScriptManager(Set& set);
~ScriptManager(); ~SetScriptManager();
/// Get a context to use for the set, for a given stylesheet
Context& getContext(const StyleSheetP&);
/// Get a context to use for the set, for a given card
Context& getContext(const CardP&);
// Update all styles for a particular card // Update all styles for a particular card
void updateStyles(const CardP& card); void updateStyles(const CardP& card);
private: private:
Set& set; ///< Set for which we are managing scripts virtual void onInit(const StyleSheetP& stylesheet, Context* ctx);
map<const StyleSheet*,Context*> contexts; ///< Context for evaluating scripts that use a given stylesheet
void initDependencies(Context&, Game&); void initDependencies(Context&, Game&);
void initDependencies(Context&, StyleSheet&); void initDependencies(Context&, StyleSheet&);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment