mirror of
https://github.com/amyinspace/MagicSetEditor2.git
synced 2026-06-11 13:17:00 -04:00
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
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
#include <data/set.hpp>
|
||||
#include <data/card.hpp>
|
||||
#include <data/settings.hpp>
|
||||
#include <data/stylesheet.hpp>
|
||||
#include <data/format/clipboard.hpp>
|
||||
#include <data/action/set.hpp>
|
||||
#include <util/window_id.hpp>
|
||||
@@ -24,6 +25,8 @@ DECLARE_TYPEOF_COLLECTION(FieldP);
|
||||
DECLARE_POINTER_TYPE(ChoiceValue);
|
||||
typedef map<int,FieldP> map_int_FieldP;
|
||||
DECLARE_TYPEOF(map_int_FieldP);
|
||||
typedef IndexMap<FieldP,StyleP> 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<CardP>& 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<ChoiceStyleP>(s);
|
||||
FOR_EACH(s, set->stylesheet->card_style) {
|
||||
ChoiceStyleP cs = dynamic_pointer_cast<ChoiceStyle>(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<ChoiceValue>( sorted_card_list[pos]->data[color_style->fieldP]);
|
||||
ChoiceValueP val = static_pointer_cast<ChoiceValue>( 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
|
||||
|
||||
@@ -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<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)
|
||||
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:
|
||||
|
||||
@@ -20,8 +20,8 @@ const vector<CardP>& FilteredCardList::getCards() const {
|
||||
return cards;
|
||||
}
|
||||
|
||||
void FilteredCardList::setFilter(const CardListFilterP& filter_) {
|
||||
filter = filter_;
|
||||
void FilteredCardList::setFilter(const CardListFilterP& filter) {
|
||||
this->filter = filter;
|
||||
rebuild();
|
||||
}
|
||||
|
||||
|
||||
@@ -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<CardP>& 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
|
||||
|
||||
@@ -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 <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 ()
|
||||
@@ -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 <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
|
||||
@@ -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 <gui/control/select_card_list.hpp>
|
||||
|
||||
// ----------------------------------------------------------------------------- : SelectCardList
|
||||
|
||||
// TODO
|
||||
@@ -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 <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 @@
|
||||
// ----------------------------------------------------------------------------- : Includes
|
||||
|
||||
#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/text_ctrl.hpp>
|
||||
#include <gui/icon_menu.hpp>
|
||||
@@ -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
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#include <gui/set/panel.hpp>
|
||||
|
||||
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
|
||||
|
||||
@@ -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 <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();
|
||||
}
|
||||
}
|
||||
@@ -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 <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
|
||||
Reference in New Issue
Block a user