diff --git a/src/data/field.hpp b/src/data/field.hpp index 9242e28b..01e815db 100644 --- a/src/data/field.hpp +++ b/src/data/field.hpp @@ -135,7 +135,7 @@ class Style : public IntrusivePtrVirtualBase { /** In particular, if dep == DEP_DUMMY and name is a content property, set dep.index=true */ virtual void markDependencyMember(const String& name, const Dependency&) const; /// Invalidate scripted images for this style - virtual void invalidate(Context&) {} + virtual void invalidate() {} /// Add a StyleListener void addListener(StyleListener*); diff --git a/src/data/field/choice.cpp b/src/data/field/choice.cpp index a36cd354..a2f9051c 100644 --- a/src/data/field/choice.cpp +++ b/src/data/field/choice.cpp @@ -239,21 +239,15 @@ void ChoiceStyle::initDependencies(Context& ctx, const Dependency& dep) const { ci.second.initDependencies(ctx, dep); } } -void ChoiceStyle::invalidate(Context& ctx) { +void ChoiceStyle::invalidate() { // TODO : this is also done in update(), once should be enough // Update choice images and thumbnails - bool change = false; int end = field().choices->lastId(); thumbnails_status.resize(end, THUMB_NOT_MADE); for (int i = 0 ; i < end ; ++i) { - String name = cannocial_name_form(field().choices->choiceName(i)); - ScriptableImage& img = choice_images[name]; - if (img.update(ctx)) { - change = true; - thumbnails_status[i] = THUMB_CHANGED; - } + if (thumbnails_status[i] == THUMB_OK) thumbnails_status[i] = THUMB_CHANGED; } - if (change) tellListeners(CHANGE_OTHER); + tellListeners(CHANGE_OTHER); } void ChoiceStyle::loadMask(Package& pkg) { diff --git a/src/data/field/choice.hpp b/src/data/field/choice.hpp index 763b858e..bc33e8a6 100644 --- a/src/data/field/choice.hpp +++ b/src/data/field/choice.hpp @@ -130,9 +130,9 @@ enum ChoiceRenderStyle }; enum ThumbnailStatus -{ THUMB_NOT_MADE -, THUMB_OK -, THUMB_CHANGED +{ THUMB_NOT_MADE // there is no image +, THUMB_OK // image is ok +, THUMB_CHANGED // there is an image, but it may need to be updated }; /// The Style for a ChoiceField @@ -165,7 +165,7 @@ class ChoiceStyle : public Style { virtual int update(Context&); virtual void initDependencies(Context&, const Dependency&) const; - virtual void invalidate(Context&); + virtual void invalidate(); private: DECLARE_REFLECTION(); diff --git a/src/gfx/generated_image.cpp b/src/gfx/generated_image.cpp index 0010a879..b1923c40 100644 --- a/src/gfx/generated_image.cpp +++ b/src/gfx/generated_image.cpp @@ -28,7 +28,14 @@ Image conform_image(const Image& img, const GeneratedImage::Options& options) { // resize? int iw = image.GetWidth(), ih = image.GetHeight(); if ((iw == options.width && ih == options.height) || (options.width == 0 && options.height == 0)) { - // already the right size + // zoom? + if (options.zoom != 1.0) { + Image resampled_image(iw * options.zoom, ih * options.zoom, false); + resample(image, resampled_image); + image = resampled_image; + } else { + // already the right size + } } else if (options.height == 0) { // width is given, determine height int h = options.width * ih / iw; diff --git a/src/gfx/generated_image.hpp b/src/gfx/generated_image.hpp index b8e9d8ad..38435d41 100644 --- a/src/gfx/generated_image.hpp +++ b/src/gfx/generated_image.hpp @@ -28,12 +28,13 @@ class GeneratedImage : public ScriptValue { /// Options for generating the image struct Options { Options(int width = 0, int height = 0, Package* package = nullptr, Package* local_package = nullptr, PreserveAspect preserve_aspect = ASPECT_STRETCH, bool saturate = false) - : width(width), height(height), angle(0) + : width(width), height(height), zoom(1.0), angle(0) , preserve_aspect(preserve_aspect), saturate(saturate) , package(package), local_package(local_package) {} int width, height; ///< Width to force the image to, or 0 to keep the width of the input + double zoom; ///< Zoom factor to use, when witdth=height=0 int angle; ///< Angle to rotate image by afterwards PreserveAspect preserve_aspect; bool saturate; @@ -52,7 +53,10 @@ class GeneratedImage : public ScriptValue { inline bool operator != (const GeneratedImage& that) const { return !(*this == that); } /// Can this image be generated safely from another thread? - virtual bool threadSafe() const { return true; }; + virtual bool threadSafe() const { return true; } + + /// Is this image specific to the set (the local_package)? + virtual bool local() const { return false; } virtual ScriptType type() const; virtual String typeName() const; @@ -86,6 +90,7 @@ class LinearBlendImage : public GeneratedImage { virtual Image generate(const Options& opt) const; virtual ImageCombine combine() const; virtual bool operator == (const GeneratedImage& that) const; + virtual bool local() const { return image1->local() && image2->local(); } private: GeneratedImageP image1, image2; double x1, y1, x2, y2; @@ -102,6 +107,7 @@ class MaskedBlendImage : public GeneratedImage { virtual Image generate(const Options& opt) const; virtual ImageCombine combine() const; virtual bool operator == (const GeneratedImage& that) const; + virtual bool local() const { return light->local() && dark->local() && mask->local(); } private: GeneratedImageP light, dark, mask; }; @@ -117,6 +123,7 @@ class CombineBlendImage : public GeneratedImage { virtual Image generate(const Options& opt) const; virtual ImageCombine combine() const; virtual bool operator == (const GeneratedImage& that) const; + virtual bool local() const { return image1->local() && image2->local(); } private: GeneratedImageP image1, image2; ImageCombine image_combine; @@ -133,6 +140,7 @@ class SetMaskImage : public GeneratedImage { virtual Image generate(const Options& opt) const; virtual ImageCombine combine() const; virtual bool operator == (const GeneratedImage& that) const; + virtual bool local() const { return image->local() && mask->local(); } private: GeneratedImageP image, mask; }; @@ -146,6 +154,7 @@ class SetAlphaImage : public GeneratedImage { virtual Image generate(const Options& opt) const; virtual ImageCombine combine() const; virtual bool operator == (const GeneratedImage& that) const; + virtual bool local() const { return image->local(); } private: GeneratedImageP image; double alpha; @@ -162,6 +171,7 @@ class SetCombineImage : public GeneratedImage { virtual Image generate(const Options& opt) const; virtual ImageCombine combine() const; virtual bool operator == (const GeneratedImage& that) const; + virtual bool local() const { return image->local(); } private: GeneratedImageP image; ImageCombine image_combine; @@ -178,6 +188,7 @@ class EnlargeImage : public GeneratedImage { virtual Image generate(const Options& opt) const; virtual ImageCombine combine() const; virtual bool operator == (const GeneratedImage& that) const; + virtual bool local() const { return image->local(); } private: GeneratedImageP image; double border_size; @@ -194,6 +205,7 @@ class CropImage : public GeneratedImage { virtual Image generate(const Options& opt) const; virtual ImageCombine combine() const; virtual bool operator == (const GeneratedImage& that) const; + virtual bool local() const { return image->local(); } private: GeneratedImageP image; double width, height; @@ -212,6 +224,7 @@ class DropShadowImage : public GeneratedImage { virtual Image generate(const Options& opt) const; virtual ImageCombine combine() const; virtual bool operator == (const GeneratedImage& that) const; + virtual bool local() const { return image->local(); } private: GeneratedImageP image; double offset_x, offset_y; @@ -257,6 +270,7 @@ class SymbolToImage : public GeneratedImage { ~SymbolToImage(); virtual Image generate(const Options& opt) const; virtual bool operator == (const GeneratedImage& that) const; + virtual bool local() const { return true; } #ifdef __WXGTK__ virtual bool threadSafe() const { return false; } @@ -277,6 +291,7 @@ class ImageValueToImage : public GeneratedImage { ~ImageValueToImage(); virtual Image generate(const Options& opt) const; virtual bool operator == (const GeneratedImage& that) const; + virtual bool local() const { return true; } private: ImageValueToImage(const ImageValueToImage&); // copy ctor String filename; diff --git a/src/gui/control/card_editor.cpp b/src/gui/control/card_editor.cpp index e0d83595..8fca0501 100644 --- a/src/gui/control/card_editor.cpp +++ b/src/gui/control/card_editor.cpp @@ -206,7 +206,8 @@ void DataEditor::onRightDown(wxMouseEvent& ev) { selectField(ev, &ValueEditor::onRightDown); } void DataEditor::onMouseWheel(wxMouseEvent& ev) { - if (current_editor) current_editor->onMouseWheel(mousePoint(ev), ev); + if (current_editor && current_editor->onMouseWheel(mousePoint(ev), ev)); + else ev.Skip(); } void DataEditor::onMotion(wxMouseEvent& ev) { diff --git a/src/gui/set/cards_panel.cpp b/src/gui/set/cards_panel.cpp index 03ca4420..6de4cd5e 100644 --- a/src/gui/set/cards_panel.cpp +++ b/src/gui/set/cards_panel.cpp @@ -160,6 +160,7 @@ void CardsPanel::onUpdateUI(wxUpdateUIEvent& ev) { ev.Check(ss.card_angle() == a); break; } + case ID_CARD_ADD_MULT: ev.Enable(false); break; // not implemented case ID_CARD_REMOVE: ev.Enable(set->cards.size() > 1); break; case ID_FORMAT_BOLD: case ID_FORMAT_ITALIC: case ID_FORMAT_SYMBOL: case ID_FORMAT_REMINDER: { if (focused_control(this) == ID_EDITOR) { diff --git a/src/gui/set/window.cpp b/src/gui/set/window.cpp index f22463a7..919827db 100644 --- a/src/gui/set/window.cpp +++ b/src/gui/set/window.cpp @@ -397,6 +397,8 @@ void SetWindow::onUpdateUI(wxUpdateUIEvent& ev) { case ID_EDIT_REPLACE : ev.Enable(current_panel->canReplace());break; // windows case ID_WINDOW_KEYWORDS: ev.Enable(set->game->has_keywords); break; + // help + case ID_HELP_INDEX : ev.Enable(false); break; // not implemented // other default: // items created by the panel, and cut/copy/paste and find/replace diff --git a/src/gui/value/choice.cpp b/src/gui/value/choice.cpp index fa57afec..d67925a5 100644 --- a/src/gui/value/choice.cpp +++ b/src/gui/value/choice.cpp @@ -20,7 +20,7 @@ DECLARE_TYPEOF_COLLECTION(ChoiceField::ChoiceP); class ChoiceThumbnailRequest : public ThumbnailRequest { public: - ChoiceThumbnailRequest(ValueViewer* cve, int id, bool from_disk); + ChoiceThumbnailRequest(ValueViewer* cve, int id, bool from_disk, bool thread_safe); virtual Image generate(); virtual void store(const Image&); @@ -34,22 +34,17 @@ class ChoiceThumbnailRequest : public ThumbnailRequest { inline ValueViewer& viewer() { return *static_cast(owner); } }; -ChoiceThumbnailRequest::ChoiceThumbnailRequest(ValueViewer* viewer, int id, bool from_disk) +ChoiceThumbnailRequest::ChoiceThumbnailRequest(ValueViewer* viewer, int id, bool from_disk, bool thread_safe) : ThumbnailRequest( static_cast(viewer), viewer->viewer.stylesheet->name() + _("/") + viewer->getField()->name + _("/") << id, from_disk ? viewer->viewer.stylesheet->lastModified() : wxDateTime::Now() ) + , isThreadSafe(thread_safe) , stylesheet(viewer->viewer.stylesheet) , id(id) -{ - assert(dynamic_pointer_cast(viewer->getStyle())); // only works on choice styles - ChoiceStyle& s = style(); - String name = cannocial_name_form(s.field().choices->choiceName(id)); - ScriptableImage img = s.choice_images[name]; - isThreadSafe = img.threadSafe(); -} +{} Image ChoiceThumbnailRequest::generate() { ChoiceStyle& s = style(); @@ -184,8 +179,8 @@ void DropDownChoiceListBase::generateThumbnailImages() { int image_count = style().thumbnails->GetImageCount(); int end = group->lastId(); // init choice images + Context& ctx = cve.viewer.getContext(); if (style().choice_images.empty() && style().image.isScripted()) { - Context& ctx = cve.viewer.getContext(); for (int i = 0 ; i < end ; ++i) { try { String name = cannocial_name_form(field().choices->choiceName(i)); @@ -200,10 +195,20 @@ void DropDownChoiceListBase::generateThumbnailImages() { // request thumbnails style().thumbnails_status.resize(end, THUMB_NOT_MADE); for (int i = 0 ; i < end ; ++i) { - ThumbnailStatus status = style().thumbnails_status[i]; + ThumbnailStatus& status = style().thumbnails_status[i]; if (i >= image_count || status != THUMB_OK) { - // request this thumbnail - thumbnail_thread.request( new_intrusive3(&cve, i, status == THUMB_NOT_MADE) ); + // update image + ChoiceStyle& s = style(); + String name = cannocial_name_form(s.field().choices->choiceName(i)); + ScriptableImage& img = s.choice_images[name]; + if (!img.update(ctx) && status == THUMB_CHANGED) { + status = THUMB_OK; // no need to rebuild + } else { + // request this thumbnail + thumbnail_thread.request( new_intrusive4( + &cve, i, status == THUMB_NOT_MADE && !img.local(), img.threadSafe() + )); + } } } } diff --git a/src/render/text/element.cpp b/src/render/text/element.cpp index 5a6ff19f..131b22b0 100644 --- a/src/render/text/element.cpp +++ b/src/render/text/element.cpp @@ -98,9 +98,13 @@ struct TextElementsFromString { else if (is_substr(text, tag_start, _("::iterator it = style().choice_images.find(cannocial_name_form(choice)); if (it != style().choice_images.end() && it->second.isReady()) { - // TODO: scaling, caching - Image image = it->second.generate(GeneratedImage::Options(0,0, viewer.stylesheet.get(),&getSet())); + // TODO: caching + GeneratedImage::Options options(0,0, viewer.stylesheet.get(),&getSet()); + options.zoom = dc.getZoom(); + options.angle = dc.trAngle(style().angle); + Image image = it->second.generate(options); ImageCombine combine = it->second.combine(); // TODO : alignment? - dc.DrawImage(image, pos + RealSize(size.width, 0), combine == COMBINE_DEFAULT ? style().combine : combine); - size = add_horizontal(size, dc.trInv(RealSize(image.GetWidth() + 1, image.GetHeight()))); + dc.DrawPreRotatedImage(image, pos + RealSize(size.width, 0), combine == COMBINE_DEFAULT ? style().combine : combine); + size = add_horizontal(size, dc.trInvNoNeg(RealSize(image.GetWidth() + 1, image.GetHeight()))); } } if (style().render_style & RENDER_TEXT) { diff --git a/src/script/image.hpp b/src/script/image.hpp index 92ecb6ba..ee587c23 100644 --- a/src/script/image.hpp +++ b/src/script/image.hpp @@ -46,10 +46,13 @@ class ScriptableImage { inline void initDependencies(Context& ctx, const Dependency& dep) const { script.initDependencies(ctx, dep); } - + /// Can this be safely generated from another thread? inline bool threadSafe() const { return !value || value->threadSafe(); } + /// Is this image specific to the set (the local_package)? + inline bool local() const { return value && value->local(); } + /// Get access to the script, be careful inline Script& getScript() { return script.getScript(); } /// Get access to the script, always returns a valid script diff --git a/src/script/script_manager.cpp b/src/script/script_manager.cpp index dd81fcc4..0e2d498a 100644 --- a/src/script/script_manager.cpp +++ b/src/script/script_manager.cpp @@ -357,7 +357,7 @@ void SetScriptManager::alsoUpdate(deque& to_update, const vector(d.data); StyleP style = stylesheet->card_style.at(d.index); - style->invalidate(getContext(card)); + style->invalidate(); // something changed, send event ScriptStyleEvent change(stylesheet, style.get()); set.actions.tellListeners(change, false); diff --git a/src/util/rotation.hpp b/src/util/rotation.hpp index 149dd811..cbbbb4e9 100644 --- a/src/util/rotation.hpp +++ b/src/util/rotation.hpp @@ -32,6 +32,8 @@ class Rotation { /// Change the zoom factor inline void setZoom(double z) { zoomX = zoomY = z; } + /// Retrieve the zoom factor + inline double getZoom() const { return zoomY; } /// Change the angle void setAngle(int a); /// Change the origin