From 4c10107dbb695c1577a6946024ab90bd7ab48f19 Mon Sep 17 00:00:00 2001 From: twanvl Date: Mon, 18 Dec 2006 19:30:44 +0000 Subject: [PATCH] Thread for generating thumbnail images; Used for card list; Implemented reordering from card list git-svn-id: svn://svn.code.sf.net/p/magicseteditor/code/trunk@115 0fc631ac-6414-0410-93d0-97cfa31319b6 --- src/data/set.cpp | 6 +- src/data/set.hpp | 13 +- src/gui/control/card_list.cpp | 36 +++-- src/gui/control/card_list.hpp | 13 +- src/gui/control/filtered_card_list.cpp | 4 +- src/gui/control/filtered_card_list.hpp | 4 +- src/gui/control/image_card_list.cpp | 110 +++++++++++++ src/gui/control/image_card_list.hpp | 43 +++++ src/gui/control/select_card_list.cpp | 13 ++ src/gui/control/select_card_list.hpp | 42 +++++ src/gui/set/cards_panel.cpp | 5 +- src/gui/set/cards_panel.hpp | 4 +- src/gui/thumbnail_thread.cpp | 212 +++++++++++++++++++++++++ src/gui/thumbnail_thread.hpp | 81 ++++++++++ src/mse.vcproj | 18 +++ src/script/script_manager.cpp | 100 +++++++----- src/script/script_manager.hpp | 38 +++-- 17 files changed, 660 insertions(+), 82 deletions(-) create mode 100644 src/gui/control/image_card_list.cpp create mode 100644 src/gui/control/image_card_list.hpp create mode 100644 src/gui/control/select_card_list.cpp create mode 100644 src/gui/control/select_card_list.hpp create mode 100644 src/gui/thumbnail_thread.cpp create mode 100644 src/gui/thumbnail_thread.hpp diff --git a/src/data/set.cpp b/src/data/set.cpp index 94af071d..f85efda0 100644 --- a/src/data/set.cpp +++ b/src/data/set.cpp @@ -26,12 +26,12 @@ DECLARE_TYPEOF_NO_REV(IndexMap_FieldP_ValueP); // ----------------------------------------------------------------------------- : Set Set::Set() - : script_manager(new ScriptManager(*this)) + : script_manager(new SetScriptManager(*this)) {} Set::Set(const GameP& game) : game(game) - , script_manager(new ScriptManager(*this)) + , script_manager(new SetScriptManager(*this)) { data.init(game->set_fields); } @@ -39,7 +39,7 @@ Set::Set(const GameP& game) Set::Set(const StyleSheetP& stylesheet) : stylesheet(stylesheet) , game(stylesheet->game) - , script_manager(new ScriptManager(*this)) + , script_manager(new SetScriptManager(*this)) { data.init(game->set_fields); } diff --git a/src/data/set.hpp b/src/data/set.hpp index 2290a0cb..f162cbc5 100644 --- a/src/data/set.hpp +++ b/src/data/set.hpp @@ -24,7 +24,8 @@ DECLARE_POINTER_TYPE(Field); DECLARE_POINTER_TYPE(Value); DECLARE_POINTER_TYPE(Keyword); DECLARE_INTRUSIVE_POINTER_TYPE(ScriptValue); -class ScriptManager; +class SetScriptManager; +class SetScriptContext; class Context; class Dependency; template class OrderCache; @@ -64,6 +65,12 @@ class Set : public Packaged { Context& getContext(const CardP& card); /// Update styles for a 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 /** card may be null */ @@ -94,7 +101,9 @@ class Set : public Packaged { DECLARE_REFLECTION(); private: /// Object for managing and executing scripts - scoped_ptr script_manager; + scoped_ptr script_manager; + /// Object for executing scripts from the thumbnail thread + scoped_ptr thumbnail_script_context; /// Cache of cards ordered by some criterion map order_cache; }; diff --git a/src/gui/control/card_list.cpp b/src/gui/control/card_list.cpp index d21c118e..52b250f1 100644 --- a/src/gui/control/card_list.cpp +++ b/src/gui/control/card_list.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -24,6 +25,8 @@ DECLARE_TYPEOF_COLLECTION(FieldP); DECLARE_POINTER_TYPE(ChoiceValue); typedef map map_int_FieldP; DECLARE_TYPEOF(map_int_FieldP); +typedef IndexMap IndexMap_FieldP_StyleP; +DECLARE_TYPEOF_NO_REV(IndexMap_FieldP_StyleP); // ----------------------------------------------------------------------------- : Events @@ -85,7 +88,7 @@ void CardListBase::onAction(const Action& action, bool undone) { if (sort_criterium) return; // nothing changes for us 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 - 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 selected_card_pos = (long)action.card_id1 == selected_card_pos ? (long)action.card_id2 : (long)action.card_id1; // select the right card @@ -99,6 +102,9 @@ void CardListBase::onAction(const Action& action, bool undone) { const vector& CardListBase::getCards() const { return set->cards; } +const CardP& CardListBase::getCard(long pos) const { + return sorted_card_list[pos]; +} // ----------------------------------------------------------------------------- : CardListBase : Selection @@ -135,7 +141,7 @@ void CardListBase::selectCardPos(long pos, bool focus) { if (selected_card_pos == pos && !focus) return; // this card is already selected if ((size_t)pos < sorted_card_list.size()) { // only if there is something to select - selectCard(sorted_card_list[pos], false, true); + selectCard(getCard(pos), false, true); } else { selectCard(CardP(), false, true); } @@ -148,7 +154,7 @@ void CardListBase::findSelectedCardPos() { long count = GetItemCount(); selected_card_pos = -1; for (long pos = 0 ; pos < count ; ++pos) { - if (sorted_card_list[pos] == selected_card) { + if (getCard(pos) == selected_card) { selected_card_pos = pos; break; } @@ -298,13 +304,12 @@ void CardListBase::refreshList() { } ChoiceStyleP CardListBase::findColorStyle() { -/* FOR_EACH(s, set->default_stylesheet->card_style) { - ChoiceStyleP cs = dynamic_cast(s); + FOR_EACH(s, set->stylesheet->card_style) { + ChoiceStyleP cs = dynamic_pointer_cast(s); if (cs && cs->colors_card_list) { return cs; } } -*/ return ChoiceStyleP(); } @@ -338,7 +343,7 @@ String CardListBase::OnGetItemText(long pos, long col) const { // wx may give us non existing columns! 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(); else return wxEmptyString; } @@ -349,7 +354,7 @@ int CardListBase::OnGetItemImage(long pos) const { wxListItemAttr* CardListBase::OnGetItemAttr(long pos) const { if (!color_style) return nullptr; - ChoiceValueP val = static_pointer_cast( sorted_card_list[pos]->data[color_style->fieldP]); + ChoiceValueP val = static_pointer_cast( getCard(pos)->data[color_style->fieldP]); assert(val); item_attr.SetTextColour(color_style->choice_colors[val->value()]); // if it doesn't exist we get black return &item_attr; @@ -412,7 +417,20 @@ void CardListBase::onChar(wxKeyEvent& 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 diff --git a/src/gui/control/card_list.hpp b/src/gui/control/card_list.hpp index 7664000a..c95ff633 100644 --- a/src/gui/control/card_list.hpp +++ b/src/gui/control/card_list.hpp @@ -41,9 +41,6 @@ struct CardSelectEvent : public wxCommandEvent { * * 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). - * - * This class is an abstract base class for card lists, derived classes must overload: - * - getCard(index) */ class CardListBase : public wxListView, public SetView { public: @@ -84,24 +81,26 @@ class CardListBase : public wxListView, public SetView { protected: /// What cards should be shown? virtual const vector& 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) void rebuild(); /// Do some additional updating before rebuilding the list virtual void onRebuild() {} /// Can the card list be modified? - virtual bool allowModify() const { return true; } + virtual bool allowModify() const { return false; } // --------------------------------------------------- : Item 'events' /// Get the text of an item in a specific column /** 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 /** Overrides a function from wxListCtrl */ - int OnGetItemImage(long pos) const; + virtual int OnGetItemImage(long pos) const; /// Get the color for an item - wxListItemAttr* OnGetItemAttr(long pos) const; + virtual wxListItemAttr* OnGetItemAttr(long pos) const; // --------------------------------------------------- : Data private: diff --git a/src/gui/control/filtered_card_list.cpp b/src/gui/control/filtered_card_list.cpp index 477271e7..25955f5c 100644 --- a/src/gui/control/filtered_card_list.cpp +++ b/src/gui/control/filtered_card_list.cpp @@ -20,8 +20,8 @@ const vector& FilteredCardList::getCards() const { return cards; } -void FilteredCardList::setFilter(const CardListFilterP& filter_) { - filter = filter_; +void FilteredCardList::setFilter(const CardListFilterP& filter) { + this->filter = filter; rebuild(); } diff --git a/src/gui/control/filtered_card_list.hpp b/src/gui/control/filtered_card_list.hpp index 18de39c2..c20740fa 100644 --- a/src/gui/control/filtered_card_list.hpp +++ b/src/gui/control/filtered_card_list.hpp @@ -32,15 +32,13 @@ class FilteredCardList : public CardListBase { FilteredCardList(Window* parent, int id, long style = 0); /// Change the filter to use - void setFilter(const CardListFilterP& filter_); + void setFilter(const CardListFilterP& filter); protected: /// Get only the subset of the cards virtual const vector& getCards() const; /// Rebuild the filtered card list virtual void onRebuild(); -// /// Don't reorder -// virtual void onDrag(wxMouseEvent& ev); private: CardListFilterP filter; ///< Filter with which this.cards is made diff --git a/src/gui/control/image_card_list.cpp b/src/gui/control/image_card_list.cpp new file mode 100644 index 00000000..d8c85f8f --- /dev/null +++ b/src/gui/control/image_card_list.cpp @@ -0,0 +1,110 @@ +//+----------------------------------------------------------------------------+ +//| 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 +#include +#include +#include +#include +#include + +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(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(*getCard(pos)->data[image_field]); + if (!val.filename) return -1; // no image + // is there already a thumbnail? + map::const_iterator it = thumbnails.find(val.filename); + if (it != thumbnails.end()) { + return it->second; + } else { + // request a thumbnail + thumbnail_thread.request(new_shared2(const_cast(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 () diff --git a/src/gui/control/image_card_list.hpp b/src/gui/control/image_card_list.hpp new file mode 100644 index 00000000..0cd5004a --- /dev/null +++ b/src/gui/control/image_card_list.hpp @@ -0,0 +1,43 @@ +//+----------------------------------------------------------------------------+ +//| 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 +#include + +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 thumbnails; ///< image thumbnails, based on image_field + + ImageFieldP findImageField(); + + friend class CardThumbnailRequest; +}; + +// ----------------------------------------------------------------------------- : EOF +#endif diff --git a/src/gui/control/select_card_list.cpp b/src/gui/control/select_card_list.cpp new file mode 100644 index 00000000..3faa12d3 --- /dev/null +++ b/src/gui/control/select_card_list.cpp @@ -0,0 +1,13 @@ +//+----------------------------------------------------------------------------+ +//| 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 + +// ----------------------------------------------------------------------------- : SelectCardList + +// TODO diff --git a/src/gui/control/select_card_list.hpp b/src/gui/control/select_card_list.hpp new file mode 100644 index 00000000..7bed954a --- /dev/null +++ b/src/gui/control/select_card_list.hpp @@ -0,0 +1,42 @@ +//+----------------------------------------------------------------------------+ +//| 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 +#include + +// ----------------------------------------------------------------------------- : 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 selected; ///< which cards are selected? + + void toggle(const CardP& card); + + void onKeyDown(wxKeyEvent&); + void onLeftDown(wxMouseEvent&); +}; + + +// ----------------------------------------------------------------------------- : EOF +#endif diff --git a/src/gui/set/cards_panel.cpp b/src/gui/set/cards_panel.cpp index 1c309057..c615b902 100644 --- a/src/gui/set/cards_panel.cpp +++ b/src/gui/set/cards_panel.cpp @@ -7,7 +7,7 @@ // ----------------------------------------------------------------------------- : Includes #include -#include +#include #include #include #include @@ -29,8 +29,7 @@ CardsPanel::CardsPanel(Window* parent, int id) wxSplitterWindow* splitter; editor = new CardEditor(this, ID_EDITOR); splitter = new wxSplitterWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0); -// card_list = new EditCardList(splitter, ID_CARD_LIST); - card_list = new CardListBase(splitter, ID_CARD_LIST); + card_list = new ImageCardList(splitter, ID_CARD_LIST); notesP = new Panel(splitter, wxID_ANY); notes = new TextCtrl(notesP, ID_NOTES); // init sizer for notes panel diff --git a/src/gui/set/cards_panel.hpp b/src/gui/set/cards_panel.hpp index 54c9d70c..e98fdb47 100644 --- a/src/gui/set/cards_panel.hpp +++ b/src/gui/set/cards_panel.hpp @@ -13,7 +13,7 @@ #include class wxSplitterWindow; -class CardListBase; +class ImageCardList; class DataEditor; class TextCtrl; @@ -93,7 +93,7 @@ class CardsPanel : public SetWindowPanel { // --------------------------------------------------- : Controls wxSplitterWindow* splitter; DataEditor* editor; - CardListBase* card_list; + ImageCardList* card_list; TextCtrl* notes; // --------------------------------------------------- : Menus & tools diff --git a/src/gui/thumbnail_thread.cpp b/src/gui/thumbnail_thread.cpp new file mode 100644 index 00000000..106dd12b --- /dev/null +++ b/src/gui/thumbnail_thread.cpp @@ -0,0 +1,212 @@ +//+----------------------------------------------------------------------------+ +//| 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 +#include + +typedef pair 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 > 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(); + } +} diff --git a/src/gui/thumbnail_thread.hpp b/src/gui/thumbnail_thread.hpp new file mode 100644 index 00000000..64777bb0 --- /dev/null +++ b/src/gui/thumbnail_thread.hpp @@ -0,0 +1,81 @@ +//+----------------------------------------------------------------------------+ +//| 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 +#include +#include +#include + +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 open_requests; ///< Requests on which work hasn't finished + vector > closed_requests; ///< Requests for which work is completed + set 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 diff --git a/src/mse.vcproj b/src/mse.vcproj index 57da4a6f..bca9da3a 100644 --- a/src/mse.vcproj +++ b/src/mse.vcproj @@ -573,6 +573,12 @@ + + + + @@ -585,6 +591,12 @@ + + + + @@ -851,6 +863,12 @@ + + + + diff --git a/src/script/script_manager.cpp b/src/script/script_manager.cpp index 40f65c9f..c1817fed 100644 --- a/src/script/script_manager.cpp +++ b/src/script/script_manager.cpp @@ -30,26 +30,20 @@ DECLARE_TYPEOF_NO_REV(IndexMap_FieldP_ValueP); void init_script_functions(Context& ctx); void init_script_image_functions(Context& ctx); -// ----------------------------------------------------------------------------- : ScriptManager : initialization +// ----------------------------------------------------------------------------- : SetScriptContext : initialization -ScriptManager::ScriptManager(Set& set) +SetScriptContext::SetScriptContext(Set& set) : set(set) -{ - // add as an action listener for the set, so we receive actions - set.actions.addListener(this); -} +{} -ScriptManager::~ScriptManager() { - set.actions.removeListener(this); - // destroy context +SetScriptContext::~SetScriptContext() { + // destroy contexts FOR_EACH(sc, contexts) { delete sc.second; } } -Context& ScriptManager::getContext(const StyleSheetP& stylesheet) { - assert(wxThread::IsMain()); // only use our contexts from the main thread - +Context& SetScriptContext::getContext(const StyleSheetP& stylesheet) { Contexts::iterator it = contexts.find(stylesheet.get()); if (it != contexts.end()) { return *it->second; // we already have a context @@ -67,23 +61,11 @@ Context& ScriptManager::getContext(const StyleSheetP& stylesheet) { ctx->setVariable(_("stylesheet"), toScript(stylesheet)); ctx->setVariable(_("card"), set.cards.empty() ? script_nil : toScript(set.cards.front())); // dummy value ctx->setVariable(_("styling"), toScript(&set.stylingDataFor(*stylesheet))); - 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); - } - // initialize dependencies + onInit(stylesheet, ctx); return *ctx; } } -Context& ScriptManager::getContext(const CardP& card) { +Context& SetScriptContext::getContext(const CardP& card) { Context& ctx = getContext(set.stylesheetFor(card)); if (card) { ctx.setVariable(_("card"), toScript(card)); @@ -93,7 +75,37 @@ Context& ScriptManager::getContext(const CardP& card) { 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; game.dependencies_initialized = true; // find dependencies of card fields @@ -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; stylesheet.dependencies_initialized = true; // find dependencies of choice images and other style stuff @@ -118,7 +130,7 @@ void ScriptManager::initDependencies(Context& ctx, StyleSheet& stylesheet) { // ----------------------------------------------------------------------------- : ScriptManager : updating -void ScriptManager::onAction(const Action& action, bool undone) { +void SetScriptManager::onAction(const Action& action, bool undone) { TYPE_CASE(action, ValueAction) { // find the affected card FOR_EACH(card, set.cards) { @@ -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; StyleSheetP stylesheet = set.stylesheetFor(card); Context& ctx = getContext(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 deque to_update; // execute script for initial changed value @@ -158,7 +170,7 @@ void ScriptManager::updateValue(Value& value, const CardP& card) { updateRecursive(to_update, starting_age); } -void ScriptManager::updateAll() { +void SetScriptManager::updateAll() { // update set data Context& ctx = getContext(set.stylesheet); FOR_EACH(v, set.data) { @@ -175,14 +187,14 @@ void ScriptManager::updateAll() { updateAllDependend(set.game->dependent_scripts_cards); } -void ScriptManager::updateAllDependend(const vector& dependent_scripts) { +void SetScriptManager::updateAllDependend(const vector& dependent_scripts) { deque to_update; Age starting_age; alsoUpdate(to_update, dependent_scripts, CardP()); updateRecursive(to_update, starting_age); } -void ScriptManager::updateRecursive(deque& to_update, Age starting_age) { +void SetScriptManager::updateRecursive(deque& to_update, Age starting_age) { // set->order_cache.clear(); // clear caches before evaluating a round of scripts while (!to_update.empty()) { updateToUpdate(to_update.front(), to_update, starting_age); @@ -190,7 +202,7 @@ void ScriptManager::updateRecursive(deque& to_update, Age starting_age } } -void ScriptManager::updateToUpdate(const ToUpdate& u, deque& to_update, Age starting_age) { +void SetScriptManager::updateToUpdate(const ToUpdate& u, deque& to_update, Age starting_age) { Age age = u.value->last_script_update; if (starting_age < age) return; // this value was already updated Context& ctx = getContext(u.card); @@ -203,7 +215,7 @@ void ScriptManager::updateToUpdate(const ToUpdate& u, deque& to_update } } -void ScriptManager::alsoUpdate(deque& to_update, const vector& deps, const CardP& card) { +void SetScriptManager::alsoUpdate(deque& to_update, const vector& deps, const CardP& card) { FOR_EACH_CONST(d, deps) { switch (d.type) { case DEP_SET_FIELD: { @@ -217,16 +229,24 @@ void ScriptManager::alsoUpdate(deque& to_update, const vectordata.at(d.index); + to_update.push_back(ToUpdate(value.get(), card)); + } break; } case DEP_STYLE: { // TODO break; } 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; } 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; } default: assert(false); @@ -269,4 +289,4 @@ void ScriptManager::alsoUpdate(deque& to_update, const vector 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 /** Whenever there is an action all necessary scripts are executed. @@ -31,22 +53,16 @@ DECLARE_POINTER_TYPE(Card); * The context contains a normal pointer to the set, not a shared_ptr, because the set * itself owns this object. */ -class ScriptManager : public ActionListener { +class SetScriptManager : public SetScriptContext, public ActionListener { public: - ScriptManager(Set& set); - ~ScriptManager(); - - /// 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&); + SetScriptManager(Set& set); + ~SetScriptManager(); // Update all styles for a particular card void updateStyles(const CardP& card); private: - Set& set; ///< Set for which we are managing scripts - map contexts; ///< Context for evaluating scripts that use a given stylesheet + virtual void onInit(const StyleSheetP& stylesheet, Context* ctx); void initDependencies(Context&, Game&); void initDependencies(Context&, StyleSheet&);