diff --git a/share/extensions b/share/extensions index 6c9b68507be427bffba23507bbaacf3f8a0f3752..d99349e15e2342dd9af99afb345a98df02ab1d1b 160000 --- a/share/extensions +++ b/share/extensions @@ -1 +1 @@ -Subproject commit 6c9b68507be427bffba23507bbaacf3f8a0f3752 +Subproject commit d99349e15e2342dd9af99afb345a98df02ab1d1b diff --git a/share/ui/dialog-export.glade b/share/ui/dialog-export.glade new file mode 100644 index 0000000000000000000000000000000000000000..ff23a4709eb20fcc93ada8f22776eb6680eea7db --- /dev/null +++ b/share/ui/dialog-export.glade @@ -0,0 +1,1100 @@ + + + + + + ExportDialog + True + False + False + vertical + True + + + True + False + False + False + + + True + False + False + vertical + 5 + + + True + True + False + True + never + external + False + + + True + False + False + natural + none + + + + True + False + 5 + 5 + 10 + + + True + True + True + 0 + 0 + False + + + 2 + 1 + + + + + True + True + True + 0 + 0 + + + 4 + 3 + + + + + True + True + True + 0 + 0 + + + 2 + 5 + + + + + True + True + True + 0 + 0 + + + 4 + 5 + + + + + True + False + 5 + 5 + Image Size + 0 + + + + + + 1 + 8 + 5 + + + + + True + False + + + + + + True + False + end + True + 5 + + + True + False + Units + True + 0 + + + False + True + 0 + + + + + True + False + 1 + + + False + True + 1 + + + + + False + True + 2 + + + + + 1 + 7 + 4 + + + + + True + False + 20 + + + True + False + 5 + 5 + 5 + 5 + vertical + + + + + + True + True + 0 + + + + + True + False + center + vertical + + + Show Preview + False + False + True + True + True + + + False + True + 0 + + + + + Export Selected only + True + False + False + True + + + False + True + 1 + + + + + True + True + end + 1 + + + + + 1 + 13 + 4 + + + + + True + True + True + 0 + 0 + + + 4 + 1 + + + + + True + True + True + 0 + 0 + + + 2 + 3 + + + + + True + False + Height + True + 0 + + + 3 + 5 + + + + + True + False + Width + True + 0 + + + 1 + 5 + + + + + True + False + True + vertical + + + + + + 0 + 14 + 6 + + + + + True + False + Left + True + 0 + + + 1 + 1 + + + + + True + False + Right + True + 0 + + + 1 + 3 + + + + + True + False + Bottom + True + 0 + + + 3 + 3 + + + + + True + False + Top + True + 0 + + + 3 + 1 + + + + + True + False + Width +(px) + True + 0 + + + 1 + 10 + + + + + True + False + Height +(px) + True + 0 + + + 3 + 10 + + + + + True + True + center + True + 0 + 0 + number + + + 2 + 10 + + + + + True + True + center + True + 0 + 0 + + + 4 + 10 + + + + + True + False + DPI + True + 0 + + + 1 + 12 + + + + + True + True + start + True + 0 + 0 + + + 2 + 12 + + + + + + True + False + 2 + 20 + + + + + + + + + + + + + + + + + + 1 + 9 + 4 + + + + + + True + False + + + Selection + export_selection + True + False + False + False + Export selected Objects + True + False + si_s_document + + + 2 + 0 + + + + + Document + export_selection + True + False + False + False + Export everything inside Document + True + True + False + + + 0 + 0 + + + + + Page + export_selection + True + False + False + False + Export cropped content inside the page + True + False + si_s_document + + + 1 + 0 + + + + + Custom + export_selection + True + False + False + False + Export Custom Area by specifying coordinates + True + False + si_s_document + + + 3 + 0 + + + + + 0 + 0 + 6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + 0 + + + + + + True + False + 5 + 10 + + + True + True + True + folder-open + Browse Export Directory + Filename + + + 0 + 0 + + + + + True + False + Select export format + center + 0 + + + 1 + 0 + + + + + True + False + + + 0 + 2 + 2 + + + + + Export + True + False + False + + + 0 + 1 + 2 + + + + + False + True + 1 + + + + + 1 + + + + + True + False + Export a part of document + False + Single Image + right + + + False + + + + + True + False + False + vertical + + + True + True + True + never + external + False + + + True + False + none + + + + True + False + vertical + 10 + 10 + + + 150 + True + True + True + True + never + False + in + + + True + False + + + True + False + True + 10 + 10 + 10 + none + False + + + + + + + 0 + 1 + 6 + + + + + True + False + True + expand + + + Selection + export_selection + True + False + False + Export selected Objects/Groups to separate file + True + False + + + True + True + 0 + + + + + Layers + export_selection + True + False + False + Export Layers as separate file + True + False + b_s_selection + + + True + True + 1 + + + + + 0 + 0 + 6 + + + + + Preview + True + False + False + False + start + True + True + + + 0 + 2 + 3 + + + + + True + False + 5 Items + + + 5 + 2 + + + + + Export Selected Only + True + False + False + False + start + True + + + 0 + 5 + 6 + + + + + True + False + vertical + + + + + + 0 + 6 + 6 + + + + + + True + False + 5 + 5 + 5 + 5 + 10 + 10 + + + + + + + + + + + + + + + 0 + 3 + 6 + 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + 0 + + + + + + True + False + 5 + 5 + 10 + + + True + True + True + folder-open + Filename + + + 0 + 0 + + + + + Export + True + False + True + + + 0 + 1 + + + + + True + False + + + 0 + 2 + + + + + False + True + 1 + + + + + 1 + + + + + True + False + Batch Export Objects and Layers + False + Batch Export + right + + + 1 + False + + + + + False + True + 0 + + + + diff --git a/share/ui/style.css b/share/ui/style.css index 15cb1e3a29f0cc1f7a8eebbe8082ba5b659eea4d..3f9f8cabaa5dbfb29bb28bc4483f0ea46a76cd92 100644 --- a/share/ui/style.css +++ b/share/ui/style.css @@ -614,3 +614,14 @@ button.square-button image { .wide-apply-button { min-width: 150px; } +/* + * Inkscape Export Dialog + */ + +#ExportDialog #export_selection{ + border-radius:0px; +} +#ExportDialog #export_preview_box{ + border:1px solid; +} + diff --git a/src/object/sp-object.cpp b/src/object/sp-object.cpp index fbae850d74ceb3e86cbe40be771a6869aeb1388d..d06f193083a3a47fd29828a8687f643cd674f455 100644 --- a/src/object/sp-object.cpp +++ b/src/object/sp-object.cpp @@ -16,27 +16,27 @@ * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ +#include #include +#include #include #include -#include - -#include -#include "helper/sp-marshal.h" -#include "xml/node-event-vector.h" -#include "attributes.h" #include "attribute-rel-util.h" +#include "attributes.h" #include "color-profile.h" +#include "debug/demangle.h" +#include "debug/event-tracker.h" +#include "debug/simple-event.h" #include "document.h" -#include "preferences.h" -#include "style.h" +#include "helper/sp-marshal.h" #include "live_effects/lpeobject.h" +#include "preferences.h" #include "sp-factory.h" #include "sp-paint-server.h" #include "sp-root.h" -#include "sp-style-elem.h" #include "sp-script.h" +#include "sp-style-elem.h" #include "streq.h" #include "strneq.h" #include "xml/node-fns.h" @@ -44,34 +44,36 @@ #include "debug/simple-event.h" #include "debug/demangle.h" #include "svg/css-ostringstream.h" +#include "style.h" #include "util/format.h" #include "util/longest-common-suffix.h" +#include "xml/node-event-vector.h" +#include "xml/node-fns.h" #define noSP_OBJECT_DEBUG_CASCADE #define noSP_OBJECT_DEBUG #ifdef SP_OBJECT_DEBUG -# define debug(f, a...) { g_print("%s(%d) %s:", \ - __FILE__,__LINE__,__FUNCTION__); \ - g_print(f, ## a); \ - g_print("\n"); \ - } +#define debug(f, a...) \ + { \ + g_print("%s(%d) %s:", __FILE__, __LINE__, __FUNCTION__); \ + g_print(f, ##a); \ + g_print("\n"); \ + } #else -# define debug(f, a...) /* */ +#define debug(f, a...) /* */ #endif // Define to enable indented tracing of SPObject. //#define OBJECT_TRACE unsigned SPObject::indent_level = 0; -Inkscape::XML::NodeEventVector object_event_vector = { - SPObject::repr_child_added, - SPObject::repr_child_removed, - SPObject::repr_attr_changed, - SPObject::repr_content_changed, - SPObject::repr_order_changed -}; +//clang-format off +Inkscape::XML::NodeEventVector object_event_vector = {SPObject::repr_child_added, SPObject::repr_child_removed, + SPObject::repr_attr_changed, SPObject::repr_content_changed, + SPObject::repr_order_changed}; +//clang-format on /** * A friend class used to set internal members on SPObject so as to not expose settors in SPObject's public API @@ -79,26 +81,27 @@ Inkscape::XML::NodeEventVector object_event_vector = { class SPObjectImpl { public: - -/** - * Null's the id member of an SPObject without attempting to free prior contents. - * - * @param[inout] obj Pointer to the object which's id shall be nulled. - */ - static void setIdNull( SPObject* obj ) { + /** + * Null's the id member of an SPObject without attempting to free prior contents. + * + * @param[inout] obj Pointer to the object which's id shall be nulled. + */ + static void setIdNull(SPObject *obj) + { if (obj) { obj->id = nullptr; } } -/** - * Sets the id member of an object, freeing any prior content. - * - * @param[inout] obj Pointer to the object which's id shall be set. - * @param[in] id New id - */ - static void setId( SPObject* obj, gchar const* id ) { - if (obj && (id != obj->id) ) { + /** + * Sets the id member of an object, freeing any prior content. + * + * @param[inout] obj Pointer to the object which's id shall be set. + * @param[in] id New id + */ + static void setId(SPObject *obj, gchar const *id) + { + if (obj && (id != obj->id)) { if (obj->id) { g_free(obj->id); obj->id = nullptr; @@ -114,14 +117,26 @@ public: * Constructor, sets all attributes to default values. */ SPObject::SPObject() - : cloned(0), clone_original(nullptr), uflags(0), mflags(0), hrefcount(0), _total_hrefcount(0), - document(nullptr), parent(nullptr), id(nullptr), repr(nullptr), refCount(1), hrefList(std::list()), - _successor(nullptr), _collection_policy(SPObject::COLLECT_WITH_PARENT), - _label(nullptr), _default_label(nullptr) + : cloned(0) + , clone_original(nullptr) + , uflags(0) + , mflags(0) + , hrefcount(0) + , _total_hrefcount(0) + , document(nullptr) + , parent(nullptr) + , id(nullptr) + , repr(nullptr) + , refCount(1) + , hrefList(std::list()) + , _successor(nullptr) + , _collection_policy(SPObject::COLLECT_WITH_PARENT) + , _label(nullptr) + , _default_label(nullptr) { - debug("id=%p, typename=%s",this, g_type_name_from_instance((GTypeInstance*)this)); + debug("id=%p, typename=%s", this, g_type_name_from_instance((GTypeInstance *)this)); - //used XML Tree here. + // used XML Tree here. this->getRepr(); // TODO check why this call is made SPObjectImpl::setIdNull(this); @@ -130,14 +145,15 @@ SPObject::SPObject() // vg, g, defs, desc, title, symbol, use, image, switch, path, rect, circle, ellipse, line, polyline, // polygon, text, tspan, tref, textPath, altGlyph, glyphRef, marker, linearGradient, radialGradient, // stop, pattern, clipPath, mask, filter, feImage, a, font, glyph, missing-glyph, foreignObject - this->style = new SPStyle( nullptr, this ); // Is it necessary to call with "this"? + this->style = new SPStyle(nullptr, this); // Is it necessary to call with "this"? this->context_style = nullptr; } /** * Destructor, frees the used memory and unreferences a potential successor of the object. */ -SPObject::~SPObject() { +SPObject::~SPObject() +{ g_free(this->_label); g_free(this->_default_label); @@ -152,11 +168,11 @@ SPObject::~SPObject() { parent->children.erase(parent->children.iterator_to(*this)); } - if( style == nullptr ) { + if (style == nullptr) { // style pointer could be NULL if unreffed too many times. // Conjecture: style pointer is never NULL. std::cerr << "SPObject::~SPObject(): style pointer is NULL" << std::endl; - } else if( style->refCount() > 1 ) { + } else if (style->refCount() > 1) { // Conjecture: style pointer should be unreffed by other classes before reaching here. // Conjecture is false for SPTSpan where ref is held by InputStreamTextSource. // As an additional note: @@ -165,27 +181,30 @@ SPObject::~SPObject() { // one for the one after, along with one for each corresponding DrawingText instance. // std::cerr << "SPObject::~SPObject(): someone else still holding ref to style" << std::endl; // - sp_style_unref( this->style ); + sp_style_unref(this->style); } else { delete this->style; } } // CPPIFY: make pure virtual -void SPObject::read_content() { - //throw; +void SPObject::read_content() +{ + // throw; } -void SPObject::update(SPCtx* /*ctx*/, unsigned int /*flags*/) { - //throw; +void SPObject::update(SPCtx * /*ctx*/, unsigned int /*flags*/) +{ + // throw; } -void SPObject::modified(unsigned int /*flags*/) { +void SPObject::modified(unsigned int /*flags*/) +{ #ifdef OBJECT_TRACE - objectTrace( "SPObject::modified (default) (empty function)" ); - objectTrace( "SPObject::modified (default)", false ); + objectTrace("SPObject::modified (default) (empty function)"); + objectTrace("SPObject::modified (default)", false); #endif - //throw; + // throw; } namespace { @@ -195,10 +214,11 @@ namespace Util = Inkscape::Util; typedef Debug::SimpleEvent BaseRefCountEvent; -class RefCountEvent : public BaseRefCountEvent { +class RefCountEvent : public BaseRefCountEvent +{ public: RefCountEvent(SPObject *object, int bias, char const *name) - : BaseRefCountEvent(name) + : BaseRefCountEvent(name) { _addProperty("object", Util::format("%p", object).pointer()); _addProperty("class", Debug::demangle(typeid(*object).name())); @@ -206,45 +226,50 @@ public: } }; -class RefEvent : public RefCountEvent { +class RefEvent : public RefCountEvent +{ public: RefEvent(SPObject *object) - : RefCountEvent(object, 1, "sp-object-ref") + : RefCountEvent(object, 1, "sp-object-ref") {} }; -class UnrefEvent : public RefCountEvent { +class UnrefEvent : public RefCountEvent +{ public: UnrefEvent(SPObject *object) - : RefCountEvent(object, -1, "sp-object-unref") + : RefCountEvent(object, -1, "sp-object-unref") {} }; -} +} // namespace -gchar const* SPObject::getId() const { +gchar const *SPObject::getId() const +{ return id; } /** * Returns the id as a url param, in the form 'url(#{id})' */ -std::string SPObject::getUrl() const { +std::string SPObject::getUrl() const +{ if (id) { return std::string("url(#") + id + ")"; } return ""; } -Inkscape::XML::Node * SPObject::getRepr() { +Inkscape::XML::Node *SPObject::getRepr() +{ return repr; } -Inkscape::XML::Node const* SPObject::getRepr() const{ +Inkscape::XML::Node const *SPObject::getRepr() const +{ return repr; } - SPObject *sp_object_ref(SPObject *object, SPObject *owner) { g_return_val_if_fail(object != nullptr, NULL); @@ -275,7 +300,7 @@ SPObject *sp_object_unref(SPObject *object, SPObject *owner) return nullptr; } -void SPObject::hrefObject(SPObject* owner) +void SPObject::hrefObject(SPObject *owner) { // if (owner) std::cout << " owner: " << *owner << std::endl; @@ -285,11 +310,11 @@ void SPObject::hrefObject(SPObject* owner) _updateTotalHRefCount(1); } - if(owner) + if (owner) hrefList.push_front(owner); } -void SPObject::unhrefObject(SPObject* owner) +void SPObject::unhrefObject(SPObject *owner) { g_return_if_fail(hrefcount > 0); @@ -298,20 +323,19 @@ void SPObject::unhrefObject(SPObject* owner) _updateTotalHRefCount(-1); } - if(owner) + if (owner) hrefList.remove(owner); } -void SPObject::_updateTotalHRefCount(int increment) { +void SPObject::_updateTotalHRefCount(int increment) +{ SPObject *topmost_collectable = nullptr; - for ( SPObject *iter = this ; iter ; iter = iter->parent ) { + for (SPObject *iter = this; iter; iter = iter->parent) { iter->_total_hrefcount += increment; - if ( iter->_total_hrefcount < iter->hrefcount ) { + if (iter->_total_hrefcount < iter->hrefcount) { g_critical("HRefs overcounted"); } - if ( iter->_total_hrefcount == 0 && - iter->_collection_policy != COLLECT_WITH_PARENT ) - { + if (iter->_total_hrefcount == 0 && iter->_collection_policy != COLLECT_WITH_PARENT) { topmost_collectable = iter; } } @@ -320,11 +344,12 @@ void SPObject::_updateTotalHRefCount(int increment) { } } -bool SPObject::isAncestorOf(SPObject const *object) const { +bool SPObject::isAncestorOf(SPObject const *object) const +{ g_return_val_if_fail(object != nullptr, false); object = object->parent; while (object) { - if ( object == this ) { + if (object == this) { return true; } object = object->parent; @@ -332,16 +357,18 @@ bool SPObject::isAncestorOf(SPObject const *object) const { return false; } -SPObject const *SPObject::nearestCommonAncestor(SPObject const *object) const { +SPObject const *SPObject::nearestCommonAncestor(SPObject const *object) const +{ g_return_val_if_fail(object != nullptr, NULL); using Inkscape::Algorithms::nearest_common_ancestor; return nearest_common_ancestor(this, object, nullptr); } -static SPObject const *AncestorSon(SPObject const *obj, SPObject const *ancestor) { +static SPObject const *AncestorSon(SPObject const *obj, SPObject const *ancestor) +{ SPObject const *result = nullptr; - if ( obj && ancestor ) { + if (obj && ancestor) { if (obj->parent == ancestor) { result = obj; } else { @@ -357,7 +384,7 @@ int sp_object_compare_position(SPObject const *first, SPObject const *second) if (first != second) { SPObject const *ancestor = first->nearestCommonAncestor(second); // Need a common ancestor to be able to compare - if ( ancestor ) { + if (ancestor) { // we have an object and its ancestor (should not happen when sorting selection) if (ancestor == first) { result = 1; @@ -376,13 +403,14 @@ int sp_object_compare_position(SPObject const *first, SPObject const *second) return result; } -bool sp_object_compare_position_bool(SPObject const *first, SPObject const *second){ - return sp_object_compare_position(first,second)<0; +bool sp_object_compare_position_bool(SPObject const *first, SPObject const *second) +{ + return sp_object_compare_position(first, second) < 0; } - -SPObject *SPObject::appendChildRepr(Inkscape::XML::Node *repr) { - if ( !cloned ) { +SPObject *SPObject::appendChildRepr(Inkscape::XML::Node *repr) +{ + if (!cloned) { getRepr()->appendChild(repr); return document->getObjectByRepr(repr); } else { @@ -403,9 +431,10 @@ void SPObject::changeCSS(SPCSSAttr *css, gchar const *attr) sp_repr_css_change(this->getRepr(), css, attr); } -std::vector SPObject::childList(bool add_ref, Action) { - std::vector l; - for (auto& child: children) { +std::vector SPObject::childList(bool add_ref, Action) +{ + std::vector l; + for (auto &child : children) { if (add_ref) { sp_object_ref(&child); } @@ -414,10 +443,10 @@ std::vector SPObject::childList(bool add_ref, Action) { return l; } -std::vector SPObject::ancestorList(bool root_to_tip) +std::vector SPObject::ancestorList(bool root_to_tip) { std::vector ancestors; - for (SPObject::ParentIterator iter=parent ; iter ; ++iter) { + for (SPObject::ParentIterator iter = parent; iter; ++iter) { ancestors.push_back(iter); } if (root_to_tip) { @@ -426,11 +455,13 @@ std::vector SPObject::ancestorList(bool root_to_tip) return ancestors; } -gchar const *SPObject::label() const { +gchar const *SPObject::label() const +{ return _label; } -gchar const *SPObject::defaultLabel() const { +gchar const *SPObject::defaultLabel() const +{ if (_label) { return _label; } else { @@ -452,8 +483,8 @@ void SPObject::setLabel(gchar const *label) getRepr()->setAttribute("inkscape:label", label); } - -void SPObject::requestOrphanCollection() { +void SPObject::requestOrphanCollection() +{ g_return_if_fail(document != nullptr); Inkscape::Preferences *prefs = Inkscape::Preferences::get(); @@ -462,7 +493,8 @@ void SPObject::requestOrphanCollection() { // leave it } else if (dynamic_cast(this)) { // leave it - } else if ((! prefs->getBool("/options/cleanupswatches/value", false)) && SP_IS_PAINT_SERVER(this) && static_cast(this)->isSwatch() ) { + } else if ((!prefs->getBool("/options/cleanupswatches/value", false)) && SP_IS_PAINT_SERVER(this) && + static_cast(this)->isSwatch()) { // leave it } else if (IS_COLORPROFILE(this)) { // leave it @@ -485,8 +517,9 @@ void SPObject::requestOrphanCollection() { } } -void SPObject::_sendDeleteSignalRecursive() { - for (auto& child: children) { +void SPObject::_sendDeleteSignalRecursive() +{ + for (auto &child : children) { child._delete_signal.emit(&child); child._sendDeleteSignalRecursive(); } @@ -495,7 +528,7 @@ void SPObject::_sendDeleteSignalRecursive() { void SPObject::deleteObject(bool propagate, bool propagate_descendants) { sp_object_ref(this, nullptr); - if ( SP_IS_LPE_ITEM(this) && SP_LPE_ITEM(this)->hasPathEffect()) { + if (SP_IS_LPE_ITEM(this) && SP_LPE_ITEM(this)->hasPathEffect()) { SP_LPE_ITEM(this)->removeAllPathEffects(false); } if (propagate) { @@ -504,7 +537,7 @@ void SPObject::deleteObject(bool propagate, bool propagate_descendants) if (propagate_descendants) { this->_sendDeleteSignalRecursive(); } - + Inkscape::XML::Node *repr = getRepr(); if (repr && repr->parent()) { sp_repr_unparent(repr); @@ -518,18 +551,60 @@ void SPObject::deleteObject(bool propagate, bool propagate_descendants) void SPObject::cropToObject(SPObject *except) { - std::vector toDelete; - for (auto& child: children) { + std::vector toDelete; + for (auto &child : children) { if (SP_IS_ITEM(&child)) { if (child.isAncestorOf(except)) { child.cropToObject(except); - } else if(&child != except) { + } else if (&child != except) { sp_object_ref(&child, nullptr); toDelete.push_back(&child); } } } - for (auto & i : toDelete) { + for (auto &i : toDelete) { + i->deleteObject(true, true); + sp_object_unref(i, nullptr); + } +} + +// Removes objects which are not related to given list of objects. +// Use Case: Group[MyRect1 , MyRect2] , MyRect3 +// List Provided: MyRect1, MyRect3 +// Output doc: Group[MyRect1], MyRect3 +// List Provided: MyRect1, Group +// Output doc: Group[MyRect1, MyRect2] (notice MyRect2 is not deleted as it is related to Group) + +void SPObject::cropToObjects(std::vector except_objects) +{ + if (except_objects.empty()) { + return; + } + std::vector toDelete; + for (auto &child : children) { + if (SP_IS_ITEM(&child)) { + std::vector except_in_child; + bool child_delete_flag = true; + for (auto except : except_objects) { + if (&child == except) { + child_delete_flag = false; + except_in_child.clear(); + break; + } + if (child.isAncestorOf(except)) { + except_in_child.push_back(except); + child_delete_flag = false; + } + } + if (child_delete_flag) { + sp_object_ref(&child, nullptr); + toDelete.push_back(&child); + } else { + child.cropToObjects(except_in_child); + } + } + } + for (auto &i : toDelete) { i->deleteObject(true, true); sp_object_unref(i, nullptr); } @@ -537,8 +612,8 @@ void SPObject::cropToObject(SPObject *except) void SPObject::attach(SPObject *object, SPObject *prev) { - //g_return_if_fail(parent != NULL); - //g_return_if_fail(SP_IS_OBJECT(parent)); + // g_return_if_fail(parent != NULL); + // g_return_if_fail(SP_IS_OBJECT(parent)); g_return_if_fail(object != nullptr); g_return_if_fail(SP_IS_OBJECT(object)); g_return_if_fail(!prev || SP_IS_OBJECT(prev)); @@ -559,7 +634,8 @@ void SPObject::attach(SPObject *object, SPObject *prev) object->xml_space.value = this->xml_space.value; } -void SPObject::reorder(SPObject* obj, SPObject* prev) { +void SPObject::reorder(SPObject *obj, SPObject *prev) +{ g_return_if_fail(obj != nullptr); g_return_if_fail(obj->parent); g_return_if_fail(obj->parent == this); @@ -576,8 +652,8 @@ void SPObject::reorder(SPObject* obj, SPObject* prev) { void SPObject::detach(SPObject *object) { - //g_return_if_fail(parent != NULL); - //g_return_if_fail(SP_IS_OBJECT(parent)); + // g_return_if_fail(parent != NULL); + // g_return_if_fail(SP_IS_OBJECT(parent)); g_return_if_fail(object != nullptr); g_return_if_fail(SP_IS_OBJECT(object)); g_return_if_fail(object->parent == this); @@ -597,9 +673,9 @@ SPObject *SPObject::get_child_by_repr(Inkscape::XML::Node *repr) SPObject *result = nullptr; if (children.size() > 0 && children.back().getRepr() == repr) { - result = &children.back(); // optimization for common scenario + result = &children.back(); // optimization for common scenario } else { - for (auto& child: children) { + for (auto &child : children) { if (child.getRepr() == repr) { result = &child; break; @@ -635,12 +711,13 @@ static SPObject *get_closest_child_by_repr(SPObject &obj, Inkscape::XML::Node *r return nullptr; } -void SPObject::child_added(Inkscape::XML::Node *child, Inkscape::XML::Node *ref) { - SPObject* object = this; +void SPObject::child_added(Inkscape::XML::Node *child, Inkscape::XML::Node *ref) +{ + SPObject *object = this; const std::string type_string = NodeTraits::get_type_string(*child); - SPObject* ochild = SPFactory::createObject(type_string); + SPObject *ochild = SPFactory::createObject(type_string); if (ochild == nullptr) { // Currently, there are many node types that do not have // corresponding classes in the SPObject tree. @@ -656,19 +733,21 @@ void SPObject::child_added(Inkscape::XML::Node *child, Inkscape::XML::Node *ref) ochild->invoke_build(object->document, child, object->cloned); } -void SPObject::release() { - SPObject* object = this; - debug("id=%p, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object)); - auto tmp = children | boost::adaptors::transformed([](SPObject& obj){return &obj;}); +void SPObject::release() +{ + SPObject *object = this; + debug("id=%p, typename=%s", object, g_type_name_from_instance((GTypeInstance *)object)); + auto tmp = children | boost::adaptors::transformed([](SPObject &obj) { return &obj; }); std::vector toRelease(tmp.begin(), tmp.end()); - for (auto& p: toRelease) { + for (auto &p : toRelease) { object->detach(p); } } -void SPObject::remove_child(Inkscape::XML::Node* child) { - debug("id=%p, typename=%s", this, g_type_name_from_instance((GTypeInstance*)this)); +void SPObject::remove_child(Inkscape::XML::Node *child) +{ + debug("id=%p, typename=%s", this, g_type_name_from_instance((GTypeInstance *)this)); SPObject *ochild = this->get_child_by_repr(child); @@ -678,8 +757,10 @@ void SPObject::remove_child(Inkscape::XML::Node* child) { } } -void SPObject::order_changed(Inkscape::XML::Node *child, Inkscape::XML::Node * /*old_ref*/, Inkscape::XML::Node *new_ref) { - SPObject* object = this; +void SPObject::order_changed(Inkscape::XML::Node *child, Inkscape::XML::Node * /*old_ref*/, + Inkscape::XML::Node *new_ref) +{ + SPObject *object = this; SPObject *ochild = object->get_child_by_repr(child); g_return_if_fail(ochild != nullptr); @@ -688,19 +769,19 @@ void SPObject::order_changed(Inkscape::XML::Node *child, Inkscape::XML::Node * / ochild->_position_changed_signal.emit(ochild); } -void SPObject::build(SPDocument *document, Inkscape::XML::Node *repr) { - +void SPObject::build(SPDocument *document, Inkscape::XML::Node *repr) +{ #ifdef OBJECT_TRACE - objectTrace( "SPObject::build" ); + objectTrace("SPObject::build"); #endif - SPObject* object = this; + SPObject *object = this; /* Nothing specific here */ - debug("id=%p, typename=%s", object, g_type_name_from_instance((GTypeInstance*)object)); + debug("id=%p, typename=%s", object, g_type_name_from_instance((GTypeInstance *)object)); object->readAttr(SPAttr::XML_SPACE); object->readAttr(SPAttr::LANG); - object->readAttr(SPAttr::XML_LANG); // "xml:lang" overrides "lang" per spec, read it last. + object->readAttr(SPAttr::XML_LANG); // "xml:lang" overrides "lang" per spec, read it last. object->readAttr(SPAttr::INKSCAPE_LABEL); object->readAttr(SPAttr::INKSCAPE_COLLECT); @@ -709,15 +790,15 @@ void SPObject::build(SPDocument *document, Inkscape::XML::Node *repr) { lang = object->parent->lang; } - if(object->cloned && (repr->attribute("id")) ) // The cases where this happens are when the "original" has no id. This happens - // if it is a SPString (a TextNode, e.g. in a ), or when importing - // stuff externally modified to have no id. + if (object->cloned && (repr->attribute("id"))) // The cases where this happens are when the "original" has no id. + // This happens if it is a SPString (a TextNode, e.g. in a <title>), + // or when importing stuff externally modified to have no id. object->clone_original = document->getObjectById(repr->attribute("id")); - for (Inkscape::XML::Node *rchild = repr->firstChild() ; rchild != nullptr; rchild = rchild->next()) { + for (Inkscape::XML::Node *rchild = repr->firstChild(); rchild != nullptr; rchild = rchild->next()) { const std::string typeString = NodeTraits::get_type_string(*rchild); - SPObject* child = SPFactory::createObject(typeString); + SPObject *child = SPFactory::createObject(typeString); if (child == nullptr) { // Currently, there are many node types that do not have // corresponding classes in the SPObject tree. @@ -732,19 +813,19 @@ void SPObject::build(SPDocument *document, Inkscape::XML::Node *repr) { } #ifdef OBJECT_TRACE - objectTrace( "SPObject::build", false ); + objectTrace("SPObject::build", false); #endif } void SPObject::invoke_build(SPDocument *document, Inkscape::XML::Node *repr, unsigned int cloned) { #ifdef OBJECT_TRACE - objectTrace( "SPObject::invoke_build" ); + objectTrace("SPObject::invoke_build"); #endif - debug("id=%p, typename=%s", this, g_type_name_from_instance((GTypeInstance*)this)); + debug("id=%p, typename=%s", this, g_type_name_from_instance((GTypeInstance *)this)); - //g_assert(object != NULL); - //g_assert(SP_IS_OBJECT(object)); + // g_assert(object != NULL); + // g_assert(SP_IS_OBJECT(object)); g_assert(document != nullptr); g_assert(repr != nullptr); @@ -764,7 +845,7 @@ void SPObject::invoke_build(SPDocument *document, Inkscape::XML::Node *repr, uns /* Invoke derived methods, if any */ this->build(document, repr); - if ( !cloned ) { + if (!cloned) { this->document->bindObjectToRepr(this->repr, this); if (Inkscape::XML::id_permitted(this->repr)) { @@ -797,12 +878,11 @@ void SPObject::invoke_build(SPDocument *document, Inkscape::XML::Node *repr, uns g_assert(this->getId() == nullptr); } - /* Signalling (should be connected AFTER processing derived methods */ sp_repr_add_listener(repr, &object_event_vector, this); #ifdef OBJECT_TRACE - objectTrace( "SPObject::invoke_build", false ); + objectTrace("SPObject::invoke_build", false); #endif } @@ -811,24 +891,27 @@ int SPObject::getIntAttribute(char const *key, int def) return getRepr()->getAttributeInt(key, def); } -unsigned SPObject::getPosition(){ +unsigned SPObject::getPosition() +{ g_assert(this->repr); return repr->position(); } -void SPObject::appendChild(Inkscape::XML::Node *child) { +void SPObject::appendChild(Inkscape::XML::Node *child) +{ g_assert(this->repr); repr->appendChild(child); } -SPObject* SPObject::nthChild(unsigned index) { +SPObject *SPObject::nthChild(unsigned index) +{ g_assert(this->repr); if (hasChildren()) { - std::vector<SPObject*> l; + std::vector<SPObject *> l; unsigned counter = 0; - for (auto& child: children) { + for (auto &child : children) { if (counter == index) { return &child; } @@ -838,14 +921,15 @@ SPObject* SPObject::nthChild(unsigned index) { return nullptr; } -void SPObject::addChild(Inkscape::XML::Node *child, Inkscape::XML::Node * prev) +void SPObject::addChild(Inkscape::XML::Node *child, Inkscape::XML::Node *prev) { g_assert(this->repr); - repr->addChild(child,prev); + repr->addChild(child, prev); } -void SPObject::releaseReferences() { +void SPObject::releaseReferences() +{ g_assert(this->document); g_assert(this->repr); @@ -884,7 +968,6 @@ void SPObject::releaseReferences() { this->repr = nullptr; } - SPObject *SPObject::getPrev() { SPObject *prev = nullptr; @@ -894,7 +977,7 @@ SPObject *SPObject::getPrev() return prev; } -SPObject* SPObject::getNext() +SPObject *SPObject::getNext() { SPObject *next = nullptr; if (parent && !parent->children.empty() && &parent->children.back() != this) { @@ -903,47 +986,49 @@ SPObject* SPObject::getNext() return next; } -void SPObject::repr_child_added(Inkscape::XML::Node * /*repr*/, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, gpointer data) +void SPObject::repr_child_added(Inkscape::XML::Node * /*repr*/, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, + gpointer data) { auto object = static_cast<SPObject *>(data); object->child_added(child, ref); } -void SPObject::repr_child_removed(Inkscape::XML::Node * /*repr*/, Inkscape::XML::Node *child, Inkscape::XML::Node * /*ref*/, gpointer data) +void SPObject::repr_child_removed(Inkscape::XML::Node * /*repr*/, Inkscape::XML::Node *child, + Inkscape::XML::Node * /*ref*/, gpointer data) { auto object = static_cast<SPObject *>(data); object->remove_child(child); } -void SPObject::repr_order_changed(Inkscape::XML::Node * /*repr*/, Inkscape::XML::Node *child, Inkscape::XML::Node *old, Inkscape::XML::Node *newer, gpointer data) +void SPObject::repr_order_changed(Inkscape::XML::Node * /*repr*/, Inkscape::XML::Node *child, Inkscape::XML::Node *old, + Inkscape::XML::Node *newer, gpointer data) { auto object = static_cast<SPObject *>(data); object->order_changed(child, old, newer); } -void SPObject::set(SPAttr key, gchar const* value) { - +void SPObject::set(SPAttr key, gchar const *value) +{ #ifdef OBJECT_TRACE std::stringstream temp; - temp << "SPObject::set: " << sp_attribute_name(key) << " " << (value?value:"null"); - objectTrace( temp.str() ); + temp << "SPObject::set: " << sp_attribute_name(key) << " " << (value ? value : "null"); + objectTrace(temp.str()); #endif g_assert(key != SPAttr::INVALID); - SPObject* object = this; + SPObject *object = this; switch (key) { - case SPAttr::ID: - //XML Tree being used here. - if ( !object->cloned && object->getRepr()->type() == Inkscape::XML::NodeType::ELEMENT_NODE ) { - SPDocument *document=object->document; - SPObject *conflict=nullptr; + // XML Tree being used here. + if (!object->cloned && object->getRepr()->type() == Inkscape::XML::NodeType::ELEMENT_NODE) { + SPDocument *document = object->document; + SPObject *conflict = nullptr; gchar const *new_id = value; @@ -951,7 +1036,7 @@ void SPObject::set(SPAttr key, gchar const* value) { conflict = document->getObjectById((char const *)new_id); } - if ( conflict && conflict != object ) { + if (conflict && conflict != object) { if (!document->isSeeking()) { sp_object_ref(conflict, nullptr); // give the conflicting object a new ID @@ -991,7 +1076,7 @@ void SPObject::set(SPAttr key, gchar const* value) { break; case SPAttr::INKSCAPE_COLLECT: - if ( value && !std::strcmp(value, "always") ) { + if (value && !std::strcmp(value, "always")) { object->setCollectionPolicy(SPObject::ALWAYS_COLLECT); } else { object->setCollectionPolicy(SPObject::COLLECT_WITH_PARENT); @@ -1028,7 +1113,7 @@ void SPObject::set(SPAttr key, gchar const* value) { break; case SPAttr::STYLE: - object->style->readFromObject( object ); + object->style->readFromObject(object); object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG); break; @@ -1036,14 +1121,14 @@ void SPObject::set(SPAttr key, gchar const* value) { break; } #ifdef OBJECT_TRACE - objectTrace( "SPObject::set", false ); + objectTrace("SPObject::set", false); #endif } void SPObject::setKeyValue(SPAttr key, gchar const *value) { - //g_assert(object != NULL); - //g_assert(SP_IS_OBJECT(object)); + // g_assert(object != NULL); + // g_assert(SP_IS_OBJECT(object)); this->set(key, value); } @@ -1062,11 +1147,11 @@ void SPObject::readAttr(SPAttr keyid) void SPObject::readAttr(gchar const *key) { - //g_assert(object != NULL); - //g_assert(SP_IS_OBJECT(object)); + // g_assert(object != NULL); + // g_assert(SP_IS_OBJECT(object)); g_assert(key != nullptr); - //XML Tree being used here. + // XML Tree being used here. g_assert(this->getRepr() != nullptr); auto keyid = sp_attribute_lookup(key); @@ -1078,7 +1163,8 @@ void SPObject::readAttr(gchar const *key) } } -void SPObject::repr_attr_changed(Inkscape::XML::Node * /*repr*/, gchar const *key, gchar const * /*oldval*/, gchar const * /*newval*/, bool is_interactive, gpointer data) +void SPObject::repr_attr_changed(Inkscape::XML::Node * /*repr*/, gchar const *key, gchar const * /*oldval*/, + gchar const * /*newval*/, bool is_interactive, gpointer data) { auto object = static_cast<SPObject *>(data); @@ -1091,7 +1177,8 @@ void SPObject::repr_attr_changed(Inkscape::XML::Node * /*repr*/, gchar const *ke } } -void SPObject::repr_content_changed(Inkscape::XML::Node * /*repr*/, gchar const * /*oldcontent*/, gchar const * /*newcontent*/, gpointer data) +void SPObject::repr_content_changed(Inkscape::XML::Node * /*repr*/, gchar const * /*oldcontent*/, + gchar const * /*newcontent*/, gpointer data) { auto object = static_cast<SPObject *>(data); @@ -1113,14 +1200,15 @@ static gchar const *sp_xml_get_space_string(unsigned int space) } } -Inkscape::XML::Node* SPObject::write(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags) { +Inkscape::XML::Node *SPObject::write(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags) +{ #ifdef OBJECT_TRACE - objectTrace( "SPObject::write" ); + objectTrace("SPObject::write"); #endif if (!repr && (flags & SP_OBJECT_WRITE_BUILD)) { repr = this->getRepr()->duplicate(doc); - if (!( flags & SP_OBJECT_WRITE_EXT )) { + if (!(flags & SP_OBJECT_WRITE_EXT)) { repr->removeAttribute("inkscape:collect"); } } else if (repr) { @@ -1132,9 +1220,7 @@ Inkscape::XML::Node* SPObject::write(Inkscape::XML::Document *doc, Inkscape::XML repr->setAttribute("xml:space", xml_space); } - if ( flags & SP_OBJECT_WRITE_EXT && - this->collectionPolicy() == SPObject::ALWAYS_COLLECT ) - { + if (flags & SP_OBJECT_WRITE_EXT && this->collectionPolicy() == SPObject::ALWAYS_COLLECT) { repr->setAttribute("inkscape:collect", "always"); } else { repr->removeAttribute("inkscape:collect"); @@ -1147,14 +1233,14 @@ Inkscape::XML::Node* SPObject::write(Inkscape::XML::Document *doc, Inkscape::XML // Write style attributes (SPStyleSrc::ATTRIBUTE) back to xml object bool any_written = false; auto properties = style->properties(); - for (auto * prop : properties) { - if(prop->shall_write(SP_STYLE_FLAG_IFSET | SP_STYLE_FLAG_IFSRC, SPStyleSrc::ATTRIBUTE)) { + for (auto *prop : properties) { + if (prop->shall_write(SP_STYLE_FLAG_IFSET | SP_STYLE_FLAG_IFSRC, SPStyleSrc::ATTRIBUTE)) { // WARNING: We don't know for sure if the css names are the same as the attribute names repr->setAttributeOrRemoveIfEmpty(prop->name(), prop->get_value()); any_written = true; } } - if(any_written) { + if (any_written) { // We need to ask the object to update the style and keep things in sync // see `case SPAttr::STYLE` above for how the style attr itself does this. style->readFromObject(this); @@ -1164,8 +1250,7 @@ Inkscape::XML::Node* SPObject::write(Inkscape::XML::Document *doc, Inkscape::XML // Check for valid attributes. This may be time consuming. // It is useful, though, for debugging Inkscape code. Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - if( prefs->getBool("/options/svgoutput/check_on_editing") ) { - + if (prefs->getBool("/options/svgoutput/check_on_editing")) { unsigned int flags = sp_attribute_clean_get_prefs(); style_prop = sp_attribute_clean_style(repr, style_prop.c_str(), flags); } @@ -1188,44 +1273,44 @@ Inkscape::XML::Node* SPObject::write(Inkscape::XML::Document *doc, Inkscape::XML } #ifdef OBJECT_TRACE - objectTrace( "SPObject::write", false ); + objectTrace("SPObject::write", false); #endif return repr; } -Inkscape::XML::Node * SPObject::updateRepr(unsigned int flags) +Inkscape::XML::Node *SPObject::updateRepr(unsigned int flags) { #ifdef OBJECT_TRACE - objectTrace( "SPObject::updateRepr 1" ); + objectTrace("SPObject::updateRepr 1"); #endif - if ( !cloned ) { + if (!cloned) { Inkscape::XML::Node *repr = getRepr(); if (repr) { #ifdef OBJECT_TRACE - objectTrace( "SPObject::updateRepr 1", false ); + objectTrace("SPObject::updateRepr 1", false); #endif return updateRepr(repr->document(), repr, flags); } else { g_critical("Attempt to update non-existent repr"); #ifdef OBJECT_TRACE - objectTrace( "SPObject::updateRepr 1", false ); + objectTrace("SPObject::updateRepr 1", false); #endif return nullptr; } } else { /* cloned objects have no repr */ #ifdef OBJECT_TRACE - objectTrace( "SPObject::updateRepr 1", false ); + objectTrace("SPObject::updateRepr 1", false); #endif return nullptr; } } -Inkscape::XML::Node * SPObject::updateRepr(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, unsigned int flags) +Inkscape::XML::Node *SPObject::updateRepr(Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, unsigned int flags) { #ifdef OBJECT_TRACE - objectTrace( "SPObject::updateRepr 2" ); + objectTrace("SPObject::updateRepr 2"); #endif g_assert(doc != nullptr); @@ -1233,7 +1318,7 @@ Inkscape::XML::Node * SPObject::updateRepr(Inkscape::XML::Document *doc, Inkscap if (cloned) { /* cloned objects have no repr */ #ifdef OBJECT_TRACE - objectTrace( "SPObject::updateRepr 2", false ); + objectTrace("SPObject::updateRepr 2", false); #endif return nullptr; } @@ -1244,19 +1329,18 @@ Inkscape::XML::Node * SPObject::updateRepr(Inkscape::XML::Document *doc, Inkscap #ifdef OBJECT_TRACE Inkscape::XML::Node *node = write(doc, repr, flags); - objectTrace( "SPObject::updateRepr 2", false ); + objectTrace("SPObject::updateRepr 2", false); return node; #else return this->write(doc, repr, flags); #endif - } /* Modification */ void SPObject::requestDisplayUpdate(unsigned int flags) { - g_return_if_fail( this->document != nullptr ); + g_return_if_fail(this->document != nullptr); #ifndef NDEBUG // expect no nested update calls @@ -1273,19 +1357,19 @@ void SPObject::requestDisplayUpdate(unsigned int flags) g_return_if_fail(!((flags & SP_OBJECT_MODIFIED_FLAG) && (flags & SP_OBJECT_CHILD_MODIFIED_FLAG))); #ifdef OBJECT_TRACE - objectTrace( "SPObject::requestDisplayUpdate" ); + objectTrace("SPObject::requestDisplayUpdate"); #endif bool already_propagated = (!(this->uflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))); - //https://stackoverflow.com/a/7841333 - if ((this->uflags & flags) != flags ) { + // https://stackoverflow.com/a/7841333 + if ((this->uflags & flags) != flags) { this->uflags |= flags; } /* If requestModified has already been called on this object or one of its children, then we * don't need to set CHILD_MODIFIED on our ancestors because it's already been done. */ if (already_propagated) { - if(this->document) { + if (this->document) { if (parent) { parent->requestDisplayUpdate(SP_OBJECT_CHILD_MODIFIED_FLAG); } else { @@ -1295,9 +1379,8 @@ void SPObject::requestDisplayUpdate(unsigned int flags) } #ifdef OBJECT_TRACE - objectTrace( "SPObject::requestDisplayUpdate", false ); + objectTrace("SPObject::requestDisplayUpdate", false); #endif - } void SPObject::updateDisplay(SPCtx *ctx, unsigned int flags) @@ -1305,13 +1388,14 @@ void SPObject::updateDisplay(SPCtx *ctx, unsigned int flags) g_return_if_fail(!(flags & ~SP_OBJECT_MODIFIED_CASCADE)); #ifdef OBJECT_TRACE - objectTrace( "SPObject::updateDisplay" ); + objectTrace("SPObject::updateDisplay"); #endif assert(++(document->update_in_progress)); #ifdef SP_OBJECT_DEBUG_CASCADE - g_print("Update %s:%s %x %x %x\n", g_type_name_from_instance((GTypeInstance *) this), getId(), flags, this->uflags, this->mflags); + g_print("Update %s:%s %x %x %x\n", g_type_name_from_instance((GTypeInstance *)this), getId(), flags, this->uflags, + this->mflags); #endif /* Get this flags */ @@ -1331,34 +1415,32 @@ void SPObject::updateDisplay(SPCtx *ctx, unsigned int flags) if ((flags & SP_OBJECT_STYLESHEET_MODIFIED_FLAG)) { style->readFromObject(this); } else if (parent && (flags & SP_OBJECT_STYLE_MODIFIED_FLAG) && (flags & SP_OBJECT_PARENT_MODIFIED_FLAG)) { - style->cascade( this->parent->style ); + style->cascade(this->parent->style); } } - try - { + try { this->update(ctx, flags); - } - catch(...) - { + } catch (...) { /** \todo - * in case of catching an exception we need to inform the user somehow that the document is corrupted - * maybe by implementing an document flag documentOk - * or by a modal error dialog - */ - g_warning("SPObject::updateDisplay(SPCtx *ctx, unsigned int flags) : throw in ((SPObjectClass *) G_OBJECT_GET_CLASS(this))->update(this, ctx, flags);"); + * in case of catching an exception we need to inform the user somehow that the document is corrupted + * maybe by implementing an document flag documentOk + * or by a modal error dialog + */ + g_warning("SPObject::updateDisplay(SPCtx *ctx, unsigned int flags) : throw in ((SPObjectClass *) " + "G_OBJECT_GET_CLASS(this))->update(this, ctx, flags);"); } assert((document->update_in_progress)--); #ifdef OBJECT_TRACE - objectTrace( "SPObject::updateDisplay", false ); + objectTrace("SPObject::updateDisplay", false); #endif } void SPObject::requestModified(unsigned int flags) { - g_return_if_fail( this->document != nullptr ); + g_return_if_fail(this->document != nullptr); /* requestModified must be used only to set one of SP_OBJECT_MODIFIED_FLAG or * SP_OBJECT_CHILD_MODIFIED_FLAG */ @@ -1367,7 +1449,7 @@ void SPObject::requestModified(unsigned int flags) g_return_if_fail(!((flags & SP_OBJECT_MODIFIED_FLAG) && (flags & SP_OBJECT_CHILD_MODIFIED_FLAG))); #ifdef OBJECT_TRACE - objectTrace( "SPObject::requestModified" ); + objectTrace("SPObject::requestModified"); #endif bool already_propagated = (!(this->mflags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))); @@ -1385,7 +1467,7 @@ void SPObject::requestModified(unsigned int flags) } } #ifdef OBJECT_TRACE - objectTrace( "SPObject::requestModified", false ); + objectTrace("SPObject::requestModified", false); #endif } @@ -1395,11 +1477,12 @@ void SPObject::emitModified(unsigned int flags) g_return_if_fail(!(flags & ~SP_OBJECT_MODIFIED_CASCADE)); #ifdef OBJECT_TRACE - objectTrace( "SPObject::emitModified", true, flags ); + objectTrace("SPObject::emitModified", true, flags); #endif #ifdef SP_OBJECT_DEBUG_CASCADE - g_print("Modified %s:%s %x %x %x\n", g_type_name_from_instance((GTypeInstance *) this), getId(), flags, this->uflags, this->mflags); + g_print("Modified %s:%s %x %x %x\n", g_type_name_from_instance((GTypeInstance *)this), getId(), flags, this->uflags, + this->mflags); #endif flags |= this->mflags; @@ -1416,7 +1499,7 @@ void SPObject::emitModified(unsigned int flags) sp_object_unref(this); #ifdef OBJECT_TRACE - objectTrace( "SPObject::emitModified", false ); + objectTrace("SPObject::emitModified", false); #endif } @@ -1425,7 +1508,7 @@ gchar const *SPObject::getTagName() const g_assert(repr != nullptr); /// \todo fixme: Exception if object is NULL? */ - //XML Tree being used here. + // XML Tree being used here. return getRepr()->name(); } @@ -1434,17 +1517,16 @@ gchar const *SPObject::getAttribute(gchar const *key) const g_assert(this->repr != nullptr); /// \todo fixme: Exception if object is NULL? */ - //XML Tree being used here. - return (gchar const *) getRepr()->attribute(key); + // XML Tree being used here. + return (gchar const *)getRepr()->attribute(key); } -void SPObject::setAttribute(Inkscape::Util::const_char_ptr key, - Inkscape::Util::const_char_ptr value) +void SPObject::setAttribute(Inkscape::Util::const_char_ptr key, Inkscape::Util::const_char_ptr value) { g_assert(this->repr != nullptr); /// \todo fixme: Exception if object is NULL? */ - //XML Tree being used here. + // XML Tree being used here. getRepr()->setAttribute(key, value); } @@ -1457,13 +1539,13 @@ void SPObject::setAttributeDouble(Inkscape::Util::const_char_ptr key, double val void SPObject::removeAttribute(gchar const *key) { /// \todo fixme: Exception if object is NULL? */ - //XML Tree being used here. + // XML Tree being used here. getRepr()->removeAttribute(key); } -bool SPObject::storeAsDouble( gchar const *key, double *val ) const +bool SPObject::storeAsDouble(gchar const *key, double *val) const { - g_assert(this->getRepr()!= nullptr); + g_assert(this->getRepr() != nullptr); double nan = std::numeric_limits<double>::quiet_NaN(); double temp_val = ((Inkscape::XML::Node *)(this->getRepr()))->getAttributeDouble(key, nan); if (std::isnan(temp_val)) { @@ -1474,9 +1556,7 @@ bool SPObject::storeAsDouble( gchar const *key, double *val ) const } /** Helper */ -gchar * -sp_object_get_unique_id(SPObject *object, - gchar const *id) +gchar *sp_object_get_unique_id(SPObject *object, gchar const *id) { static unsigned long count = 0; @@ -1484,7 +1564,7 @@ sp_object_get_unique_id(SPObject *object, count++; - //XML Tree being used here. + // XML Tree being used here. gchar const *name = object->getRepr()->name(); g_assert(name != nullptr); @@ -1501,23 +1581,24 @@ sp_object_get_unique_id(SPObject *object, size_t const name_len = std::strlen(name); size_t const buflen = name_len + (sizeof(count) * 10 / 4) + 1; - gchar *const buf = (gchar *) g_malloc(buflen); + gchar *const buf = (gchar *)g_malloc(buflen); std::memcpy(buf, name, name_len); gchar *const count_buf = buf + name_len; size_t const count_buflen = buflen - name_len; do { ++count; g_snprintf(count_buf, count_buflen, "%lu", count); - } while ( object->document->getObjectById(buf) != nullptr ); + } while (object->document->getObjectById(buf) != nullptr); return buf; } -void SPObject::_requireSVGVersion(Inkscape::Version version) { - for ( SPObject::ParentIterator iter=this ; iter ; ++iter ) { +void SPObject::_requireSVGVersion(Inkscape::Version version) +{ + for (SPObject::ParentIterator iter = this; iter; ++iter) { SPObject *object = iter; if (SP_IS_ROOT(object)) { SPRoot *root = SP_ROOT(object); - if ( root->version.svg < version ) { + if (root->version.svg < version) { root->version.svg = version; } } @@ -1539,7 +1620,7 @@ void SPObject::_requireSVGVersion(Inkscape::Version version) { be allowed with different localized strings. */ -gchar * SPObject::title() const +gchar *SPObject::title() const { return getTitleOrDesc("svg:title"); } @@ -1549,7 +1630,7 @@ bool SPObject::setTitle(gchar const *title, bool verbatim) return setTitleOrDesc(title, "svg:title", verbatim); } -gchar * SPObject::desc() const +gchar *SPObject::desc() const { return getTitleOrDesc("svg:desc"); } @@ -1559,14 +1640,14 @@ bool SPObject::setDesc(gchar const *desc, bool verbatim) return setTitleOrDesc(desc, "svg:desc", verbatim); } -char * SPObject::getTitleOrDesc(gchar const *svg_tagname) const +char *SPObject::getTitleOrDesc(gchar const *svg_tagname) const { char *result = nullptr; SPObject *elem = findFirstChild(svg_tagname); - if ( elem ) { - //This string copy could be avoided by changing - //the return type of SPObject::getTitleOrDesc - //to std::unique_ptr<Glib::ustring> + if (elem) { + // This string copy could be avoided by changing + // the return type of SPObject::getTitleOrDesc + // to std::unique_ptr<Glib::ustring> result = g_strdup(elem->textualContent().c_str()); } return result; @@ -1625,12 +1706,11 @@ bool SPObject::setTitleOrDesc(gchar const *value, gchar const *svg_tagname, bool repr->addChild(xml_elem, nullptr); elem = document->getObjectByRepr(xml_elem); Inkscape::GC::release(xml_elem); - } - else { + } else { // remove the current content of the 'text' or 'desc' element - auto tmp = elem->children | boost::adaptors::transformed([](SPObject& obj) { return &obj; }); - std::vector<SPObject*> vec(tmp.begin(), tmp.end()); - for (auto &child: vec) { + auto tmp = elem->children | boost::adaptors::transformed([](SPObject &obj) { return &obj; }); + std::vector<SPObject *> vec(tmp.begin(), tmp.end()); + for (auto &child : vec) { child->deleteObject(); } } @@ -1640,12 +1720,10 @@ bool SPObject::setTitleOrDesc(gchar const *value, gchar const *svg_tagname, bool return true; } -SPObject* SPObject::findFirstChild(gchar const *tagname) const +SPObject *SPObject::findFirstChild(gchar const *tagname) const { - for (auto& child: const_cast<SPObject*>(this)->children) - { - if (child.repr->type() == Inkscape::XML::NodeType::ELEMENT_NODE && - !std::strcmp(child.repr->name(), tagname)) { + for (auto &child : const_cast<SPObject *>(this)->children) { + if (child.repr->type() == Inkscape::XML::NodeType::ELEMENT_NODE && !std::strcmp(child.repr->name(), tagname)) { return &child; } } @@ -1656,14 +1734,12 @@ Glib::ustring SPObject::textualContent() const { Glib::ustring text; - for (auto& child: children) - { + for (auto &child : children) { Inkscape::XML::NodeType child_type = child.repr->type(); if (child_type == Inkscape::XML::NodeType::ELEMENT_NODE) { text += child.textualContent(); - } - else if (child_type == Inkscape::XML::NodeType::TEXT_NODE) { + } else if (child_type == Inkscape::XML::NodeType::TEXT_NODE) { text += child.repr->content(); } } @@ -1671,7 +1747,7 @@ Glib::ustring SPObject::textualContent() const } // For debugging: Print SP tree structure. -void SPObject::recursivePrintTree( unsigned level ) +void SPObject::recursivePrintTree(unsigned level) { if (level == 0) { std::cout << "SP Object Tree" << std::endl; @@ -1680,24 +1756,24 @@ void SPObject::recursivePrintTree( unsigned level ) for (unsigned i = 0; i < level; ++i) { std::cout << " "; } - std::cout << (getId()?getId():"No object id") - << " clone: " << std::boolalpha << (bool)cloned + std::cout << (getId() ? getId() : "No object id") << " clone: " << std::boolalpha << (bool)cloned << " hrefcount: " << hrefcount << std::endl; - for (auto& child: children) { + for (auto &child : children) { child.recursivePrintTree(level + 1); } } // Function to allow tracing of program flow through SPObject and derived classes. // To trace function, add at entrance ('in' = true) and exit of function ('in' = false). -void SPObject::objectTrace( std::string const &text, bool in, unsigned flags ) { - if( in ) { +void SPObject::objectTrace(std::string const &text, bool in, unsigned flags) +{ + if (in) { for (unsigned i = 0; i < indent_level; ++i) { std::cout << " "; } std::cout << text << ":" << " entrance: " - << (id?id:"null") + << (id ? id : "null") // << " uflags: " << uflags // << " mflags: " << mflags // << " flags: " << flags @@ -1710,7 +1786,7 @@ void SPObject::objectTrace( std::string const &text, bool in, unsigned flags ) { } std::cout << text << ":" << " exit: " - << (id?id:"null") + << (id ? id : "null") // << " uflags: " << uflags // << " mflags: " << mflags // << " flags: " << flags @@ -1720,11 +1796,8 @@ void SPObject::objectTrace( std::string const &text, bool in, unsigned flags ) { std::ostream &operator<<(std::ostream &out, const SPObject &o) { - out << (o.getId()?o.getId():"No ID") - << " cloned: " << std::boolalpha << (bool)o.cloned - << " ref: " << o.refCount - << " href: " << o.hrefcount - << " total href: " << o._total_hrefcount; + out << (o.getId() ? o.getId() : "No ID") << " cloned: " << std::boolalpha << (bool)o.cloned + << " ref: " << o.refCount << " href: " << o.hrefcount << " total href: " << o._total_hrefcount; return out; } /* diff --git a/src/object/sp-object.h b/src/object/sp-object.h index 42d9cff2ecdab3ed436ce76934cc2cc9f24bdacb..f6d794ea123c0b38a4c2afe2d92d280bc64ae7c8 100644 --- a/src/object/sp-object.h +++ b/src/object/sp-object.h @@ -466,7 +466,8 @@ public: /** * Removes all children except for the given object, it's children and it's ancesstors. */ - void cropToObject(SPObject *except); + void cropToObject(SPObject *except); + void cropToObjects(std::vector<SPObject *> except_objects); /** * Connects a slot to be called when an object is deleted. @@ -480,11 +481,10 @@ public: * * @see SPObject::deleteObject */ - sigc::connection connectDelete(sigc::slot<void, SPObject *> slot) { - return _delete_signal.connect(slot); - } + sigc::connection connectDelete(sigc::slot<void, SPObject *> slot) { return _delete_signal.connect(slot); } - sigc::connection connectPositionChanged(sigc::slot<void, SPObject *> slot) { + sigc::connection connectPositionChanged(sigc::slot<void, SPObject *> slot) + { return _position_changed_signal.connect(slot); } diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index 26ea2675e19cb69528b9b88e41070ac6fb98695e..73df92f79ff15dc021d1c81556de957d274c2dbd 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -113,6 +113,10 @@ set(ui_SRC dialog/dialog-window.cpp dialog/document-properties.cpp dialog/export.cpp + dialog/export-batch.cpp + dialog/export-single.cpp + dialog/export-helper.cpp + dialog/export-preview.cpp dialog/filedialog.cpp dialog/filedialogimpl-gtkmm.cpp dialog/fill-and-stroke.cpp @@ -138,6 +142,7 @@ set(ui_SRC dialog/object-properties.cpp dialog/objects.cpp dialog/polar-arrange-tab.cpp + dialog/preview-util.cpp dialog/print.cpp dialog/prototype.cpp dialog/selectorsdialog.cpp @@ -286,6 +291,10 @@ set(ui_SRC dialog/dialog-window.h dialog/document-properties.h dialog/export.h + dialog/export-batch.h + dialog/export-single.h + dialog/export-helper.h + dialog/export-preview.h dialog/filedialog.h dialog/filedialogimpl-gtkmm.h dialog/filedialogimpl-win32.h @@ -312,6 +321,7 @@ set(ui_SRC dialog/object-properties.h dialog/objects.h dialog/polar-arrange-tab.h + dialog/preview-util.h dialog/print.h dialog/prototype.h dialog/selectorsdialog.h diff --git a/src/ui/dialog/export-batch.cpp b/src/ui/dialog/export-batch.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ba3aa3e5cc6cbb6798cb38ebed12b71c6c64f9e7 --- /dev/null +++ b/src/ui/dialog/export-batch.cpp @@ -0,0 +1,710 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Authors: + * Lauris Kaplinski <lauris@kaplinski.com> + * bulia byak <buliabyak@users.sf.net> + * Johan Engelen <j.b.c.engelen@ewi.utwente.nl> + * Anshudhar Kumar Singh <anshudhar2001@gmail.com> + * + * Copyright (C) 1999-2007, 2021 Authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "export-batch.h" + +#include <glibmm/convert.h> +#include <glibmm/i18n.h> +#include <glibmm/miscutils.h> +#include <gtkmm.h> +#include <png.h> + +#include "desktop.h" +#include "document-undo.h" +#include "document.h" +#include "export-helper.h" +#include "export-preview.h" +#include "extension/db.h" +#include "file.h" +#include "helper/png-write.h" +#include "inkscape-window.h" +#include "inkscape.h" +#include "io/resource.h" +#include "io/sys.h" +#include "message-stack.h" +#include "object/object-set.h" +#include "object/sp-namedview.h" +#include "object/sp-root.h" +#include "preferences.h" +#include "selection-chemistry.h" +#include "ui/dialog-events.h" +#include "ui/dialog/dialog-notebook.h" +#include "ui/dialog/filedialog.h" +#include "ui/interface.h" +#include "ui/widget/scrollprotected.h" +#include "ui/widget/unit-menu.h" + +#ifdef _WIN32 + +#endif + +using Inkscape::Util::unit_table; + +namespace Inkscape { +namespace UI { +namespace Dialog { + +// START OF BATCH ITEM + +class BatchItem : public Gtk::FlowBoxChild +{ +public: + BatchItem(SPItem *item); + ~BatchItem() override; + +public: + Gtk::CheckButton selector; + SPItem *getItem() { return _item; } + bool isActive() { return selector.get_active(); } + void refresh(bool hide = false); + void refreshHide(const std::vector<SPItem *> *list) { preview->refreshHide(list); } + +private: + Gtk::Grid grid; + Gtk::Label label; + ExportPreview *preview = nullptr; + SPItem *_item; +}; + +BatchItem::~BatchItem() +{ + if (preview) { + delete preview; + preview = nullptr; + } +} + +BatchItem::BatchItem(SPItem *item) + : grid() + , selector() +{ + if (!item) { + return; + } + _item = item; + grid.attach(selector, 0, 1, 1, 1); + grid.set_row_spacing(5); + grid.set_column_spacing(5); + + Glib::ustring id = _item->defaultLabel(); + if (id.empty()) { + id = _item->getId(); + } + Glib::ustring compactId = id.substr(0, 7); + if (id.length() > 7) { + compactId = compactId + "..."; + } + + selector.set_active(true); + selector.set_can_focus(false); + selector.set_valign(Gtk::Align::ALIGN_END); + selector.set_margin_start(2); + selector.set_margin_bottom(2); + + if (!preview) { + preview = Gtk::manage(new ExportPreview()); + grid.attach(*preview, 0, 0, 2, 2); + } + preview->setItem(_item); + preview->setDocument(_item->document); + preview->setSize(64); + + label.set_text(compactId); + label.set_halign(Gtk::Align::ALIGN_CENTER); + grid.attach(label, 0, 2, 2, 1); + + add(grid); + show_all_children(); + show(); + this->set_can_focus(false); + this->set_tooltip_text(id); +} + +void BatchItem::refresh(bool hide) +{ + if (!_item) { + return; + } + if (hide) { + preview->resetPixels(); + } else { + preview->queueRefresh(); + } +} + +// END OF BATCH ITEM + +// START OF BATCH EXPORT + +BatchExport::~BatchExport() +{ + ; +} + +void BatchExport::initialise(const Glib::RefPtr<Gtk::Builder> &builder) +{ + builder->get_widget("b_s_selection", selection_buttons[SELECTION_SELECTION]); + selection_names[SELECTION_SELECTION] = "selection"; + builder->get_widget("b_s_layers", selection_buttons[SELECTION_LAYER]); + selection_names[SELECTION_LAYER] = "layer"; + + builder->get_widget("b_preview_box", preview_container); + builder->get_widget("b_show_preview", show_preview); + builder->get_widget("b_num_elements", num_elements); + builder->get_widget("b_advance_box", adv_box); + builder->get_widget("b_hide_all", hide_all); + builder->get_widget("b_filename", filename_entry); + builder->get_widget("b_export", export_btn); + builder->get_widget("b_progress_bar", _prog); + builder->get_widget_derived("b_export_list", export_list); + + Inkscape::UI::Widget::ScrollTransfer<Gtk::ScrolledWindow> *temp = nullptr; + builder->get_widget_derived("b_pbox_scroll", temp); + builder->get_widget_derived("b_scroll", temp); +} + +void BatchExport::selectionModified(Inkscape::Selection *selection, guint flags) +{ + if (!_desktop || _desktop->getSelection() != selection) { + return; + } + if (!(flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_PARENT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + return; + } + refreshItems(); +} + +void BatchExport::selectionChanged(Inkscape::Selection *selection) +{ + if (!_desktop || _desktop->getSelection() != selection) { + return; + } + if (selection->isEmpty()) { + selection_buttons[SELECTION_SELECTION]->set_sensitive(false); + if (current_key == SELECTION_SELECTION) { + selection_buttons[(selection_mode)0]->set_active(true); // This causes refresh area + // return otherwise refreshArea will be called again + // even though we are at default key, selection is the one which was original key. + prefs->setString("/dialogs/export/batchexportarea/value", selection_names[SELECTION_SELECTION]); + return; + } + } else { + selection_buttons[SELECTION_SELECTION]->set_sensitive(true); + Glib::ustring pref_key_name = prefs->getString("/dialogs/export/batchexportarea/value"); + if (selection_names[SELECTION_SELECTION] == pref_key_name && current_key != SELECTION_SELECTION) { + selection_buttons[SELECTION_SELECTION]->set_active(); + return; + } + } + refreshItems(); + refreshExportHints(); +} + +// Setup Single Export.Called by export on realize +void BatchExport::setup() +{ + if (setupDone) { + return; + } + setupDone = true; + prefs = Inkscape::Preferences::get(); + + // Setup Advance Options + adv_box->pack_start(advance_options, true, true, 0); + adv_box->show_all_children(); + + export_list->setup(); + + // set them before connecting to signals + setDefaultFilename(); + setDefaultSelectionMode(); + + refreshExportHints(); + + refreshItems(); + + // Connect Signals + for (auto [key, button] : selection_buttons) { + button->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &BatchExport::onAreaTypeToggle), key)); + } + show_preview->signal_toggled().connect(sigc::mem_fun(*this, &BatchExport::refreshPreview)); + filenameConn = filename_entry->signal_changed().connect(sigc::mem_fun(*this, &BatchExport::onFilenameModified)); + exportConn = export_btn->signal_clicked().connect(sigc::mem_fun(*this, &BatchExport::onExport)); + browseConn = filename_entry->signal_icon_press().connect(sigc::mem_fun(*this, &BatchExport::onBrowse)); + hide_all->signal_toggled().connect(sigc::mem_fun(*this, &BatchExport::refreshPreview)); +} + +void BatchExport::refreshItems() +{ + if (!_desktop) { + return; + } + SPDocument *doc = _desktop->getDocument(); + if (!doc) { + return; + } + doc->ensureUpToDate(); + + // Create New List of Items + std::set<SPItem *> itemsList; + switch (current_key) { + case SELECTION_SELECTION: { + auto items = _desktop->getSelection()->items(); + for (auto i = items.begin(); i != items.end(); ++i) { + SPItem *item = *i; + if (item) { + itemsList.insert(item); + } + } + break; + } + case SELECTION_LAYER: { + auto layersList = doc->getResourceList("layer"); + for (auto item : layersList) { + if (SP_IS_GROUP(item) && SP_GROUP(item)->layerMode() == SPGroup::LAYER) { + itemsList.insert(dynamic_cast<SPItem *>(item)); + } + } + + break; + } + default: + break; + } + + // Number of Items + int num = itemsList.size(); + Glib::ustring label_text = std::to_string(num) + " Items"; + num_elements->set_text(label_text); + + // Create a list of items which are already present but will be removed as they are not present anymore + std::vector<std::string> toRemove; + for (auto &[key, val] : current_items) { + SPItem *item = val->getItem(); + // if item is not present in itemList add it to remove list so that we can remove it + auto itemItr = itemsList.find(item); + if (itemItr == itemsList.end() || (*itemItr)->getId() != key) { + toRemove.push_back(key); + } + } + + // now remove all the items + for (auto key : toRemove) { + if (current_items[key]) { + // Preview Boxes are GTK managed so simply removing from container will handle delete + preview_container->remove(*current_items[key]); + current_items.erase(key); + } + } + + // now add which were are new + for (auto &item : itemsList) { + auto id = item->getId(); + // If an Item with same Id is already present, Skip + if (current_items[id] && current_items[id]->getItem() == item) { + continue; + } + // Add new item to the end of list + current_items[id] = Gtk::manage(new BatchItem(item)); + preview_container->insert(*current_items[id], -1); + } + + refreshPreview(); +} + +void BatchExport::refreshPreview() +{ + if (_desktop) { + // For Batch Export we are now hiding all object except current object + // std::vector<SPItem *> selected(_desktop->getSelection()->items().begin(), + // _desktop->getSelection()->items().end()); + bool hide = hide_all->get_active(); + for (auto &[key, val] : current_items) { + if (show_preview->get_active()) { + std::vector<SPItem *> selected = {val->getItem()}; + val->refreshHide(hide ? &selected : nullptr); + val->refresh(); + } else { + val->refresh(true); + } + } + } +} + +void BatchExport::refreshExportHints() +{ + ; +} + +// Signals CallBack + +void BatchExport::onAreaTypeToggle(selection_mode key) +{ + // Prevent executing function twice + if (!selection_buttons[key]->get_active()) { + return; + } + // If you have reached here means the current key is active one ( not sure if multiple transitions happen but + // last call will change values) + current_key = key; + prefs->setString("/dialogs/export/batchexportarea/value", selection_names[current_key]); + + refreshItems(); + refreshExportHints(); +} + +void BatchExport::onFilenameModified() +{ + ; +} + +void BatchExport::onExport() +{ + interrupted = false; + if (!_desktop) + return; + export_btn->set_sensitive(false); + bool exportSuccessful = true; + + // If there are no selected button, simply flash message in status bar + int num = current_items.size(); + if (current_items.size() == 0) { + _desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("No items selected.")); + export_btn->set_sensitive(true); + return; + } + + // Find and remove any extension from filename so that we can add suffix to it. + Glib::ustring filename = filename_entry->get_text(); + Glib::ustring filename_extension = get_ext_from_filename(filename); + if (ExtensionList::all_extensions[filename_extension]) { + auto extension_point = filename.rfind(filename_extension); + filename.erase(extension_point); + } + + int n = 0; + + // create vector of exports + int num_rows = export_list->get_rows(); + std::vector<Glib::ustring> suffixs; + std::vector<Glib::ustring> extensions; + std::vector<double> dpis; + for (int i = 0; i < num_rows; i++) { + suffixs.push_back(export_list->get_suffix(i)); + extensions.push_back(export_list->get_extension(i)); + dpis.push_back(export_list->get_dpi(i)); + } + + // We are exporting standalone items only for now + // std::vector<SPItem *> selected(_desktop->getSelection()->items().begin(), + // _desktop->getSelection()->items().end()); + bool hide = hide_all->get_active(); + + // Start Exporting Each Item + for (auto i = current_items.begin(); i != current_items.end() && !interrupted; ++i) { + BatchItem *batchItem = i->second; + if (!batchItem->isActive()) { + n++; + continue; + } + SPItem *item = batchItem->getItem(); + if (!item) { + n++; + continue; + } + + Geom::OptRect area = item->documentVisualBounds(); + if (!area) { + n++; + continue; + } + Glib::ustring id = item->defaultLabel(); + if (id.empty()) { + id = item->getId(); + } + if (id.empty()) { + n++; + continue; + } + for (int i = 0; i < num_rows; i++) { + auto omod = ExtensionList::valid_extensions[extensions[i]]; + float dpi = dpis[i]; + + Glib::ustring item_filename = filename + "_" + id; + if (!suffixs[i].empty()) { + item_filename = item_filename + "_" + suffixs[i]; + } + item_filename = item_filename + "_" + std::to_string((int)dpi); + + if (!omod) { + continue; + } + if (prog_dlg) { + delete prog_dlg; + prog_dlg = nullptr; + } + prog_dlg = create_progress_dialog(Glib::ustring::compose(_("Exporting %1 files"), num)); + prog_dlg->set_export_panel(this); + setExporting(true, Glib::ustring::compose(_("Exporting %1 files"), num)); + prog_dlg->set_current(n); + prog_dlg->set_total(num); + + onProgressCallback(0.0, prog_dlg); + bool found = getNonConflictingFilename(item_filename, extensions[i]); + if (!found) { + n++; + continue; + } + + if (omod->is_raster()) { + unsigned long int width = (int)(area->width() * dpi / DPI_BASE + 0.5); + unsigned long int height = (int)(area->height() * dpi / DPI_BASE + 0.5); + + std::vector<SPItem *> show_only = {item}; + exportSuccessful = _export_raster(*area, width, height, dpi, item_filename, true, onProgressCallback, + prog_dlg, omod, hide ? &show_only : nullptr, &advance_options); + } else { + setExporting(true, Glib::ustring::compose(_("Exporting %1"), filename)); + SPDocument *doc = _desktop->getDocument(); + SPDocument *copy_doc = (doc->copy()).get(); + std::vector<SPItem *> items; + items.push_back(item); + exportSuccessful = _export_vector(omod, copy_doc, item_filename, true, &items); + } + setExporting(false); + } + if (prog_dlg) { + delete prog_dlg; + prog_dlg = nullptr; + } + } +} + +bool BatchExport::getNonConflictingFilename(Glib::ustring &filename, Glib::ustring const extension) +{ + if (!_desktop) { + return false; + } + SPDocument *doc = _desktop->getDocument(); + std::string path = absolutize_path_from_document_location(doc, Glib::filename_from_utf8(filename)); + Glib::ustring test_filename = path + extension; + if (!Inkscape::IO::file_test(test_filename.c_str(), G_FILE_TEST_EXISTS)) { + filename = test_filename; + return true; + } + for (int i = 1; i <= 100; i++) { + test_filename = path + "_copy_" + std::to_string(i) + extension; + if (!Inkscape::IO::file_test(test_filename.c_str(), G_FILE_TEST_EXISTS)) { + filename = test_filename; + return true; + } + } + return false; +} + +void BatchExport::onBrowse(Gtk::EntryIconPosition pos, const GdkEventButton *ev) +{ + if (!_app) { + return; + } + Gtk::Window *window = _app->get_active_window(); + browseConn.block(); + Glib::ustring filename = Glib::filename_from_utf8(filename_entry->get_text()); + + if (filename.empty()) { + Glib::ustring tmp; + filename = create_filepath_from_id(tmp, tmp); + } + + Inkscape::UI::Dialog::FileSaveDialog *dialog = Inkscape::UI::Dialog::FileSaveDialog::create( + *window, filename, Inkscape::UI::Dialog::RASTER_TYPES, _("Select a filename for exporting"), "", "", + Inkscape::Extension::FILE_SAVE_METHOD_EXPORT); + + if (dialog->show()) { + filename = dialog->getFilename(); + Inkscape::Extension::Output *selection_type = + dynamic_cast<Inkscape::Extension::Output *>(dialog->getSelectionType()); + Glib::ustring extension = selection_type->get_extension(); + ExtensionList::appendExtensionToFilename(filename, extension); + filename_entry->set_text(filename); + filename_entry->set_position(filename.length()); + // deleting dialog before exporting is important + // proper delete function should be made for dialog IMO + delete dialog; + onExport(); + } else { + delete dialog; + } + browseConn.unblock(); +} + +// Utils Functions + +// We first check any export hints related to document. If there is none we create a default name using document +// name. doc_export_name is set here and will only be changed when exporting. +void BatchExport::setDefaultFilename() +{ + if (!_desktop) { + return; + } + Glib::ustring filename; + float xdpi = 0.0, ydpi = 0.0; + SPDocument *doc = _desktop->getDocument(); + sp_document_get_export_hints(doc, filename, &xdpi, &ydpi); + if (filename.empty()) { + Glib::ustring filename_entry_text = filename_entry->get_text(); + Glib::ustring extension = ".png"; + filename = get_default_filename(filename_entry_text, extension, doc); + } + doc_export_name = filename; + original_name = filename; + filename_entry->set_text(filename); + filename_entry->set_position(filename.length()); +} + +void BatchExport::setDefaultSelectionMode() +{ + current_key = (selection_mode)0; // default key + bool found = false; + Glib::ustring pref_key_name = prefs->getString("/dialogs/export/batchexportarea/value"); + for (auto [key, name] : selection_names) { + if (pref_key_name == name) { + current_key = key; + found = true; + break; + } + } + if (!found) { + pref_key_name = selection_names[current_key]; + } + if (_desktop) { + if (current_key == SELECTION_SELECTION && (_desktop->getSelection())->isEmpty()) { + current_key = (selection_mode)0; + } + if ((_desktop->getSelection())->isEmpty()) { + selection_buttons[SELECTION_SELECTION]->set_sensitive(false); + } + } + selection_buttons[current_key]->set_active(true); + + // we need to set pref key because signals above will set set pref == current key but we sometimes change + // current key like selection key + prefs->setString("/dialogs/export/batchexportarea/value", pref_key_name); +} + +void BatchExport::setExporting(bool exporting, Glib::ustring const &text) +{ + if (exporting) { + _prog->set_text(text); + _prog->set_fraction(0.0); + _prog->set_sensitive(true); + export_btn->set_sensitive(false); + } else { + _prog->set_text(""); + _prog->set_fraction(0.0); + _prog->set_sensitive(false); + export_btn->set_sensitive(true); + } +} + +ExportProgressDialog *BatchExport::create_progress_dialog(Glib::ustring progress_text) +{ + // dont forget to delete it later + auto dlg = new ExportProgressDialog(_("Export in progress"), true); + dlg->set_transient_for(*(INKSCAPE.active_desktop()->getToplevel())); + + Gtk::ProgressBar *prg = Gtk::manage(new Gtk::ProgressBar()); + prg->set_text(progress_text); + dlg->set_progress(prg); + auto CA = dlg->get_content_area(); + CA->pack_start(*prg, FALSE, FALSE, 4); + + Gtk::Button *btn = dlg->add_button(_("_Cancel"), Gtk::RESPONSE_CANCEL); + + btn->signal_clicked().connect(sigc::mem_fun(*this, &BatchExport::onProgressCancel)); + dlg->signal_delete_event().connect(sigc::mem_fun(*this, &BatchExport::onProgressDelete)); + + dlg->show_all(); + return dlg; +} + +/// Called when dialog is deleted +bool BatchExport::onProgressDelete(GdkEventAny * /*event*/) +{ + interrupted = true; + prog_dlg->set_stopped(); + return TRUE; +} + +/// Called when progress is cancelled +void BatchExport::onProgressCancel() +{ + interrupted = true; + prog_dlg->set_stopped(); +} + +/// Called for every progress iteration +unsigned int BatchExport::onProgressCallback(float value, void *dlg) +{ + auto dlg2 = reinterpret_cast<ExportProgressDialog *>(dlg); + + auto self = dynamic_cast<BatchExport *>(dlg2->get_export_panel()); + + if (!self || self->interrupted) + return FALSE; + + auto current = dlg2->get_current(); + auto total = dlg2->get_total(); + if (total > 0) { + double completed = current; + completed /= static_cast<double>(total); + + value = completed + (value / static_cast<double>(total)); + } + + auto prg = dlg2->get_progress(); + prg->set_fraction(value); + + if (self) { + self->_prog->set_fraction(value); + } + + int evtcount = 0; + while ((evtcount < 16) && gdk_events_pending()) { + Gtk::Main::iteration(false); + evtcount += 1; + } + + Gtk::Main::iteration(false); + return TRUE; +} + +void BatchExport::setDocument(SPDocument *document) +{ + ; +} + +} // namespace Dialog +} // namespace UI +} // namespace Inkscape + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : \ No newline at end of file diff --git a/src/ui/dialog/export-batch.h b/src/ui/dialog/export-batch.h new file mode 100644 index 0000000000000000000000000000000000000000..ab8b89d44c436641eb9d3839dcf6326d99b1a497 --- /dev/null +++ b/src/ui/dialog/export-batch.h @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Authors: + * Lauris Kaplinski <lauris@kaplinski.com> + * bulia byak <buliabyak@users.sf.net> + * Johan Engelen <j.b.c.engelen@ewi.utwente.nl> + * Anshudhar Kumar Singh <anshudhar2001@gmail.com> + * + * Copyright (C) 1999-2007, 2021 Authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SP_EXPORT_BATCH_H +#define SP_EXPORT_BATCH_H + +#include <gtkmm.h> + +#include "export-helper.h" +#include "extension/output.h" +#include "ui/widget/scroll-utils.h" +#include "ui/widget/scrollprotected.h" +#include "ui/widget/unit-menu.h" + +namespace Inkscape { +namespace UI { +namespace Dialog { + +class BatchItem; + +class BatchExport : public Gtk::Box +{ +public: + BatchExport(){}; + BatchExport(BaseObjectType *cobject, const Glib::RefPtr<Gtk::Builder> &refGlade) + : Gtk::Box(cobject){}; + ~BatchExport() override; + +private: + InkscapeApplication *_app; + SPDesktop *_desktop = nullptr; + +private: + bool setupDone = false; // To prevent setup() call add connections again. + +public: + void setApp(InkscapeApplication *app) { _app = app; } + void setDocument(SPDocument *document); + void setDesktop(SPDesktop *desktop) { _desktop = desktop; } + void selectionChanged(Inkscape::Selection *selection); + void selectionModified(Inkscape::Selection *selection, guint flags); + +private: + enum selection_mode + { + SELECTION_LAYER = 0, // Default is alaways placed first + SELECTION_SELECTION, + }; + +private: + typedef Inkscape::UI::Widget::ScrollProtected<Gtk::SpinButton> SpinButton; + + std::map<selection_mode, Gtk::RadioButton *> selection_buttons; + Gtk::FlowBox *preview_container = nullptr; + Gtk::CheckButton *show_preview = nullptr; + Gtk::Label *num_elements = nullptr; + Gtk::Box *adv_box = nullptr; + Gtk::CheckButton *hide_all = nullptr; + Gtk::Entry *filename_entry = nullptr; + Gtk::Button *export_btn = nullptr; + Gtk::ProgressBar *_prog = nullptr; + ExportList *export_list = nullptr; + + AdvanceOptions advance_options; + +private: + // Store all items to be displayed in flowbox + std::map<std::string, BatchItem *> current_items; + +private: + bool filename_modified; + Glib::ustring original_name; + Glib::ustring doc_export_name; + + Inkscape::Preferences *prefs = nullptr; + std::map<selection_mode, Glib::ustring> selection_names; + selection_mode current_key; + +public: + // initialise variables from builder + void initialise(const Glib::RefPtr<Gtk::Builder> &builder); + void setup(); + bool getNonConflictingFilename(Glib::ustring &filename, Glib::ustring const extension); + +private: + void setDefaultSelectionMode(); + void setDefaultFilename(); + +private: + void onFilenameModified(); + void onAreaTypeToggle(selection_mode key); + void onExport(); + void onBrowse(Gtk::EntryIconPosition pos, const GdkEventButton *ev); + +public: + void refresh() + { + refreshItems(); + refreshExportHints(); + }; + +private: + void refreshPreview(); + void refreshItems(); + void refreshExportHints(); + +private: + void setExporting(bool exporting, Glib::ustring const &text = ""); + ExportProgressDialog *create_progress_dialog(Glib::ustring progress_text); + /** + * Callback to be used in for loop to update the progress bar. + * + * @param value number between 0 and 1 indicating the fraction of progress (0.17 = 17 % progress) + * @param dlg void pointer to the Gtk::Dialog progress dialog + */ + static unsigned int onProgressCallback(float value, void *dlg); + + /** + * Callback for pressing the cancel button. + */ + void onProgressCancel(); + + /** + * Callback invoked on closing the progress dialog. + */ + bool onProgressDelete(GdkEventAny *event); + +private: + ExportProgressDialog *prog_dlg = nullptr; + bool interrupted; + +private: + sigc::connection filenameConn; + sigc::connection exportConn; + sigc::connection browseConn; + sigc::connection selectionModifiedConn; + sigc::connection selectionChangedConn; +}; +} // namespace Dialog +} // namespace UI +} // namespace Inkscape +#endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : \ No newline at end of file diff --git a/src/ui/dialog/export-helper.cpp b/src/ui/dialog/export-helper.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6a72109e372291f21b5a53c2bb2988e889a0b84d --- /dev/null +++ b/src/ui/dialog/export-helper.cpp @@ -0,0 +1,704 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Authors: + * Anshudhar Kumar Singh <anshudhar2001@gmail.com> + * + * Copyright (C) 2021 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "export-helper.h" + +#include <glibmm/convert.h> +#include <glibmm/i18n.h> +#include <glibmm/miscutils.h> +#include <gtkmm.h> +#include <png.h> + +#include "desktop.h" +#include "document-undo.h" +#include "document.h" +#include "extension/db.h" +#include "file.h" +#include "helper/png-write.h" +#include "inkscape-window.h" +#include "inkscape.h" +#include "io/resource.h" +#include "io/sys.h" +#include "message-stack.h" +#include "object/object-set.h" +#include "object/sp-namedview.h" +#include "object/sp-root.h" +#include "preferences.h" +#include "selection-chemistry.h" +#include "ui/dialog-events.h" +#include "ui/dialog/dialog-notebook.h" +#include "ui/dialog/filedialog.h" +#include "ui/icon-loader.h" +#include "ui/interface.h" +#include "ui/widget/scrollprotected.h" +#include "ui/widget/unit-menu.h" + +#ifdef _WIN32 + +#endif + +using Inkscape::Util::unit_table; + +namespace Inkscape { +namespace UI { +namespace Dialog { + +AdvanceOptions::AdvanceOptions() + : row(0) +{ + this->set_label(_("Advance")); + Gtk::Grid *grid = Gtk::manage(new Gtk::Grid()); + this->add(*grid); + { + interlacing.set_label(_("Use Interlacing")); + grid->attach(interlacing, 0, row, 2, 1); + row++; + } + { + bit_depth_list.clear(); + bit_depth_list.insert(bit_depth_list.end(), {{_("Gray 1"), {1, 0}}, + {_("Gray 2"), {2, 0}}, + {_("Gray 4"), {4, 0}}, + {_("Gray 8"), {8, 0}}, + {_("Gray 16"), {16, 0}}, + {_("RGB 8"), {8, 2}}, + {_("RGB 16"), {16, 2}}, + {_("GrayAlpha 8"), {8, 4}}, + {_("GrayAlpha 16"), {16, 4}}, + {_("RGBA 8"), {8, 6}}, + {_("RGBA 16"), {16, 6}}}); + + for (auto [label, depth] : bit_depth_list) { + bit_depth_cb.append(label); + } + + bit_depth_cb.set_active_text(_("RGBA 8")); + bit_depth_cb.set_hexpand(); + Gtk::Label *bit_depth_label = Gtk::manage(new Gtk::Label(_("Bit Depth"), Gtk::ALIGN_START)); + grid->attach(*bit_depth_label, 0, row, 1, 1); + grid->attach(bit_depth_cb, 1, row, 1, 1); + row++; + } + { + compression_list.clear(); + compression_list.insert(compression_list.end(), {{_("Z No Compression"), 0}, + {_("Z Best Speed"), 1}, + {_("2"), 2}, + {_("3"), 3}, + {_("4"), 4}, + {_("5"), 5}, + {_("Z Default Compression"), 6}, + {_("7"), 7}, + {_("8"), 8}, + {_("Z Best Compression"), 9}}); + for (auto [label, compress] : compression_list) { + compression_cb.append(label); + } + + compression_cb.set_active_text(_("Z Default Compression")); + Gtk::Label *compression_label = Gtk::manage(new Gtk::Label(_("Compression"), Gtk::ALIGN_START)); + grid->attach(*compression_label, 0, row, 1, 1); + grid->attach(compression_cb, 1, row, 1, 1); + row++; + } + + { + auto pHYs_adj = Gtk::Adjustment::create(0, 0, 100000, 0.1, 1.0, 0); + pHYs_sb.set_adjustment(pHYs_adj); + pHYs_sb.set_width_chars(7); + pHYs_sb.set_digits(2); + Gtk::Label *phys_dpi_label = Gtk::manage(new Gtk::Label(_("pHYs DPI"), Gtk::ALIGN_START)); + grid->attach(*phys_dpi_label, 0, row, 1, 1); + grid->attach(pHYs_sb, 1, row, 1, 1); + row++; + } + { + anti_aliasing_list.clear(); + anti_aliasing_list.insert(anti_aliasing_list.end(), {{_("Cairo Antialias None"), 0}, + {_("Cairo Antialias Fast"), 1}, + {_("Cairo Antialias Good (Default)"), 2}, + {_("Cairo Antialias Best"), 3}}); + + for (auto [label, anti_alias] : anti_aliasing_list) { + anti_aliasing_cb.append(label); + } + + anti_aliasing_cb.set_active_text(_("Cairo Antialias Good (Default)")); + Gtk::Label *anti_aliasing_label = Gtk::manage(new Gtk::Label(_("Anti Aliasing"), Gtk::ALIGN_START)); + grid->attach(*anti_aliasing_label, 0, row, 1, 1); + grid->attach(anti_aliasing_cb, 1, row, 1, 1); + row++; + } + grid->set_row_spacing(4); + grid->set_column_spacing(5); +} + +AdvanceOptions::~AdvanceOptions() +{ + ; +} + +bool ExtensionList::list_created{false}; +std::map<Glib::ustring, Inkscape::Extension::Output *> ExtensionList::valid_extensions{}; +std::map<Glib::ustring, Inkscape::Extension::Output *> ExtensionList::all_extensions{}; + +void ExtensionList::setup() +{ + this->remove_all(); + createList(); + + for (auto [key, omod] : valid_extensions) { + this->append(key); + } + this->set_active_text(".png"); +} +void ExtensionList::createList() +{ + if (list_created) { + return; + } + Inkscape::Extension::DB::OutputList extensions; + Inkscape::Extension::db.get_output_list(extensions); + Glib::ustring extension; + for (auto omod : extensions) { + all_extensions[omod->get_extension()] = omod; + + // FIXME: would be nice to grey them out instead of not listing them + if (omod->deactivated() || (omod->is_raster() != true)) + continue; + + extension = omod->get_extension(); + valid_extensions[extension] = omod; + } + + // add extentions manually + Inkscape::Extension::Output *manual_omod; + manual_omod = dynamic_cast<Inkscape::Extension::Output *>(Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG)); + extension = manual_omod->get_extension(); + valid_extensions[extension] = manual_omod; + + list_created = true; +} + +ExtensionList::~ExtensionList() +{ + ; +} +void ExtensionList::setExtensionFromFilename(Glib::ustring const &filename) +{ + Glib::ustring extension = get_ext_from_filename(filename); + if (valid_extensions[extension]) { + this->set_active_text(extension); + return; + } +} +void ExtensionList::appendExtensionToFilename(Glib::ustring &filename) +{ + Glib::ustring filename_extension = get_ext_from_filename(filename); + Glib::ustring active_extension = this->get_active_text(); + if (active_extension == filename_extension) { + return; + } + if (valid_extensions[filename_extension]) { + auto extension_point = filename.rfind(filename_extension); + filename.erase(extension_point); + } + filename = filename + active_extension; + return; +} +void ExtensionList::appendExtensionToFilename(Glib::ustring &filename, Glib::ustring &extension) +{ + createList(); + Glib::ustring filename_extension = get_ext_from_filename(filename); + Glib::ustring active_extension = extension; + if (all_extensions[filename_extension]) { + auto extension_point = filename.rfind(filename_extension); + filename.erase(extension_point); + } + if (valid_extensions[filename_extension]) { + active_extension = filename_extension; + } + // We use ".png" as default extension. Change it to get extension from module. + if (!valid_extensions[active_extension]) { + active_extension = ".png"; + } + filename = filename + active_extension; + return; +} + +void ExportList::setup() +{ + if (_initialised) { + return; + } + _initialised = true; + prefs = Inkscape::Preferences::get(); + default_dpi = prefs->getDouble("/dialogs/export/defaultxdpi/value", DPI_BASE); + + Gtk::Button *add_button = Gtk::manage(new Gtk::Button()); + Glib::ustring label = "Add Export"; + add_button->set_label(label); + this->attach(*add_button, 0, 0, 4, 1); + + this->insert_row(0); + + Gtk::Label *suffix_label = Gtk::manage(new Gtk::Label("Suffix")); + this->attach(*suffix_label, _suffix_col, 0, 1, 1); + suffix_label->show(); + + Gtk::Label *extension_label = Gtk::manage(new Gtk::Label("Format")); + this->attach(*extension_label, _extension_col, 0, 1, 1); + extension_label->show(); + + Gtk::Label *dpi_label = Gtk::manage(new Gtk::Label("DPI")); + this->attach(*dpi_label, _dpi_col, 0, 1, 1); + dpi_label->show(); + + append_row(); + + add_button->signal_clicked().connect(sigc::mem_fun(*this, &ExportList::append_row)); + add_button->set_hexpand(true); + add_button->show(); + + this->set_row_spacing(5); + this->set_column_spacing(2); +} + +ExportList::~ExportList() +{ + ; +} + +void ExportList::append_row() +{ + int current_row = _num_rows + 1; // because we have label row at top + this->insert_row(current_row); + + Gtk::Entry *suffix = Gtk::manage(new Gtk::Entry()); + this->attach(*suffix, _suffix_col, current_row, 1, 1); + suffix->set_width_chars(2); + suffix->set_hexpand(true); + suffix->set_placeholder_text("Suffix"); + suffix->show(); + + ExtensionList *extension = Gtk::manage(new ExtensionList()); + extension->setup(); + this->attach(*extension, _extension_col, current_row, 1, 1); + extension->show(); + + SpinButton *dpi_sb = Gtk::manage(new SpinButton()); + dpi_sb->set_digits(2); + dpi_sb->set_increments(0.1, 1.0); + dpi_sb->set_range(1.0, 100000.0); + dpi_sb->set_value(default_dpi); + dpi_sb->set_sensitive(true); + dpi_sb->set_width_chars(6); + dpi_sb->set_max_width_chars(6); + this->attach(*dpi_sb, _dpi_col, current_row, 1, 1); + dpi_sb->show(); + + Gtk::Image *pIcon = Gtk::manage(sp_get_icon_image("window-close", Gtk::ICON_SIZE_SMALL_TOOLBAR)); + Gtk::Button *delete_btn = Gtk::manage(new Gtk::Button()); + delete_btn->set_relief(Gtk::RELIEF_NONE); + delete_btn->set_no_show_all(true); + if (_num_rows != 0) { + Gtk::Widget *d_button_0 = dynamic_cast<Gtk::Widget *>(this->get_child_at(_delete_col, 1)); + if (d_button_0) { + d_button_0->show(); + } + delete_btn->show(); + } + pIcon->show(); + delete_btn->add(*pIcon); + this->attach(*delete_btn, _delete_col, current_row, 1, 1); + delete_btn->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &ExportList::delete_row), delete_btn)); + + _num_rows++; +} + +void ExportList::delete_row(Gtk::Widget *widget) +{ + if (widget == nullptr) { + return; + } + if (_num_rows <= 1) { + return; + } + int row = this->child_property_top_attach(*widget); + this->remove_row(row); + _num_rows--; + if (_num_rows <= 1) { + Gtk::Widget *d_button_0 = dynamic_cast<Gtk::Widget *>(this->get_child_at(_delete_col, 1)); + if (d_button_0) { + d_button_0->hide(); + } + } +} + +Glib::ustring ExportList::get_suffix(int row) +{ + Glib::ustring suffix = ""; + Gtk::Entry *entry = dynamic_cast<Gtk::Entry *>(this->get_child_at(_suffix_col, row + 1)); + if (entry == nullptr) { + return suffix; + } + suffix = entry->get_text(); + return suffix; +} +Glib::ustring ExportList::get_extension(int row) +{ + Glib::ustring extension = ""; + ExtensionList *extension_cb = dynamic_cast<ExtensionList *>(this->get_child_at(_extension_col, row + 1)); + if (extension_cb == nullptr) { + return extension; + } + extension = extension_cb->get_active_text(); + return extension; +} +double ExportList::get_dpi(int row) +{ + double dpi = default_dpi; + SpinButton *spin_sb = dynamic_cast<SpinButton *>(this->get_child_at(_dpi_col, row + 1)); + if (spin_sb == nullptr) { + return dpi; + } + dpi = spin_sb->get_value(); + return dpi; +} + +/* + ****************************************** + * HELPER FUNCTIONS NOT SPECIF TO CLASSES * + ****************************************** + */ + +float getValuePx(float value, Unit const *unit) +{ + return Inkscape::Util::Quantity::convert(value, unit, "px"); +} + +void setValuePx(Glib::RefPtr<Gtk::Adjustment> &adj, double val, Unit const *unit) +{ + auto value = Inkscape::Util::Quantity::convert(val, "px", unit); + adj->set_value(value); + return; +} + +// We Create filename by removing already present extension in document name and replacing it with extension passed +// as parameter if exxtension is not valid. If document doesn't have a name we use bitmap as defalt name. +Glib::ustring get_default_filename(Glib::ustring &filename_entry_text, Glib::ustring &extension, SPDocument *doc) +{ + Glib::ustring filename; + if (doc && doc->getDocumentFilename()) { + filename = doc->getDocumentFilename(); + ExtensionList::appendExtensionToFilename(filename, extension); + } else if (doc) { + filename = create_filepath_from_id(_("bitmap"), filename_entry_text); + filename = filename + extension; + } + return filename; +} + +std::string create_filepath_from_id(Glib::ustring id, const Glib::ustring &file_entry_text) +{ + if (id.empty()) { /* This should never happen */ + id = "bitmap"; + } + + std::string directory; + + if (!file_entry_text.empty()) { + directory = Glib::path_get_dirname(Glib::filename_from_utf8(file_entry_text)); + } + + if (directory.empty()) { + /* Grab document directory */ + const gchar *docFilename = SP_ACTIVE_DOCUMENT->getDocumentFilename(); + if (docFilename) { + directory = Glib::path_get_dirname(docFilename); + } + } + + if (directory.empty()) { + directory = Inkscape::IO::Resource::homedir_path(nullptr); + } + + return Glib::build_filename(directory, Glib::filename_from_utf8(id)); +} + +Glib::ustring get_ext_from_filename(Glib::ustring const &filename) +{ + Glib::ustring extension = ""; + if (!filename.empty()) { + auto extension_point = filename.rfind('.'); + if (extension_point != Glib::ustring::npos) { + extension = filename.substr(extension_point); + } + } + return extension; +} + +std::string absolutize_path_from_document_location(SPDocument *doc, const std::string &filename) +{ + std::string path; + // Make relative paths go from the document location, if possible: + if (!Glib::path_is_absolute(filename) && doc->getDocumentFilename()) { + auto dirname = Glib::path_get_dirname(doc->getDocumentFilename()); + if (!dirname.empty()) { + path = Glib::build_filename(dirname, filename); + } + } + if (path.empty()) { + path = filename; + } + return path; +} + +bool _export_raster(Geom::Rect const &area, unsigned long int const &width, unsigned long int const &height, + float const &dpi, Glib::ustring const &filename, bool overwrite, + unsigned (*callback)(float, void *), ExportProgressDialog *&prog_dialog, + Inkscape::Extension::Output *extension, std::vector<SPItem *> *items, AdvanceOptions *adv) +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (!desktop) + return false; + SPNamedView *nv = desktop->getNamedView(); + SPDocument *doc = desktop->getDocument(); + + if (area.hasZeroArea() || width == 0 || height == 0) { + desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("The chosen area to be exported is invalid.")); + sp_ui_error_dialog(_("The chosen area to be exported is invalid")); + return false; + } + if (filename.empty()) { + desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("You have to enter a filename.")); + sp_ui_error_dialog(_("You have to enter a filename")); + return false; + } + + if (!extension || !extension->is_raster()) { + desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Raster Export Error")); + sp_ui_error_dialog(_("Raster export Method is used for NON RASTER EXTENSION")); + return false; + } + + // Advance Parameters default value. We will change them later if adv dialog is provided. + bool use_interlacing = false; // Maybe use prefs here? + float pHYs = dpi; // default is dpi. + int bit_depth = 8; // corresponds to RGBA 8 + int color_type = 6; // corresponds to RGBA 8 + int zlib = 6; // Z_DEFAULT_COMPRESSION + int antialiasing = 2; // Cairo anti aliasing + + if (adv) { + use_interlacing = adv->get_interlacing(); + if (adv->get_pHYs() > 0.01) { + pHYs = adv->get_pHYs(); + } + bit_depth = adv->get_bit_depth(); + color_type = adv->get_color(); + zlib = adv->get_compression(); + antialiasing = adv->get_anti_aliasing(); + } + + std::string path = absolutize_path_from_document_location(doc, Glib::filename_from_utf8(filename)); + Glib::ustring dirname = Glib::path_get_dirname(path); + + if (dirname.empty() || + !Inkscape::IO::file_test(dirname.c_str(), (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))) { + Glib::ustring safeDir = Inkscape::IO::sanitizeString(dirname.c_str()); + Glib::ustring error = + g_strdup_printf(_("Directory <b>%s</b> does not exist or is not a directory.\n"), safeDir.c_str()); + + desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, error.c_str()); + sp_ui_error_dialog(error.c_str()); + return false; + } + + // Do the over-write protection now, since the png is just a temp file. + if (!overwrite && !sp_ui_overwrite_file(path.c_str())) { + return false; + } + + auto fn = Glib::path_get_basename(path); + auto png_filename = path; + { + // Select the extension and set the filename to a temporary file + int tempfd_out = Glib::file_open_tmp(png_filename, "ink_ext_"); + close(tempfd_out); + } + + // Export Start Here + std::vector<SPItem *> selected; + if (items && items->size() > 0) { + selected = *items; + } + + ExportResult result = sp_export_png_file(desktop->getDocument(), png_filename.c_str(), area, width, height, pHYs, + pHYs, // previously xdpi, ydpi. + nv->pagecolor, callback, (void *)prog_dialog, true, selected, + use_interlacing, color_type, bit_depth, zlib, antialiasing); + + bool failed = result == EXPORT_ERROR || prog_dialog->get_stopped(); + delete prog_dialog; + prog_dialog = nullptr; + if (failed) { + Glib::ustring safeFile = Inkscape::IO::sanitizeString(path.c_str()); + Glib::ustring error = g_strdup_printf(_("Could not export to filename <b>%s</b>.\n"), safeFile.c_str()); + + desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, error.c_str()); + sp_ui_error_dialog(error.c_str()); + return false; + } else if (result == EXPORT_OK) { + if (extension->prefs()) { + try { + extension->export_raster(doc, png_filename, path.c_str(), false); + } catch (Inkscape::Extension::Output::save_failed &e) { + return false; + } + } else { + return false; + } + + } else { + // Extensions have their own error popup, so this only tracks failures in the png step + desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE, _("Export aborted.")); + return false; + } + + auto recentmanager = Gtk::RecentManager::get_default(); + if (recentmanager && Glib::path_is_absolute(path)) { + Glib::ustring uri = Glib::filename_to_uri(path); + recentmanager->add_item(uri); + } + + Glib::ustring safeFile = Inkscape::IO::sanitizeString(path.c_str()); + desktop->messageStack()->flashF(Inkscape::INFORMATION_MESSAGE, _("Drawing exported to <b>%s</b>."), + safeFile.c_str()); + + unlink(png_filename.c_str()); + return true; +} + +bool _export_vector(Inkscape::Extension::Output *extension, SPDocument *doc, Glib::ustring const &filename, + bool overwrite, std::vector<SPItem *> *items) +{ + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if (!desktop) + return false; + + if (filename.empty()) { + desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("You have to enter a filename.")); + sp_ui_error_dialog(_("You have to enter a filename")); + return false; + } + + if (!extension || extension->is_raster()) { + desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Vector Export Error")); + sp_ui_error_dialog(_("Vector export Method is used for RASTER EXTENSION")); + return false; + } + + std::string path = absolutize_path_from_document_location(doc, Glib::filename_from_utf8(filename)); + Glib::ustring dirname = Glib::path_get_dirname(path); + + if (dirname.empty() || + !Inkscape::IO::file_test(dirname.c_str(), (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))) { + Glib::ustring safeDir = Inkscape::IO::sanitizeString(dirname.c_str()); + Glib::ustring error = + g_strdup_printf(_("Directory <b>%s</b> does not exist or is not a directory.\n"), safeDir.c_str()); + + desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, error.c_str()); + sp_ui_error_dialog(error.c_str()); + + return false; + } + + // Do the over-write protection now + if (!overwrite && !sp_ui_overwrite_file(path.c_str())) { + return false; + } + doc->ensureUpToDate(); + SPDocument *copy_doc = (doc->copy()).get(); + copy_doc->ensureUpToDate(); + + if (items && items->size() > 0) { + std::vector<SPObject *> objects_to_export; + std::vector<SPItem *> objects = *items; + Inkscape::ObjectSet s(copy_doc); + for (auto &object : objects) { + SPObject *temp = dynamic_cast<SPObject *>(object); + + if (!temp) { + Glib::ustring safeFile = Inkscape::IO::sanitizeString(path.c_str()); + Glib::ustring error = g_strdup_printf(_("Could not export to filename <b>%s</b>.\n"), safeFile.c_str()); + + desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, error.c_str()); + sp_ui_error_dialog(error.c_str()); + return false; + } + SPObject *obj = copy_doc->getObjectById(temp->getId()); + if (!obj) { + Glib::ustring safeFile = Inkscape::IO::sanitizeString(path.c_str()); + Glib::ustring error = g_strdup_printf(_("Could not export to filename <b>%s</b>.\n"), safeFile.c_str()); + + desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, error.c_str()); + sp_ui_error_dialog(error.c_str()); + + return false; + } + copy_doc->ensureUpToDate(); + + s.add(obj, true); + objects_to_export.push_back(obj); + } + copy_doc->getRoot()->cropToObjects(objects_to_export); + s.fitCanvas(true, true); + } + + copy_doc->vacuumDocument(); + try { + Inkscape::Extension::save(dynamic_cast<Inkscape::Extension::Extension *>(extension), copy_doc, path.c_str(), + false, false, Inkscape::Extension::FILE_SAVE_METHOD_SAVE_COPY); + } catch (Inkscape::Extension::Output::save_failed &e) { + Glib::ustring safeFile = Inkscape::IO::sanitizeString(path.c_str()); + Glib::ustring error = g_strdup_printf(_("Could not export to filename <b>%s</b>.\n"), safeFile.c_str()); + + desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, error.c_str()); + sp_ui_error_dialog(error.c_str()); + + return false; + } + + auto recentmanager = Gtk::RecentManager::get_default(); + if (recentmanager && Glib::path_is_absolute(path)) { + Glib::ustring uri = Glib::filename_to_uri(path); + recentmanager->add_item(uri); + } + + Glib::ustring safeFile = Inkscape::IO::sanitizeString(path.c_str()); + desktop->messageStack()->flashF(Inkscape::INFORMATION_MESSAGE, _("Drawing exported to <b>%s</b>."), + safeFile.c_str()); + return true; +} + +} // namespace Dialog +} // namespace UI +} // namespace Inkscape + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : \ No newline at end of file diff --git a/src/ui/dialog/export-helper.h b/src/ui/dialog/export-helper.h new file mode 100644 index 0000000000000000000000000000000000000000..6e03c7dd8f50ba40b7bfc3085226186b2767e941 --- /dev/null +++ b/src/ui/dialog/export-helper.h @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Authors: + * Anshudhar Kumar Singh <anshudhar2001@gmail.com> + * + * Copyright (C) 2021 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SP_EXPORT_HELPER_H +#define SP_EXPORT_HELPER_H + +#include <glibmm/convert.h> +#include <glibmm/i18n.h> +#include <glibmm/miscutils.h> +#include <gtkmm.h> +#include <png.h> + +#include "desktop.h" +#include "document-undo.h" +#include "document.h" +#include "extension/db.h" +#include "extension/output.h" +#include "file.h" +#include "helper/png-write.h" +#include "inkscape-window.h" +#include "inkscape.h" +#include "io/resource.h" +#include "io/sys.h" +#include "message-stack.h" +#include "object/object-set.h" +#include "object/sp-namedview.h" +#include "object/sp-root.h" +#include "preferences.h" +#include "selection-chemistry.h" +#include "ui/dialog-events.h" +#include "ui/dialog/dialog-notebook.h" +#include "ui/dialog/filedialog.h" +#include "ui/interface.h" +#include "ui/widget/scrollprotected.h" +#include "ui/widget/unit-menu.h" + +using Inkscape::Util::unit_table; + +namespace Inkscape { +namespace UI { +namespace Dialog { + +#define EXPORT_COORD_PRECISION 3 +#define SP_EXPORT_MIN_SIZE 1.0 +#define DPI_BASE Inkscape::Util::Quantity::convert(1, "in", "px") + +// Class for storing and manipulating advance options. +class AdvanceOptions : public Gtk::Expander +{ +public: + AdvanceOptions(); + ~AdvanceOptions() override; + +private: + Gtk::CheckButton interlacing; + + std::vector<std::pair<Glib::ustring, std::pair<int, int>>> bit_depth_list; + Inkscape::UI::Widget::ScrollProtected<Gtk::ComboBoxText> bit_depth_cb; + + std::vector<std::pair<Glib::ustring, int>> compression_list; + Inkscape::UI::Widget::ScrollProtected<Gtk::ComboBoxText> compression_cb; + + Inkscape::UI::Widget::ScrollProtected<Gtk::SpinButton> pHYs_sb; + Gtk::SpinButton a; + + std::vector<std::pair<Glib::ustring, int>> anti_aliasing_list; + Inkscape::UI::Widget::ScrollProtected<Gtk::ComboBoxText> anti_aliasing_cb; + +private: + int row; + +public: + int get_color() { return bit_depth_list[bit_depth_cb.get_active_row_number()].second.second; } + int get_bit_depth() { return bit_depth_list[bit_depth_cb.get_active_row_number()].second.first; } + int get_compression() { return compression_list[compression_cb.get_active_row_number()].second; } + int get_anti_aliasing() { return anti_aliasing_list[anti_aliasing_cb.get_active_row_number()].second; } + bool get_interlacing() { return interlacing.get_active(); } + double get_pHYs() { return pHYs_sb.get_value(); } +}; + +// Class for storing and manipulating extensions +class ExtensionList : public Inkscape::UI::Widget::ScrollProtected<Gtk::ComboBoxText> +{ +public: + ExtensionList(){}; + ExtensionList(BaseObjectType *cobject, const Glib::RefPtr<Gtk::Builder> &refGlade) + : Inkscape::UI::Widget::ScrollProtected<Gtk::ComboBoxText>(cobject, refGlade){}; + ~ExtensionList() override; + +public: + void setup(); + void setExtensionFromFilename(Glib::ustring const &filename); + void appendExtensionToFilename(Glib::ustring &filename); + static void createList(); + static bool list_created; + static void appendExtensionToFilename(Glib::ustring &filename, Glib::ustring &extension); + +public: + static std::map<Glib::ustring, Inkscape::Extension::Output *> valid_extensions; + static std::map<Glib::ustring, Inkscape::Extension::Output *> all_extensions; +}; + +class ExportProgressDialog : public Gtk::Dialog +{ +private: + Gtk::ProgressBar *_progress = nullptr; + Gtk::Widget *_export_panel = nullptr; + int _current = 0; + int _total = 0; + bool _stopped = false; + +public: + ExportProgressDialog(const Glib::ustring &title, bool modal = false) + : Gtk::Dialog(title, modal) + {} + + inline void set_export_panel(const decltype(_export_panel) export_panel) { _export_panel = export_panel; } + inline decltype(_export_panel) get_export_panel() const { return _export_panel; } + + inline void set_progress(const decltype(_progress) progress) { _progress = progress; } + inline decltype(_progress) get_progress() const { return _progress; } + + inline void set_current(const int current) { _current = current; } + inline int get_current() const { return _current; } + + inline void set_total(const int total) { _total = total; } + inline int get_total() const { return _total; } + + inline bool get_stopped() const { return _stopped; } + inline void set_stopped() { _stopped = true; } +}; + +class ExportList : public Gtk::Grid +{ +public: + ExportList(){}; + ExportList(BaseObjectType *cobject, const Glib::RefPtr<Gtk::Builder> &refGlade) + : Gtk::Grid(cobject){}; + ~ExportList() override; + +public: + void setup(); + void append_row(); + void delete_row(Gtk::Widget *widget); + Glib::ustring get_suffix(int row); + Glib::ustring get_extension(int row); + double get_dpi(int row); + int get_rows() { return _num_rows; } + +private: + typedef Inkscape::UI::Widget::ScrollProtected<Gtk::SpinButton> SpinButton; + Inkscape::Preferences *prefs = nullptr; + double default_dpi = 96.00; + +private: + bool _initialised = false; + int _num_rows = 0; + int _suffix_col = 0; + int _extension_col = 1; + int _dpi_col = 2; + int _delete_col = 3; +}; + +float getValuePx(float value, Unit const *unit); +void setValuePx(Glib::RefPtr<Gtk::Adjustment> &adj, double val, Unit const *unit); +Glib::ustring get_default_filename(Glib::ustring &filename_entry_text, Glib::ustring &extension, SPDocument *doc); +std::string create_filepath_from_id(Glib::ustring id, const Glib::ustring &file_entry_text); +Glib::ustring get_ext_from_filename(Glib::ustring const &filename); +std::string absolutize_path_from_document_location(SPDocument *doc, const std::string &filename); + +bool _export_raster(Geom::Rect const &area, unsigned long int const &width, unsigned long int const &height, + float const &dpi, Glib::ustring const &filename, bool overwrite, + unsigned (*callback)(float, void *), ExportProgressDialog *&prog_dialog, + Inkscape::Extension::Output *extension, std::vector<SPItem *> *items = nullptr, + AdvanceOptions *adv = nullptr); + +bool _export_vector(Inkscape::Extension::Output *extension, SPDocument *doc, Glib::ustring const &filename, + bool overwrite, std::vector<SPItem *> *items = nullptr); + +} // namespace Dialog +} // namespace UI +} // namespace Inkscape +#endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : \ No newline at end of file diff --git a/src/ui/dialog/export-preview.cpp b/src/ui/dialog/export-preview.cpp new file mode 100644 index 0000000000000000000000000000000000000000..425623649842aa57611b153280fb3f7b16b4741b --- /dev/null +++ b/src/ui/dialog/export-preview.cpp @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Authors: + * Anshudhar Kumar Singh <anshudhar2001@gmail.com> + * + * Copyright (C) 2021 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "export-preview.h" + +#include <glibmm/i18n.h> +#include <glibmm/main.h> +#include <glibmm/timer.h> +#include <gtkmm.h> + +#include "display/cairo-utils.h" +#include "inkscape.h" +#include "object/sp-defs.h" +#include "object/sp-item.h" +#include "object/sp-namedview.h" +#include "object/sp-root.h" +#include "preview-util.h" + +namespace Inkscape { +namespace UI { +namespace Dialog { + +ExportPreview::ExportPreview() + : drawing(nullptr) + , visionkey(0) + , timer(nullptr) + , renderTimer(nullptr) + , pending(false) + , minDelay(0.1) + , size(128) +{ + pixMem = nullptr; + image = nullptr; + int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, size); + pixMem = new guchar[size * stride]; + memset(pixMem, 0x00, size * stride); + + auto pb = Gdk::Pixbuf::create_from_data(pixMem, Gdk::COLORSPACE_RGB, true, 8, size, size, stride); + image = Gtk::manage(new Gtk::Image(pb)); + image->show(); + image->set_name("export_preview_image"); + // add this image to box here + this->pack_start(*image, true, true, 0); + show_all_children(); + this->set_name("export_preview_box"); + this->set_can_focus(false); + image->set_can_focus(false); +} + +void ExportPreview::resetPixels() +{ + if (pixMem) { + delete pixMem; + pixMem = nullptr; + } + int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, size); + pixMem = new guchar[size * stride]; + auto pb = Gdk::Pixbuf::create_from_data(pixMem, Gdk::COLORSPACE_RGB, true, 8, size, size, stride); + memset(pixMem, 0x00, size * stride); + image->set(pb); + image->show(); +} + +ExportPreview::~ExportPreview() +{ + if (drawing) { + if (_document) { + _document->getRoot()->invoke_hide(visionkey); + } + delete drawing; + drawing = nullptr; + } + if (timer) { + timer->stop(); + delete timer; + timer = nullptr; + } + if (renderTimer) { + renderTimer->stop(); + delete renderTimer; + renderTimer = nullptr; + } + _item = nullptr; + _document = nullptr; +} + +void ExportPreview::setItem(SPItem *item) +{ + _item = item; +} +void ExportPreview::setDbox(double x0, double x1, double y0, double y1) +{ + if (!_document) { + return; + } + if ((x1 - x0 == 0) || (y1 - y0) == 0) { + return; + } + _dbox = Geom::Rect(Geom::Point(x0, y0), Geom::Point(x1, y1)) * _document->dt2doc(); +} + +void ExportPreview::setDocument(SPDocument *document) +{ + if (drawing) { + if (_document) { + _document->getRoot()->invoke_hide(visionkey); + } + delete drawing; + drawing = nullptr; + } + _document = document; + if (_document) { + drawing = new Inkscape::Drawing(); + visionkey = SPItem::display_key_new(1); + DrawingItem *ai = _document->getRoot()->invoke_show(*drawing, visionkey, SP_ITEM_SHOW_DISPLAY); + if (ai) { + drawing->setRoot(ai); + } + } +} + +void ExportPreview::refreshHide(const std::vector<SPItem *> *list) +{ + if (_document) { + if (isLastHide) { + if (drawing) { + if (_document) { + _document->getRoot()->invoke_hide(visionkey); + } + delete drawing; + drawing = nullptr; + } + drawing = new Inkscape::Drawing(); + visionkey = SPItem::display_key_new(1); + DrawingItem *ai = _document->getRoot()->invoke_show(*drawing, visionkey, SP_ITEM_SHOW_DISPLAY); + if (ai) { + drawing->setRoot(ai); + } + isLastHide = false; + } + if (list && !list->empty()) { + hide_other_items_recursively(_document->getRoot(), *list); + isLastHide = true; + } + } +} + +void ExportPreview::hide_other_items_recursively(SPObject *o, const std::vector<SPItem *> &list) +{ + if (SP_IS_ITEM(o) && !SP_IS_DEFS(o) && !SP_IS_ROOT(o) && !SP_IS_GROUP(o) && + list.end() == find(list.begin(), list.end(), o)) { + SP_ITEM(o)->invoke_hide(visionkey); + } + + // recurse + if (list.end() == find(list.begin(), list.end(), o)) { + for (auto &child : o->children) { + hide_other_items_recursively(&child, list); + } + } +} + +void ExportPreview::queueRefresh() +{ + if (drawing == nullptr) { + return; + } + if (!pending) { + pending = true; + if (!timer) { + timer = new Glib::Timer(); + } + Glib::signal_idle().connect(sigc::mem_fun(this, &ExportPreview::refreshCB), Glib::PRIORITY_DEFAULT_IDLE); + } +} + +bool ExportPreview::refreshCB() +{ + bool callAgain = true; + if (!timer) { + timer = new Glib::Timer(); + } + if (timer->elapsed() > minDelay) { + callAgain = false; + refreshPreview(); + pending = false; + } + return callAgain; +} + +void ExportPreview::refreshPreview() +{ + auto document = _document; + if (!timer) { + timer = new Glib::Timer(); + } + if (timer->elapsed() < minDelay) { + // Do not refresh too quickly + queueRefresh(); + } else if (document) { + renderPreview(); + timer->reset(); + } +} + +/* +This is main function which finally render preview. Call this after setting document, item and dbox. +If dbox is given it will use it. +if item is given and not dbox then item is used +If both are not given then simply we do nothing. +*/ +void ExportPreview::renderPreview() +{ + if (!renderTimer) { + renderTimer = new Glib::Timer(); + } + renderTimer->reset(); + if (drawing == nullptr) { + return; + } + + if (_document) { + unsigned unused; + int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, size); + guchar *px = nullptr; + + if (_dbox) { + px = Inkscape::UI::PREVIEW::sp_icon_doc_icon(_document, *drawing, nullptr, size, unused, &_dbox); + } else if (_item) { + gchar const *id = _item->getId(); + px = Inkscape::UI::PREVIEW::sp_icon_doc_icon(_document, *drawing, id, size, unused); + } + + if (px) { + memcpy(pixMem, px, size * stride); + g_free(px); + px = nullptr; + } else { + memset(pixMem, 0, size * stride); + } + image->set(image->get_pixbuf()); + image->show(); + } + + renderTimer->stop(); + minDelay = std::max(0.1, renderTimer->elapsed() * 3.0); +} + +} // namespace Dialog +} // namespace UI +} // namespace Inkscape + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : \ No newline at end of file diff --git a/src/ui/dialog/export-preview.h b/src/ui/dialog/export-preview.h new file mode 100644 index 0000000000000000000000000000000000000000..e97c74d5679fd0c2b118c32d91085aef0431287d --- /dev/null +++ b/src/ui/dialog/export-preview.h @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Authors: + * Anshudhar Kumar Singh <anshudhar2001@gmail.com> + * + * Copyright (C) 2021 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SP_EXPORT_PREVIEW_H +#define SP_EXPORT_PREVIEW_H + +#include <gtkmm.h> + +#include "desktop.h" +#include "document.h" + +class SPObject; +namespace Glib { +class Timer; +} + +namespace Inkscape { +class Drawing; +namespace UI { +namespace Dialog { + +class ExportPreview : public Gtk::Box +{ +public: + ExportPreview(); + ~ExportPreview() override; + +private: + bool isLastHide = false; + +private: + SPItem *_item = nullptr; + + Geom::OptRect _dbox; + + SPDocument *_document = nullptr; + + Drawing *drawing; + unsigned int visionkey; + Glib::Timer *timer; + Glib::Timer *renderTimer; + bool pending; + gdouble minDelay; + + int size; // size of preview image + + guchar *pixMem; // Our Itme pixels + Gtk::Image *image; // Our Item Image + +public: + void setDocument(SPDocument *document); + void refreshHide(const std::vector<SPItem *> *list); + void hide_other_items_recursively(SPObject *o, const std::vector<SPItem *> &list); + void setItem(SPItem *item); + void setDbox(double x0, double x1, double y0, double y1); + void queueRefresh(); + void resetPixels(); + + void setSize(int newSize) + { + size = newSize; + resetPixels(); + } + +private: + void refreshPreview(); + void renderPreview(); + bool refreshCB(); +}; +} // namespace Dialog +} // namespace UI +} // namespace Inkscape +#endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : \ No newline at end of file diff --git a/src/ui/dialog/export-single.cpp b/src/ui/dialog/export-single.cpp new file mode 100644 index 0000000000000000000000000000000000000000..757560e71d56cf61fc9b21b81fa680961f1156ce --- /dev/null +++ b/src/ui/dialog/export-single.cpp @@ -0,0 +1,957 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Authors: + * Lauris Kaplinski <lauris@kaplinski.com> + * bulia byak <buliabyak@users.sf.net> + * Johan Engelen <j.b.c.engelen@ewi.utwente.nl> + * Anshudhar Kumar Singh <anshudhar2001@gmail.com> + * + * Copyright (C) 1999-2007, 2021 Authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "export-single.h" + +#include <glibmm/convert.h> +#include <glibmm/i18n.h> +#include <glibmm/miscutils.h> +#include <gtkmm.h> +#include <png.h> + +#include "desktop.h" +#include "document-undo.h" +#include "document.h" +#include "export-helper.h" +#include "extension/db.h" +#include "file.h" +#include "helper/png-write.h" +#include "inkscape-window.h" +#include "inkscape.h" +#include "io/resource.h" +#include "io/sys.h" +#include "message-stack.h" +#include "object/object-set.h" +#include "object/sp-namedview.h" +#include "object/sp-root.h" +#include "preferences.h" +#include "selection-chemistry.h" +#include "ui/dialog-events.h" +#include "ui/dialog/dialog-notebook.h" +#include "ui/dialog/filedialog.h" +#include "ui/interface.h" +#include "ui/widget/scrollprotected.h" +#include "ui/widget/unit-menu.h" +#ifdef _WIN32 + +#endif + +using Inkscape::Util::unit_table; + +namespace Inkscape { +namespace UI { +namespace Dialog { + +SingleExport::~SingleExport() +{ + ; +} + +/** + * Initialise Builder Objects. Called in Export constructor. + */ +void SingleExport::initialise(const Glib::RefPtr<Gtk::Builder> &builder) +{ + builder->get_widget("si_s_document", selection_buttons[SELECTION_DRAWING]); + selection_names[SELECTION_DRAWING] = "drawing"; + builder->get_widget("si_s_page", selection_buttons[SELECTION_PAGE]); + selection_names[SELECTION_PAGE] = "page"; + builder->get_widget("si_s_selection", selection_buttons[SELECTION_SELECTION]); + selection_names[SELECTION_SELECTION] = "selection"; + builder->get_widget("si_s_custom", selection_buttons[SELECTION_CUSTOM]); + selection_names[SELECTION_CUSTOM] = "custom"; + + builder->get_widget_derived("si_left_sb", spin_buttons[SPIN_X0]); + builder->get_widget_derived("si_right_sb", spin_buttons[SPIN_X1]); + builder->get_widget_derived("si_top_sb", spin_buttons[SPIN_Y0]); + builder->get_widget_derived("si_bottom_sb", spin_buttons[SPIN_Y1]); + builder->get_widget_derived("si_height_sb", spin_buttons[SPIN_HEIGHT]); + builder->get_widget_derived("si_width_sb", spin_buttons[SPIN_WIDTH]); + + builder->get_widget("si_label_left", spin_labels[SPIN_X0]); + builder->get_widget("si_label_right", spin_labels[SPIN_X1]); + builder->get_widget("si_label_top", spin_labels[SPIN_Y0]); + builder->get_widget("si_label_bottom", spin_labels[SPIN_Y1]); + builder->get_widget("si_label_height", spin_labels[SPIN_HEIGHT]); + builder->get_widget("si_label_width", spin_labels[SPIN_WIDTH]); + + builder->get_widget_derived("si_img_height_sb", spin_buttons[SPIN_BMHEIGHT]); + builder->get_widget_derived("si_img_width_sb", spin_buttons[SPIN_BMWIDTH]); + builder->get_widget_derived("si_dpi_sb", spin_buttons[SPIN_DPI]); + + // builder->get_widget("si_show_export_area", show_export_area); + builder->get_widget_derived("si_units", units); + builder->get_widget("si_units_row", si_units_row); + + builder->get_widget("si_hide_all", si_hide_all); + builder->get_widget("si_preview_box", si_preview_box); + builder->get_widget("si_show_preview", si_show_preview); + + builder->get_widget_derived("si_extention", si_extension_cb); + builder->get_widget("si_filename", si_filename_entry); + builder->get_widget("si_export", si_export); + + builder->get_widget("si_progress", _prog); + + builder->get_widget("si_advance_box", adv_box); + + Inkscape::UI::Widget::ScrollTransfer<Gtk::ScrolledWindow> *temp = nullptr; + builder->get_widget_derived("s_scroll", temp); +} + +// Inkscape Selection Modified CallBack +void SingleExport::selectionModified(Inkscape::Selection *selection, guint flags) +{ + if (!_desktop || _desktop->getSelection() != selection) { + return; + } + if (!(flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_PARENT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG))) { + return; + } + Geom::OptRect bbox; + SPDocument *doc = _desktop->getDocument(); + + if (!doc) { + return; + } + + // Will Remove this code after testing + + // switch (current_key) { + // case SELECTION_DRAWING: + // bbox = doc->getRoot()->desktopVisualBounds(); + // if (bbox) { + // setArea(bbox->left(), bbox->top(), bbox->right(), bbox->bottom()); + // } + // break; + // case SELECTION_SELECTION: + // if (selection->isEmpty() == false) { + // bbox = selection->visualBounds(); + // if (bbox) { + // setArea(bbox->left(), bbox->top(), bbox->right(), bbox->bottom()); + // } + // } + // break; + // default: + // /* Do nothing for page or for custom */ + // break; + // } + + refreshArea(); + refreshExportHints(); +} + +void SingleExport::selectionChanged(Inkscape::Selection *selection) +{ + if (!_desktop || _desktop->getSelection() != selection) { + return; + } + Glib::ustring pref_key_name = prefs->getString("/dialogs/export/exportarea/value"); + for (auto [key, name] : selection_names) { + if (name == pref_key_name && current_key != key && key != SELECTION_SELECTION) { + selection_buttons[key]->set_active(true); + current_key = key; + break; + } + } + if (selection->isEmpty()) { + selection_buttons[SELECTION_SELECTION]->set_sensitive(false); + if (current_key == SELECTION_SELECTION) { + selection_buttons[(selection_mode)0]->set_active(true); // This causes refresh area + // even though we are at default key, selection is the one which was original key. + prefs->setString("/dialogs/export/exportarea/value", selection_names[SELECTION_SELECTION]); + // return otherwise refreshArea will be called again + return; + } + } else { + selection_buttons[SELECTION_SELECTION]->set_sensitive(true); + if (selection_names[SELECTION_SELECTION] == pref_key_name && current_key != SELECTION_SELECTION) { + selection_buttons[SELECTION_SELECTION]->set_active(); + return; + } + } + + refreshArea(); + refreshExportHints(); +} + +// Setup Single Export.Called by export on realize +void SingleExport::setup() +{ + if (setupDone) { + // We need to setup only once + return; + } + setupDone = true; + prefs = Inkscape::Preferences::get(); + si_extension_cb->setup(); + + // Add advance options to adv box + adv_box->pack_start(advance_options, true, true, 0); + adv_box->show_all_children(); + + setupUnits(); + setupSpinButtons(); + + // set them before connecting to signals + setDefaultFilename(); + setDefaultSelectionMode(); + + // Refresh values to sync them with defaults. + refreshArea(); + refreshExportHints(); + + // Connect Signals Here + for (auto [key, button] : selection_buttons) { + button->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &SingleExport::onAreaTypeToggle), key)); + } + units->signal_changed().connect(sigc::mem_fun(*this, &SingleExport::onUnitChanged)); + filenameConn = si_filename_entry->signal_changed().connect(sigc::mem_fun(*this, &SingleExport::onFilenameModified)); + extensionConn = si_extension_cb->signal_changed().connect(sigc::mem_fun(*this, &SingleExport::onExtensionChanged)); + exportConn = si_export->signal_clicked().connect(sigc::mem_fun(*this, &SingleExport::onExport)); + browseConn = si_filename_entry->signal_icon_press().connect(sigc::mem_fun(*this, &SingleExport::onBrowse)); + si_show_preview->signal_toggled().connect(sigc::mem_fun(*this, &SingleExport::refreshPreview)); + si_hide_all->signal_toggled().connect(sigc::mem_fun(*this, &SingleExport::refreshPreview)); +} + +// Setup units combobox +void SingleExport::setupUnits() +{ + units->setUnitType(Inkscape::Util::UNIT_TYPE_LINEAR); + if (_desktop) { + units->setUnit(_desktop->getNamedView()->display_units->abbr); + } +} + +// Create all spin buttons +void SingleExport::setupSpinButtons() +{ + setupSpinButton<sb_type>(spin_buttons[SPIN_X0], 0.0, -1000000.0, 1000000.0, 0.1, 1.0, EXPORT_COORD_PRECISION, true, + &SingleExport::onAreaXChange, SPIN_X0); + setupSpinButton<sb_type>(spin_buttons[SPIN_X1], 0.0, -1000000.0, 1000000.0, 0.1, 1.0, EXPORT_COORD_PRECISION, true, + &SingleExport::onAreaXChange, SPIN_X1); + setupSpinButton<sb_type>(spin_buttons[SPIN_Y0], 0.0, -1000000.0, 1000000.0, 0.1, 1.0, EXPORT_COORD_PRECISION, true, + &SingleExport::onAreaYChange, SPIN_Y0); + setupSpinButton<sb_type>(spin_buttons[SPIN_Y1], 0.0, -1000000.0, 1000000.0, 0.1, 1.0, EXPORT_COORD_PRECISION, true, + &SingleExport::onAreaYChange, SPIN_Y1); + + setupSpinButton<sb_type>(spin_buttons[SPIN_HEIGHT], 0.0, 0.0, PNG_UINT_31_MAX, 0.1, 1.0, EXPORT_COORD_PRECISION, + true, &SingleExport::onAreaYChange, SPIN_HEIGHT); + setupSpinButton<sb_type>(spin_buttons[SPIN_WIDTH], 0.0, 0.0, PNG_UINT_31_MAX, 0.1, 1.0, EXPORT_COORD_PRECISION, + true, &SingleExport::onAreaXChange, SPIN_WIDTH); + + setupSpinButton<sb_type>(spin_buttons[SPIN_BMHEIGHT], 1.0, 1.0, 1000000.0, 1.0, 10.0, 0, true, + &SingleExport::onDpiChange, SPIN_BMHEIGHT); + setupSpinButton<sb_type>(spin_buttons[SPIN_BMWIDTH], 1.0, 1.0, 1000000.0, 1.0, 10.0, 0, true, + &SingleExport::onDpiChange, SPIN_BMWIDTH); + setupSpinButton<sb_type>(spin_buttons[SPIN_DPI], prefs->getDouble("/dialogs/export/defaultxdpi/value", DPI_BASE), + 1.0, 100000.0, 0.1, 1.0, 2, true, &SingleExport::onDpiChange, SPIN_DPI); +} + +template <typename T> +void SingleExport::setupSpinButton(Gtk::SpinButton *sb, double val, double min, double max, double step, double page, + int digits, bool sensitive, void (SingleExport::*cb)(T), T param) +{ + if (sb) { + sb->set_digits(digits); + sb->set_increments(step, page); + sb->set_range(min, max); + sb->set_value(val); + sb->set_sensitive(sensitive); + sb->set_width_chars(0); + sb->set_max_width_chars(0); + if (cb) { + auto signal = sb->signal_value_changed().connect(sigc::bind(sigc::mem_fun(*this, cb), param)); + // add signals to list to block all easily + spinButtonConns.push_back(signal); + } + } +} + +void SingleExport::refreshArea() +{ + if (_desktop) { + SPDocument *doc; + Geom::OptRect bbox; + doc = _desktop->getDocument(); + doc->ensureUpToDate(); + + switch (current_key) { + case SELECTION_SELECTION: + if ((_desktop->getSelection())->isEmpty() == false) { + bbox = _desktop->getSelection()->visualBounds(); + break; + } + case SELECTION_DRAWING: + bbox = doc->getRoot()->desktopVisualBounds(); + if (bbox) { + break; + } + case SELECTION_PAGE: + bbox = Geom::Rect(Geom::Point(0.0, 0.0), + Geom::Point(doc->getWidth().value("px"), doc->getHeight().value("px"))); + break; + case SELECTION_CUSTOM: + break; + default: + break; + } + if (current_key != SELECTION_CUSTOM && bbox) { + setArea(bbox->min()[Geom::X], bbox->min()[Geom::Y], bbox->max()[Geom::X], bbox->max()[Geom::Y]); + } + } + refreshPreview(); +} + +void SingleExport::refreshExportHints() +{ + if (_desktop && !filename_modified) { + SPDocument *doc = _desktop->getDocument(); + Glib::ustring filename; + float xdpi = 0.0, ydpi = 0.0; + switch (current_key) { + case SELECTION_CUSTOM: + case SELECTION_PAGE: + case SELECTION_DRAWING: + sp_document_get_export_hints(doc, filename, &xdpi, &ydpi); + if (filename.empty()) { + Glib::ustring filename_entry_text = si_filename_entry->get_text(); + Glib::ustring extension_entry_text = si_extension_cb->get_active_text(); + filename = get_default_filename(filename_entry_text, extension_entry_text, doc); + } + doc_export_name = filename; + break; + case SELECTION_SELECTION: + if ((_desktop->getSelection())->isEmpty()) { + break; + } + _desktop->getSelection()->getExportHints(filename, &xdpi, &ydpi); + + /* If we still don't have a filename -- let's build + one that's nice */ + if (filename.empty()) { + const gchar *id = "object"; + auto reprlst = _desktop->getSelection()->xmlNodes(); + for (auto i = reprlst.begin(); reprlst.end() != i; ++i) { + Inkscape::XML::Node *repr = *i; + if (repr->attribute("id")) { + id = repr->attribute("id"); + break; + } + } + filename = create_filepath_from_id(id, si_filename_entry->get_text()); + filename = filename + si_extension_cb->get_active_text(); + } + break; + default: + break; + } + if (!filename.empty()) { + original_name = filename; + si_filename_entry->set_text(filename); + si_filename_entry->set_position(filename.length()); + } else { + Glib::ustring newName = !doc_export_name.empty() ? doc_export_name : original_name; + if (!newName.empty()) { + si_filename_entry->set_text(filename); + si_filename_entry->set_position(filename.length()); + } + } + + if (xdpi != 0.0) { + spin_buttons[SPIN_DPI]->set_value(xdpi); + } + } +} + +void SingleExport::setArea(double x0, double y0, double x1, double y1) +{ + blockSpinConns(true); + + auto x0_adj = spin_buttons[SPIN_X0]->get_adjustment(); + auto x1_adj = spin_buttons[SPIN_X1]->get_adjustment(); + auto y0_adj = spin_buttons[SPIN_Y0]->get_adjustment(); + auto y1_adj = spin_buttons[SPIN_Y1]->get_adjustment(); + + Unit const *unit = units->getUnit(); + setValuePx(x1_adj, x1, unit); + setValuePx(y1_adj, y1, unit); + setValuePx(x0_adj, x0, unit); + setValuePx(y0_adj, y0, unit); + + areaXChange(SPIN_X1); + areaYChange(SPIN_Y1); + + blockSpinConns(false); +} + +// Signals CallBack + +void SingleExport::onUnitChanged() +{ + refreshArea(); +} + +void SingleExport::onAreaTypeToggle(selection_mode key) +{ + // Prevent executing function twice + if (!selection_buttons[key]->get_active()) { + return; + } + // If you have reached here means the current key is active one ( not sure if multiple transitions happen but + // last call will change values) + current_key = key; + prefs->setString("/dialogs/export/exportarea/value", selection_names[current_key]); + + refreshArea(); + refreshExportHints(); + toggleSpinButtonVisibility(); +} + +void SingleExport::toggleSpinButtonVisibility() +{ + bool show = current_key == SELECTION_CUSTOM; + spin_buttons[SPIN_X0]->set_visible(show); + spin_buttons[SPIN_X1]->set_visible(show); + spin_buttons[SPIN_Y0]->set_visible(show); + spin_buttons[SPIN_Y1]->set_visible(show); + spin_buttons[SPIN_WIDTH]->set_visible(show); + spin_buttons[SPIN_HEIGHT]->set_visible(show); + + spin_labels[SPIN_X0]->set_visible(show); + spin_labels[SPIN_X1]->set_visible(show); + spin_labels[SPIN_Y0]->set_visible(show); + spin_labels[SPIN_Y1]->set_visible(show); + spin_labels[SPIN_WIDTH]->set_visible(show); + spin_labels[SPIN_HEIGHT]->set_visible(show); + + si_units_row->set_visible(show); +} + +void SingleExport::onAreaXChange(sb_type type) +{ + blockSpinConns(true); + areaXChange(type); + selection_buttons[SELECTION_CUSTOM]->set_active(true); + refreshPreview(); + blockSpinConns(false); +} +void SingleExport::onAreaYChange(sb_type type) +{ + blockSpinConns(true); + areaYChange(type); + selection_buttons[SELECTION_CUSTOM]->set_active(true); + refreshPreview(); + blockSpinConns(false); +} +void SingleExport::onDpiChange(sb_type type) +{ + blockSpinConns(true); + dpiChange(type); + blockSpinConns(false); +} + +void SingleExport::onFilenameModified() +{ + extensionConn.block(); + Glib::ustring filename = si_filename_entry->get_text(); + + if (original_name == filename) { + filename_modified = false; + } else { + filename_modified = true; + } + + si_extension_cb->setExtensionFromFilename(filename); + + extensionConn.unblock(); +} + +void SingleExport::onExtensionChanged() +{ + filenameConn.block(); + Glib::ustring filename = si_filename_entry->get_text(); + si_extension_cb->appendExtensionToFilename(filename); + si_filename_entry->set_text(filename); + si_filename_entry->set_position(filename.length()); + filenameConn.unblock(); +} + +void SingleExport::onExport() +{ + interrupted = false; + if (!_desktop) + return; + si_export->set_sensitive(false); + bool exportSuccessful = false; + auto extension = si_extension_cb->get_active_text(); + auto omod = ExtensionList::valid_extensions[extension]; + if (!omod) { + si_export->set_sensitive(true); + return; + } + + Unit const *unit = units->getUnit(); + + Glib::ustring filename = si_filename_entry->get_text(); + + if (omod->is_raster()) { + float x0 = getValuePx(spin_buttons[SPIN_X0]->get_value(), unit); + float x1 = getValuePx(spin_buttons[SPIN_X1]->get_value(), unit); + float y0 = getValuePx(spin_buttons[SPIN_Y0]->get_value(), unit); + float y1 = getValuePx(spin_buttons[SPIN_Y1]->get_value(), unit); + auto area = Geom::Rect(Geom::Point(x0, y0), Geom::Point(x1, y1)) * _desktop->dt2doc(); + + unsigned long int width = int(spin_buttons[SPIN_BMWIDTH]->get_value() + 0.5); + unsigned long int height = int(spin_buttons[SPIN_BMHEIGHT]->get_value() + 0.5); + + float dpi = spin_buttons[SPIN_DPI]->get_value(); + + /* TRANSLATORS: %1 will be the filename, %2 the width, and %3 the height of the image */ + prog_dlg = create_progress_dialog(Glib::ustring::compose(_("Exporting %1 (%2 x %3)"), filename, width, height)); + prog_dlg->set_export_panel(this); + setExporting(true, Glib::ustring::compose(_("Exporting %1 (%2 x %3)"), filename, width, height)); + prog_dlg->set_current(0); + prog_dlg->set_total(0); + + std::vector<SPItem *> selected(_desktop->getSelection()->items().begin(), + _desktop->getSelection()->items().end()); + bool hide = si_hide_all->get_active(); + + exportSuccessful = _export_raster(area, width, height, dpi, filename, false, onProgressCallback, prog_dlg, omod, + hide ? &selected : nullptr, &advance_options); + + } else { + setExporting(true, Glib::ustring::compose(_("Exporting %1"), filename)); + SPDocument *doc = _desktop->getDocument(); + SPDocument *copy_doc = (doc->copy()).get(); + if (current_key == SELECTION_DRAWING) { + fit_canvas_to_drawing(copy_doc, true); + } + std::vector<SPItem *> items; + if (current_key == SELECTION_SELECTION) { + auto itemlist = _desktop->getSelection()->items(); + for (auto i = itemlist.begin(); i != itemlist.end(); ++i) { + SPItem *item = *i; + items.push_back(item); + } + } + exportSuccessful = _export_vector(omod, copy_doc, filename, false, &items); + } + if (prog_dlg) { + delete prog_dlg; + prog_dlg = nullptr; + } + setExporting(false); + si_export->set_sensitive(true); + original_name = filename; + filename_modified = false; + interrupted = false; +} + +void SingleExport::onBrowse(Gtk::EntryIconPosition pos, const GdkEventButton *ev) +{ + if (!_app) { + return; + } + Gtk::Window *window = _app->get_active_window(); + browseConn.block(); + Glib::ustring filename = Glib::filename_from_utf8(si_filename_entry->get_text()); + + if (filename.empty()) { + Glib::ustring tmp; + filename = create_filepath_from_id(tmp, tmp); + } + + Inkscape::UI::Dialog::FileSaveDialog *dialog = Inkscape::UI::Dialog::FileSaveDialog::create( + *window, filename, Inkscape::UI::Dialog::RASTER_TYPES, _("Select a filename for exporting"), "", "", + Inkscape::Extension::FILE_SAVE_METHOD_EXPORT); + + if (dialog->show()) { + filename = dialog->getFilename(); + Inkscape::Extension::Output *selection_type = + dynamic_cast<Inkscape::Extension::Output *>(dialog->getSelectionType()); + Glib::ustring extension = selection_type->get_extension(); + ExtensionList::appendExtensionToFilename(filename, extension); + si_filename_entry->set_text(filename); + si_filename_entry->set_position(filename.length()); + // deleting dialog before exporting is important + // proper delete function should be made for dialog IMO + delete dialog; + onExport(); + } else { + delete dialog; + } + browseConn.unblock(); +} + +// Utils Functions + +void SingleExport::blockSpinConns(bool status = true) +{ + for (auto signal : spinButtonConns) { + if (status) { + signal.block(); + } else { + signal.unblock(); + } + } +} + +void SingleExport::areaXChange(sb_type type) +{ + auto x0_adj = spin_buttons[SPIN_X0]->get_adjustment(); + auto x1_adj = spin_buttons[SPIN_X1]->get_adjustment(); + auto width_adj = spin_buttons[SPIN_WIDTH]->get_adjustment(); + + float x0, x1, dpi, width, bmwidth; + + // Get all values in px + Unit const *unit = units->getUnit(); + x0 = getValuePx(x0_adj->get_value(), unit); + x1 = getValuePx(x1_adj->get_value(), unit); + width = getValuePx(width_adj->get_value(), unit); + bmwidth = spin_buttons[SPIN_BMWIDTH]->get_value(); + dpi = spin_buttons[SPIN_DPI]->get_value(); + + switch (type) { + case SPIN_X0: + bmwidth = (x1 - x0) * dpi / DPI_BASE; + if (bmwidth < SP_EXPORT_MIN_SIZE) { + x0 = x1 - (SP_EXPORT_MIN_SIZE * DPI_BASE) / dpi; + } + break; + case SPIN_X1: + bmwidth = (x1 - x0) * dpi / DPI_BASE; + if (bmwidth < SP_EXPORT_MIN_SIZE) { + x1 = x0 + (SP_EXPORT_MIN_SIZE * DPI_BASE) / dpi; + } + break; + case SPIN_WIDTH: + bmwidth = width * dpi / DPI_BASE; + if (bmwidth < SP_EXPORT_MIN_SIZE) { + width = (SP_EXPORT_MIN_SIZE * DPI_BASE) / dpi; + } + x1 = x0 + width; + break; + default: + break; + } + + width = x1 - x0; + bmwidth = floor(width * dpi / DPI_BASE + 0.5); + + setValuePx(x0_adj, x0, unit); + setValuePx(x1_adj, x1, unit); + setValuePx(width_adj, width, unit); + spin_buttons[SPIN_BMWIDTH]->set_value(bmwidth); +} + +void SingleExport::areaYChange(sb_type type) +{ + auto y0_adj = spin_buttons[SPIN_Y0]->get_adjustment(); + auto y1_adj = spin_buttons[SPIN_Y1]->get_adjustment(); + auto height_adj = spin_buttons[SPIN_HEIGHT]->get_adjustment(); + + float y0, y1, dpi, height, bmheight; + + // Get all values in px + Unit const *unit = units->getUnit(); + y0 = getValuePx(y0_adj->get_value(), unit); + y1 = getValuePx(y1_adj->get_value(), unit); + height = getValuePx(height_adj->get_value(), unit); + bmheight = spin_buttons[SPIN_BMHEIGHT]->get_value(); + dpi = spin_buttons[SPIN_DPI]->get_value(); + + switch (type) { + case SPIN_Y0: + bmheight = (y1 - y0) * dpi / DPI_BASE; + if (bmheight < SP_EXPORT_MIN_SIZE) { + y0 = y1 - (SP_EXPORT_MIN_SIZE * DPI_BASE) / dpi; + } + break; + case SPIN_Y1: + bmheight = (y1 - y0) * dpi / DPI_BASE; + if (bmheight < SP_EXPORT_MIN_SIZE) { + y1 = y0 + (SP_EXPORT_MIN_SIZE * DPI_BASE) / dpi; + } + break; + case SPIN_HEIGHT: + bmheight = height * dpi / DPI_BASE; + if (bmheight < SP_EXPORT_MIN_SIZE) { + height = (SP_EXPORT_MIN_SIZE * DPI_BASE) / dpi; + } + y1 = y0 + height; + break; + default: + break; + } + + height = y1 - y0; + bmheight = floor(height * dpi / DPI_BASE + 0.5); + + setValuePx(y0_adj, y0, unit); + setValuePx(y1_adj, y1, unit); + setValuePx(height_adj, height, unit); + spin_buttons[SPIN_BMHEIGHT]->set_value(bmheight); +} + +void SingleExport::dpiChange(sb_type type) +{ + float dpi, height, width, bmheight, bmwidth; + + // Get all values in px + Unit const *unit = units->getUnit(); + height = getValuePx(spin_buttons[SPIN_HEIGHT]->get_value(), unit); + width = getValuePx(spin_buttons[SPIN_WIDTH]->get_value(), unit); + bmheight = spin_buttons[SPIN_BMHEIGHT]->get_value(); + bmwidth = spin_buttons[SPIN_BMWIDTH]->get_value(); + dpi = spin_buttons[SPIN_DPI]->get_value(); + + switch (type) { + case SPIN_BMHEIGHT: + if (bmheight < SP_EXPORT_MIN_SIZE) { + bmheight = SP_EXPORT_MIN_SIZE; + } + dpi = bmheight * DPI_BASE / height; + break; + case SPIN_BMWIDTH: + if (bmwidth < SP_EXPORT_MIN_SIZE) { + bmwidth = SP_EXPORT_MIN_SIZE; + } + dpi = bmwidth * DPI_BASE / width; + break; + case SPIN_DPI: + prefs->setDouble("/dialogs/export/defaultdpi/value", dpi); + break; + default: + break; + } + + bmwidth = floor(width * dpi / DPI_BASE + 0.5); + bmheight = floor(height * dpi / DPI_BASE + 0.5); + + spin_buttons[SPIN_BMHEIGHT]->set_value(bmheight); + spin_buttons[SPIN_BMWIDTH]->set_value(bmwidth); + spin_buttons[SPIN_DPI]->set_value(dpi); +} + +// We first check any export hints related to document. If there is none we create a default name using document +// name. doc_export_name is set here and will only be changed when exporting. +void SingleExport::setDefaultFilename() +{ + if (!_desktop) { + return; + } + Glib::ustring filename; + float xdpi = 0.0, ydpi = 0.0; + SPDocument *doc = _desktop->getDocument(); + sp_document_get_export_hints(doc, filename, &xdpi, &ydpi); + if (filename.empty()) { + Glib::ustring filename_entry_text = si_filename_entry->get_text(); + Glib::ustring extention_entry_text = si_extension_cb->get_active_text(); + filename = get_default_filename(filename_entry_text, extention_entry_text, doc); + } + doc_export_name = filename; + original_name = filename; + si_filename_entry->set_text(filename); + si_filename_entry->set_position(filename.length()); + + si_extension_cb->setExtensionFromFilename(filename); + + // We only need to check xdpi + if (xdpi != 0.0) { + spin_buttons[SPIN_DPI]->set_value(xdpi); + } +} + +void SingleExport::setDefaultSelectionMode() +{ + current_key = (selection_mode)0; // default key + bool found = false; + Glib::ustring pref_key_name = prefs->getString("/dialogs/export/exportarea/value"); + for (auto [key, name] : selection_names) { + if (pref_key_name == name) { + current_key = key; + found = true; + break; + } + } + if (!found) { + pref_key_name = selection_names[current_key]; + } + + if (_desktop) { + if (current_key == SELECTION_SELECTION && (_desktop->getSelection())->isEmpty()) { + current_key = (selection_mode)0; + } + if ((_desktop->getSelection())->isEmpty()) { + selection_buttons[SELECTION_SELECTION]->set_sensitive(false); + } + if (current_key == SELECTION_CUSTOM && + (spin_buttons[SPIN_HEIGHT]->get_value() == 0 || spin_buttons[SPIN_WIDTH]->get_value() == 0)) { + SPDocument *doc; + Geom::OptRect bbox; + doc = _desktop->getDocument(); + bbox = Geom::Rect(Geom::Point(0.0, 0.0), + Geom::Point(doc->getWidth().value("px"), doc->getHeight().value("px"))); + setArea(bbox->min()[Geom::X], bbox->min()[Geom::Y], bbox->max()[Geom::X], bbox->max()[Geom::Y]); + } + } else { + current_key = (selection_mode)0; + } + selection_buttons[current_key]->set_active(true); + prefs->setString("/dialogs/export/exportarea/value", pref_key_name); + + toggleSpinButtonVisibility(); +} + +void SingleExport::setExporting(bool exporting, Glib::ustring const &text) +{ + if (exporting) { + _prog->set_text(text); + _prog->set_fraction(0.0); + _prog->set_sensitive(true); + si_export->set_sensitive(false); + } else { + _prog->set_text(""); + _prog->set_fraction(0.0); + _prog->set_sensitive(false); + si_export->set_sensitive(true); + } +} + +ExportProgressDialog *SingleExport::create_progress_dialog(Glib::ustring progress_text) +{ + // dont forget to delete it later + auto dlg = new ExportProgressDialog(_("Export in progress"), true); + dlg->set_transient_for(*(INKSCAPE.active_desktop()->getToplevel())); + + Gtk::ProgressBar *prg = Gtk::manage(new Gtk::ProgressBar()); + prg->set_text(progress_text); + dlg->set_progress(prg); + auto CA = dlg->get_content_area(); + CA->pack_start(*prg, FALSE, FALSE, 4); + + Gtk::Button *btn = dlg->add_button(_("_Cancel"), Gtk::RESPONSE_CANCEL); + + btn->signal_clicked().connect(sigc::mem_fun(*this, &SingleExport::onProgressCancel)); + dlg->signal_delete_event().connect(sigc::mem_fun(*this, &SingleExport::onProgressDelete)); + + dlg->show_all(); + return dlg; +} + +/// Called when dialog is deleted +bool SingleExport::onProgressDelete(GdkEventAny * /*event*/) +{ + interrupted = true; + prog_dlg->set_stopped(); + return TRUE; +} +/// Called when progress is cancelled +void SingleExport::onProgressCancel() +{ + interrupted = true; + prog_dlg->set_stopped(); +} + +// Called for every progress iteration +unsigned int SingleExport::onProgressCallback(float value, void *dlg) +{ + auto dlg2 = reinterpret_cast<ExportProgressDialog *>(dlg); + + auto self = dynamic_cast<SingleExport *>(dlg2->get_export_panel()); + + if (!self || self->interrupted) + return FALSE; + + auto current = dlg2->get_current(); + auto total = dlg2->get_total(); + if (total > 0) { + double completed = current; + completed /= static_cast<double>(total); + + value = completed + (value / static_cast<double>(total)); + } + + auto prg = dlg2->get_progress(); + prg->set_fraction(value); + + if (self) { + self->_prog->set_fraction(value); + } + + int evtcount = 0; + while ((evtcount < 16) && gdk_events_pending()) { + Gtk::Main::iteration(false); + evtcount += 1; + } + + Gtk::Main::iteration(false); + return TRUE; +} + +void SingleExport::refreshPreview() +{ + if (!_desktop) { + return; + } + if (!preview) { + preview = Gtk::manage(new ExportPreview()); + si_preview_box->pack_start(*preview, true, true, 0); + si_preview_box->show_all_children(); + } + if (!si_show_preview->get_active()) { + preview->resetPixels(); + return; + } + + std::vector<SPItem *> selected(_desktop->getSelection()->items().begin(), _desktop->getSelection()->items().end()); + bool hide = si_hide_all->get_active(); + + Unit const *unit = units->getUnit(); + float x0 = getValuePx(spin_buttons[SPIN_X0]->get_value(), unit); + float x1 = getValuePx(spin_buttons[SPIN_X1]->get_value(), unit); + float y0 = getValuePx(spin_buttons[SPIN_Y0]->get_value(), unit); + float y1 = getValuePx(spin_buttons[SPIN_Y1]->get_value(), unit); + preview->setItem(nullptr); + preview->setDbox(x0, x1, y0, y1); + preview->refreshHide(hide ? &selected : nullptr); + preview->queueRefresh(); +} + +void SingleExport::setDocument(SPDocument *document) +{ + if (!preview) { + preview = Gtk::manage(new ExportPreview()); + si_preview_box->pack_start(*preview, true, true, 0); + si_preview_box->show_all_children(); + } + preview->setDocument(document); +} + +} // namespace Dialog +} // namespace UI +} // namespace Inkscape + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : \ No newline at end of file diff --git a/src/ui/dialog/export-single.h b/src/ui/dialog/export-single.h new file mode 100644 index 0000000000000000000000000000000000000000..b19966e60bcc1b2699d3a56a0db6f7f9940fa3eb --- /dev/null +++ b/src/ui/dialog/export-single.h @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* Authors: + * Lauris Kaplinski <lauris@kaplinski.com> + * bulia byak <buliabyak@users.sf.net> + * Johan Engelen <j.b.c.engelen@ewi.utwente.nl> + * Anshudhar Kumar Singh <anshudhar2001@gmail.com> + * + * Copyright (C) 1999-2007, 2021 Authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SP_EXPORT_SINGLE_H +#define SP_EXPORT_SINGLE_H + +#include <gtkmm.h> + +#include "export-helper.h" +#include "export-preview.h" +#include "extension/output.h" +#include "ui/widget/scrollprotected.h" +#include "ui/widget/unit-menu.h" + +namespace Inkscape { +namespace UI { +namespace Dialog { + +class SingleExport : public Gtk::Box +{ +public: + SingleExport(){}; + SingleExport(BaseObjectType *cobject, const Glib::RefPtr<Gtk::Builder> &refGlade) + : Gtk::Box(cobject){}; + ~SingleExport() override; + +private: + InkscapeApplication *_app = nullptr; + SPDesktop *_desktop = nullptr; + +private: + bool setupDone = false; // To prevent setup() call add connections again. + +public: + void setApp(InkscapeApplication *app) { _app = app; } + void setDocument(SPDocument *document); + void setDesktop(SPDesktop *desktop) { _desktop = desktop; } + void selectionChanged(Inkscape::Selection *selection); + void selectionModified(Inkscape::Selection *selection, guint flags); + +private: + enum sb_type + { + SPIN_X0 = 0, + SPIN_X1, + SPIN_Y0, + SPIN_Y1, + SPIN_WIDTH, + SPIN_HEIGHT, + SPIN_BMWIDTH, + SPIN_BMHEIGHT, + SPIN_DPI + }; + + enum selection_mode + { + SELECTION_PAGE = 0, // Default is alaways placed first + SELECTION_SELECTION, + SELECTION_DRAWING, + SELECTION_CUSTOM, + }; + +private: + typedef Inkscape::UI::Widget::ScrollProtected<Gtk::SpinButton> SpinButton; + + std::map<sb_type, SpinButton *> spin_buttons; + std::map<sb_type, Gtk::Label *> spin_labels; + std::map<selection_mode, Gtk::RadioButton *> selection_buttons; + + Gtk::Box *si_units_row = nullptr; + Gtk::CheckButton *show_export_area = nullptr; + Inkscape::UI::Widget::UnitMenu *units = nullptr; + + Gtk::CheckButton *si_hide_all = nullptr; + + Gtk::Box *si_preview_box = nullptr; + Gtk::CheckButton *si_show_preview = nullptr; + ExportPreview *preview = nullptr; + + ExtensionList *si_extension_cb = nullptr; + Gtk::Entry *si_filename_entry = nullptr; + Gtk::Button *si_export = nullptr; + Gtk::Box *adv_box = nullptr; + Gtk::ProgressBar *_prog = nullptr; + + AdvanceOptions advance_options; + +private: + bool filename_modified; + Glib::ustring original_name; + Glib::ustring doc_export_name; + + Inkscape::Preferences *prefs = nullptr; + std::map<selection_mode, Glib::ustring> selection_names; + selection_mode current_key = (selection_mode)0; + +public: + // initialise variables from builder + void initialise(const Glib::RefPtr<Gtk::Builder> &builder); + void setup(); + +private: + void setupUnits(); + void setupExtensionList(); + void setupSpinButtons(); + void toggleSpinButtonVisibility(); + +private: + void refreshPreview(); + +private: + // change range and callbacks to spinbuttons + template <typename T> + void setupSpinButton(Gtk::SpinButton *sb, double val, double min, double max, double step, double page, int digits, + bool sensitive, void (SingleExport::*cb)(T), T param); + +private: + void setDefaultSelectionMode(); + void setDefaultFilename(); + +private: + void onAreaXChange(sb_type type); + void onAreaYChange(sb_type type); + void onDpiChange(sb_type type); + void onAreaTypeToggle(selection_mode key); + void onUnitChanged(); + void onFilenameModified(); + void onExtensionChanged(); + void onExport(); + void onBrowse(Gtk::EntryIconPosition pos, const GdkEventButton *ev); + void on_inkscape_selection_modified(Inkscape::Selection *selection, guint flags); + void on_inkscape_selection_changed(Inkscape::Selection *selection); + +public: + void refresh() + { + refreshArea(); + refreshExportHints(); + }; + +private: + void refreshArea(); + void refreshExportHints(); + void areaXChange(sb_type type); + void areaYChange(sb_type type); + void dpiChange(sb_type type); + void setArea(double x0, double y0, double x1, double y1); + void blockSpinConns(bool status); + +private: + void setExporting(bool exporting, Glib::ustring const &text = ""); + ExportProgressDialog *create_progress_dialog(Glib::ustring progress_text); + /** + * Callback to be used in for loop to update the progress bar. + * + * @param value number between 0 and 1 indicating the fraction of progress (0.17 = 17 % progress) + * @param dlg void pointer to the Gtk::Dialog progress dialog + */ + static unsigned int onProgressCallback(float value, void *dlg); + + /** + * Callback for pressing the cancel button. + */ + void onProgressCancel(); + + /** + * Callback invoked on closing the progress dialog. + */ + bool onProgressDelete(GdkEventAny *event); + +private: + ExportProgressDialog *prog_dlg = nullptr; + bool interrupted; + +private: + // Signals + std::vector<sigc::connection> spinButtonConns; + sigc::connection filenameConn; + sigc::connection extensionConn; + sigc::connection exportConn; + sigc::connection browseConn; +}; +} // namespace Dialog +} // namespace UI +} // namespace Inkscape +#endif + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : \ No newline at end of file diff --git a/src/ui/dialog/export.cpp b/src/ui/dialog/export.cpp index 2fefcdd94b711f148c6e2d96e5996638fa39fa07..68e3ae17a50e7370d4b7c0b69d6307296664ea20 100644 --- a/src/ui/dialog/export.cpp +++ b/src/ui/dialog/export.cpp @@ -7,1850 +7,183 @@ * Jon A. Cruz <jon@joncruz.org> * Abhishek Sharma * Kris De Gussem <Kris.DeGussem@gmail.com> + * Anshudhar Kumar Singh <anshudhar2001@gmail.com> * - * Copyright (C) 1999-2007, 2012 Authors + * Copyright (C) 1999-2007, 2012, 2021 Authors * Copyright (C) 2001-2002 Ximian, Inc. * * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ // This has to be included prior to anything that includes setjmp.h, it croaks otherwise -#include <png.h> - -#include <gtkmm/box.h> -#include <gtkmm/buttonbox.h> -#include <gtkmm/dialog.h> -#include <gtkmm/entry.h> -#include <gtkmm/filechooserdialog.h> -#include <gtkmm/grid.h> -#include <gtkmm/main.h> -#include <gtkmm/spinbutton.h> +#include "export.h" #include <glibmm/convert.h> -#include <glibmm/miscutils.h> #include <glibmm/i18n.h> +#include <glibmm/miscutils.h> +#include <gtkmm.h> +#include <png.h> +#include "desktop.h" #include "document-undo.h" #include "document.h" +#include "extension/db.h" #include "file.h" -#include "inkscape.h" -#include "inkscape-window.h" -#include "preferences.h" -#include "selection-chemistry.h" - -// required to set status message after export -#include "desktop.h" -#include "message-stack.h" - #include "helper/png-write.h" - +#include "inkscape-window.h" +#include "inkscape.h" #include "io/resource.h" #include "io/sys.h" - +#include "message-stack.h" +#include "object/object-set.h" #include "object/sp-namedview.h" #include "object/sp-root.h" - +#include "preferences.h" +#include "selection-chemistry.h" #include "ui/dialog-events.h" -#include "ui/interface.h" -#include "ui/widget/unit-menu.h" -#include "ui/widget/scrollprotected.h" #include "ui/dialog/dialog-notebook.h" #include "ui/dialog/filedialog.h" - -#include "extension/db.h" +#include "ui/interface.h" +#include "ui/widget/scrollprotected.h" +#include "ui/widget/unit-menu.h" #ifdef _WIN32 -#include <windows.h> -#include <commdlg.h> -#include <gdk/gdkwin32.h> -#include <glibmm/fileutils.h> -#endif - -#define SP_EXPORT_MIN_SIZE 1.0 - -#define DPI_BASE Inkscape::Util::Quantity::convert(1, "in", "px") - -#define EXPORT_COORD_PRECISION 3 -#include "export.h" +#endif using Inkscape::Util::unit_table; - -namespace { - -class MessageCleaner -{ -public: - MessageCleaner(Inkscape::MessageId messageId, SPDesktop *desktop) : - _desktop(desktop), - _messageId(messageId) - { - } - - ~MessageCleaner() - { - if (_messageId && _desktop) { - _desktop->messageStack()->cancel(_messageId); - } - } - -private: - MessageCleaner(MessageCleaner const &other) = delete; - MessageCleaner &operator=(MessageCleaner const &other) = delete; - - SPDesktop *_desktop; - Inkscape::MessageId _messageId; -}; - -} // namespace - namespace Inkscape { namespace UI { namespace Dialog { -class ExportProgressDialog : public Gtk::Dialog { - private: - Gtk::ProgressBar *_progress = nullptr; - Export *_export_panel = nullptr; - int _current = 0; - int _total = 0; - - public: - ExportProgressDialog(const Glib::ustring &title, bool modal = false) - : Gtk::Dialog(title, modal) - {} - - inline void set_export_panel(const decltype(_export_panel) export_panel) { _export_panel = export_panel; } - inline decltype(_export_panel) get_export_panel() const { return _export_panel; } - - inline void set_progress(const decltype(_progress) progress) { _progress = progress; } - inline decltype(_progress) get_progress() const { return _progress; } - - inline void set_current(const int current) { _current = current; } - inline int get_current() const { return _current; } - - inline void set_total(const int total) { _total = total; } - inline int get_total() const { return _total; } -}; - -/** A list of strings that is used both in the preferences, and in the - data fields to describe the various values of \c selection_type. */ -static const char * selection_names[SELECTION_NUMBER_OF] = { - "page", "drawing", "selection", "custom" -}; - -/** The names on the buttons for the various selection types. */ -static const char * selection_labels[SELECTION_NUMBER_OF] = { - N_("_Page"), N_("_Drawing"), N_("_Selection"), N_("_Custom") -}; - Export::Export() : DialogBase("/dialogs/export/", "Export") - , current_key(SELECTION_PAGE) - , manual_key(SELECTION_PAGE) - , original_name() - , doc_export_name() - , filename_modified(false) - , update_flag(false) - , togglebox(Gtk::ORIENTATION_HORIZONTAL, 0) - , area_box(Gtk::ORIENTATION_VERTICAL, 3) - , singleexport_box(Gtk::ORIENTATION_VERTICAL, 0) - , size_box(Gtk::ORIENTATION_VERTICAL, 3) - , file_box(Gtk::ORIENTATION_VERTICAL, 3) - , unitbox(Gtk::ORIENTATION_HORIZONTAL, 0) - , unit_selector() - , units_label(_("Units:")) - , filename_box(Gtk::ORIENTATION_HORIZONTAL, 5) - , browse_label(_("_Export As..."), true) - , browse_image() - , batch_box(Gtk::ORIENTATION_HORIZONTAL, 5) - , batch_export(_("B_atch export all selected objects")) - , interlacing(_("Use interlacing")) - , bitdepth_label(_("Bit depth")) - , bitdepth_cb() - , zlib_label(_("Compression")) - , zlib_compression() - , pHYs_label(_("pHYs dpi")) - , pHYs_sb(pHYs_adj, 1.0, 2) - , antialiasing_label(_("Antialiasing")) - , antialiasing_cb() - , hide_box(Gtk::ORIENTATION_HORIZONTAL, 3) - , hide_export(_("Hide all except selected")) - , closeWhenDone(_("Close when complete")) - , button_box(Gtk::ORIENTATION_HORIZONTAL, 3) - , _prog() - , prog_dlg(nullptr) - , interrupted(false) - , prefs(nullptr) { - batch_export.set_use_underline(); - batch_export.set_tooltip_text(_("Export each selected object into its own PNG file, using export hints if any (caution, overwrites without asking!)")); - hide_export.set_use_underline(); - hide_export.set_tooltip_text(_("In the exported image, hide all objects except those that are selected")); - interlacing.set_use_underline(); - interlacing.set_tooltip_text(_("Enables ADAM7 interlacing for PNG output. This results in slightly larger image files, but big images can already be displayed (slightly blurry) while still loading.")); - closeWhenDone.set_use_underline(); - closeWhenDone.set_tooltip_text(_("Once the export completes, close this dialog")); - prefs = Inkscape::Preferences::get(); - - singleexport_box.set_border_width(0); - - /* Export area frame */ - { - Gtk::Label* lbl = new Gtk::Label(_("<b>Export area</b>"), Gtk::ALIGN_START); - lbl->set_use_markup(true); - area_box.pack_start(*lbl); + std::string gladefile = get_filename_string(Inkscape::IO::Resource::UIS, "dialog-export.glade"); - /* Units box */ - /* gets added to the vbox later, but the unit selector is needed - earlier than that */ - unit_selector.setUnitType(Inkscape::Util::UNIT_TYPE_LINEAR); - - unitChangedConn = unit_selector.signal_changed().connect(sigc::mem_fun(*this, &Export::onUnitChanged)); - unitbox.pack_end(unit_selector, false, false, 0); - unitbox.pack_end(units_label, false, false, 3); - - for (int i = 0; i < SELECTION_NUMBER_OF; i++) { - selectiontype_buttons[i] = new Gtk::RadioButton(_(selection_labels[i]), true); - if (i > 0) { - Gtk::RadioButton::Group group = selectiontype_buttons[0]->get_group(); - selectiontype_buttons[i]->set_group(group); - } - selectiontype_buttons[i]->set_mode(false); - togglebox.pack_start(*selectiontype_buttons[i], false, true, 0); - selectiontype_buttons[i]->signal_clicked().connect(sigc::mem_fun(*this, &Export::onAreaTypeToggled)); - } - - auto t = new Gtk::Grid(); - t->set_row_spacing(4); - t->set_column_spacing(4); - - x0_adj = createSpinbutton("x0", 0.0, -1000000.0, 1000000.0, 0.1, 1.0, t, 0, 0, _("_x0:"), "", - EXPORT_COORD_PRECISION, 1, &Export::onAreaX0Change); - - x1_adj = createSpinbutton("x1", 0, -1000000.0, 1000000.0, 0.1, 1.0, t, 0, 1, - _("x_1:"), "", EXPORT_COORD_PRECISION, 1, &Export::onAreaX1Change); - - width_adj = createSpinbutton("width", 0, 0.0, PNG_UINT_31_MAX, 0.1, 1.0, t, 0, 2, - _("Wid_th:"), "", EXPORT_COORD_PRECISION, 1, &Export::onAreaWidthChange); - - y0_adj = createSpinbutton("y0", 0.0, -1000000.0, 1000000.0, 0.1, 1.0, t, 2, 0, _("_y0:"), "", - EXPORT_COORD_PRECISION, 1, &Export::onAreaY0Change); - - y1_adj = createSpinbutton("y1", 0, -1000000.0, 1000000.0, 0.1, 1.0, t, 2, 1, - _("y_1:"), "", EXPORT_COORD_PRECISION, 1, &Export::onAreaY1Change); - - height_adj = createSpinbutton("height", 0, 0.0, PNG_UINT_31_MAX, 0.1, 1.0, t, 2, 2, - _("Hei_ght:"), "", EXPORT_COORD_PRECISION, 1, &Export::onAreaHeightChange); - - area_box.pack_start(togglebox, false, false, 3); - area_box.pack_start(*t, false, false, 0); - area_box.pack_start(unitbox, false, false, 0); - - area_box.set_border_width(3); - singleexport_box.pack_start(area_box, false, false, 0); - - } // end of area box - - /* Bitmap size frame */ - { - size_box.set_border_width(3); - bm_label = new Gtk::Label(_("<b>Image size</b>"), Gtk::ALIGN_START); - bm_label->set_use_markup(true); - size_box.pack_start(*bm_label, false, false, 0); - - auto t = new Gtk::Grid(); - t->set_row_spacing(4); - t->set_column_spacing(4); - - size_box.pack_start(*t); - - bmwidth_adj = createSpinbutton("bmwidth", 0, 1.0, 1000000.0, 1.0, 10.0, t, 0, 0, - _("_Width:"), _("pixels at"), 0, 1, &Export::onBitmapWidthChange); - - xdpi_adj = createSpinbutton("xdpi", prefs->getDouble("/dialogs/export/defaultxdpi/value", DPI_BASE), 0.01, - 100000.0, 0.1, 1.0, t, 3, 0, "", _("dp_i"), 2, 1, &Export::onExportXdpiChange); - - bmheight_adj = createSpinbutton("bmheight", 0, 1.0, 1000000.0, 1.0, 10.0, t, 0, 1, - _("_Height:"), _("pixels at"), 0, 1, &Export::onBitmapHeightChange); - - /** TODO - * There's no way to set ydpi currently, so we use the defaultxdpi value here, too... - */ - ydpi_adj = createSpinbutton ( "ydpi", prefs->getDouble("/dialogs/export/defaultxdpi/value", DPI_BASE), - 0.01, 100000.0, 0.1, 1.0, t, 3, 1, - "", _("dpi"), 2, 0, nullptr ); - - singleexport_box.pack_start(size_box, Gtk::PACK_SHRINK); - } - - /* File entry */ - { - file_box.set_border_width(3); - flabel = new Gtk::Label(_("<b>_Filename</b>"), Gtk::ALIGN_START, Gtk::ALIGN_CENTER, true); - flabel->set_use_markup(true); - file_box.pack_start(*flabel, false, false, 0); - - filename_box.pack_start (filename_entry, true, true, 0); - - Gtk::Box* browser_im_label = new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 3); - browse_image.set_from_icon_name("folder", Gtk::ICON_SIZE_BUTTON); - browser_im_label->pack_start(browse_image); - browser_im_label->pack_start(browse_label); - browse_button.add(*browser_im_label); - filename_box.pack_end (browse_button, false, false); - filename_box.pack_end(export_button, false, false); - - file_box.add(filename_box); - - // mnemonic in frame label moves focus to filename: - flabel->set_mnemonic_widget(filename_entry); + try { + builder = Gtk::Builder::create_from_file(gladefile); + } catch (const Glib::Error &ex) { + g_error("Glade file loading failed for export screen"); + return; } - batch_export.set_sensitive(true); - batch_box.pack_start(batch_export, false, false, 3); - - hide_export.set_sensitive(true); - hide_export.set_active (prefs->getBool("/dialogs/export/hideexceptselected/value", false)); - hide_box.pack_start(hide_export, false, false, 3); - - - /* Export Button row */ - export_button.set_label(_("_Export")); - export_button.set_use_underline(); - export_button.set_tooltip_text (_("Export the bitmap file with these settings")); - - button_box.set_border_width(3); - button_box.pack_start(closeWhenDone, true, true, 0); - - /*Advanced*/ - Gtk::Label *label_advanced = Gtk::manage(new Gtk::Label(_("Advanced"),true)); - expander.set_label_widget(*label_advanced); - expander.set_vexpand(false); - const char* const modes_list[]={"Gray_1", "Gray_2","Gray_4","Gray_8","Gray_16","RGB_8","RGB_16","GrayAlpha_8","GrayAlpha_16","RGBA_8","RGBA_16"}; - for(auto i : modes_list) - bitdepth_cb.append(i); - bitdepth_cb.set_active_text("RGBA_8"); - bitdepth_cb.set_hexpand(); - const char* const zlist[]={"Z_NO_COMPRESSION","Z_BEST_SPEED","2","3","4","5","Z_DEFAULT_COMPRESSION","7","8","Z_BEST_COMPRESSION"}; - for(auto i : zlist) - zlib_compression.append(i); - zlib_compression.set_active_text("Z_DEFAULT_COMPRESSION"); - pHYs_adj = Gtk::Adjustment::create(0, 0, 100000, 0.1, 1.0, 0); - pHYs_sb.set_adjustment(pHYs_adj); - pHYs_sb.set_width_chars(7); - pHYs_sb.set_tooltip_text( _("Will force-set the physical dpi for the png file. Set this to 72 if you're planning to work on your png with Photoshop") ); - zlib_compression.set_hexpand(); - const char* const antialising_list[] = {"CAIRO_ANTIALIAS_NONE","CAIRO_ANTIALIAS_FAST","CAIRO_ANTIALIAS_GOOD (default)","CAIRO_ANTIALIAS_BEST"}; - for(auto i : antialising_list) - antialiasing_cb.append(i); - antialiasing_cb.set_active_text(antialising_list[2]); - bitdepth_label.set_halign(Gtk::ALIGN_START); - zlib_label.set_halign(Gtk::ALIGN_START); - pHYs_label.set_halign(Gtk::ALIGN_START); - antialiasing_label.set_halign(Gtk::ALIGN_START); - auto table = new Gtk::Grid(); - expander.add(*table); - table->set_border_width(4); - table->attach(interlacing,0,0,1,1); - table->attach(bitdepth_label,0,1,1,1); - table->attach(bitdepth_cb,1,1,1,1); - table->attach(zlib_label,0,2,1,1); - table->attach(zlib_compression,1,2,1,1); - table->attach(pHYs_label,0,3,1,1); - table->attach(pHYs_sb,1,3,1,1); - table->attach(antialiasing_label,0,4,1,1); - table->attach(antialiasing_cb,1,4,1,1); - table->show(); - - /* Main dialog */ - set_spacing(0); - pack_start(singleexport_box, Gtk::PACK_SHRINK); - pack_start(file_box, Gtk::PACK_SHRINK); - pack_start(batch_box, Gtk::PACK_SHRINK); - pack_start(hide_box, Gtk::PACK_SHRINK); - pack_start(button_box, Gtk::PACK_SHRINK); - pack_start(expander, Gtk::PACK_SHRINK); - pack_end(_prog, Gtk::PACK_SHRINK); - - /* Signal handlers */ - filename_entry.signal_changed().connect( sigc::mem_fun(*this, &Export::onFilenameModified)); - // pressing enter in the filename field is the same as clicking export: - filename_entry.signal_activate().connect(sigc::mem_fun(*this, &Export::onExport)); - browse_button.signal_clicked().connect(sigc::mem_fun(*this, &Export::onBrowse)); - batch_export.signal_clicked().connect(sigc::mem_fun(*this, &Export::onBatchClicked)); - export_button.signal_clicked().connect(sigc::mem_fun(*this, &Export::onExport)); - hide_export.signal_clicked().connect(sigc::mem_fun(*this, &Export::onHideExceptSelected)); + prefs = Inkscape::Preferences::get(); + builder->get_widget("Export Dialog Box", container); + add(*container); show_all_children(); - setExporting(false); -} - -Export::~Export () -{ -} -void Export::documentReplaced() -{ - if (auto document = getDocument()) { - unit_selector.setUnit(document->getNamedView()->display_units->abbr); + builder->get_widget("Export Notebook", export_notebook); - set_default_filename(document->getDocumentFilename()); + // Initialise Single Export and its objects + builder->get_widget_derived("Single Image", single_image); + single_image->initialise(builder); - // focus is in the filename initially: - original_name = filename_entry.get_text(); - filename_entry.grab_focus(); - filename_modified = false; + // Initialise Batch Export and its objects + builder->get_widget_derived("Batch Export", batch_export); + batch_export->initialise(builder); - refreshArea(); - findDefaultSelection(); + if (single_image) { + single_image->setDesktop(getDesktop()); + single_image->setApp(getApp()); } -} - -/* - * set the default filename to be that of the current path + document - * with .png extension - * - * One thing to notice here is that this filename may get - * overwritten, but it won't happen here. The filename gets - * written into the text field, but then the button to select - * the area gets set. In that code the filename can be changed - * if there are some with presidence in the document. So, while - * this code sets the name first, it may not be the one users - * really see. - */ -void Export::set_default_filename (const gchar *doc_filename) -{ - if (doc_filename) { - auto &&text_extension = get_file_save_extension(Inkscape::Extension::FILE_SAVE_METHOD_SAVE_AS); - Inkscape::Extension::Output * oextension = nullptr; - - if (!text_extension.empty()) { - oextension = dynamic_cast<Inkscape::Extension::Output *>(Inkscape::Extension::db.get(text_extension.c_str())); - } - - if (oextension != nullptr) { - gchar * old_extension = oextension->get_extension(); - if (g_str_has_suffix(doc_filename, old_extension)) { - gchar *filename_copy = g_strdup(doc_filename); - gchar *extension_point = g_strrstr(filename_copy, old_extension); - extension_point[0] = '\0'; - - gchar *final_name = g_strconcat(filename_copy, ".png", nullptr); - filename_entry.set_text(final_name); - filename_entry.set_position(strlen(final_name)); - - g_free(final_name); - g_free(filename_copy); - } - } else { - gchar *name = g_strconcat(doc_filename, ".png", nullptr); - filename_entry.set_text(name); - filename_entry.set_position(strlen(name)); - - g_free(name); - } - - doc_export_name = filename_entry.get_text(); - } else { - Glib::ustring filename = create_filepath_from_id (_("bitmap"), filename_entry.get_text()); - filename_entry.set_text(filename); - filename_entry.set_position(filename.length()); - doc_export_name = filename_entry.get_text(); + if (batch_export) { + batch_export->setDesktop(getDesktop()); + batch_export->setApp(getApp()); } -} -Glib::RefPtr<Gtk::Adjustment> Export::createSpinbutton( gchar const * /*key*/, - double val, double min, double max, double step, double page, - Gtk::Grid *t, int x, int y, - const Glib::ustring& ll, const Glib::ustring& lr, - int digits, unsigned int sensitive, - void (Export::*cb)() ) -{ - auto adj = Gtk::Adjustment::create(val, min, max, step, page, 0); - - int pos = 0; - Gtk::Label *l = nullptr; - - if (!ll.empty()) { - l = new Gtk::Label(ll,true); - l->set_halign(Gtk::ALIGN_END); - l->set_valign(Gtk::ALIGN_CENTER); - l->set_hexpand(); - t->attach(*l, x + pos, y, 1, 1); - l->set_sensitive(sensitive); - pos++; - } - - auto sb = new Inkscape::UI::Widget::ScrollProtected<Gtk::SpinButton>(adj, 1.0, digits); - sb->set_hexpand(); - t->attach(*sb, x + pos, y, 1, 1); - - sb->set_width_chars(7); - sb->set_sensitive (sensitive); - pos++; - - if (l) { - l->set_mnemonic_widget(*sb); - } - - if (!lr.empty()) { - l = new Gtk::Label(lr,true); - l->set_halign(Gtk::ALIGN_START); - l->set_valign(Gtk::ALIGN_CENTER); - l->set_hexpand(); - t->attach(*l, x + pos, y, 1, 1); - l->set_sensitive (sensitive); - pos++; - l->set_mnemonic_widget (*sb); - } - - if (cb) { - adj->signal_value_changed().connect( sigc::mem_fun(*this, cb) ); - } - - return adj; -} // end of createSpinbutton() - - -std::string Export::create_filepath_from_id(Glib::ustring id, const Glib::ustring &file_entry_text) -{ - if (id.empty()) - { /* This should never happen */ - id = "bitmap"; - } - - std::string directory; - - if (!file_entry_text.empty()) { - directory = Glib::path_get_dirname(Glib::filename_from_utf8(file_entry_text)); - } - - if (directory.empty()) { - /* Grab document directory */ - const gchar* docFilename = getDocument()->getDocumentFilename(); - if (docFilename) { - directory = Glib::path_get_dirname(docFilename); - } - } - - if (directory.empty()) { - directory = Inkscape::IO::Resource::homedir_path(nullptr); - } - - return Glib::build_filename(directory, Glib::filename_from_utf8(id) + ".png"); + // Callback when container is finally mapped on window. All intialisation like set active is done inside it. + container->signal_realize().connect(sigc::mem_fun(*this, &Export::onRealize)); + export_notebook->signal_switch_page().connect(sigc::mem_fun(*this, &Export::onPageSwitch)); } -void Export::onBatchClicked () -{ - if (batch_export.get_active()) { - singleexport_box.set_sensitive(false); - } else { - singleexport_box.set_sensitive(true); - } -} +Export::~Export() {} -void Export::updateCheckbuttons () +// When conainer is visible then setup all widgets. +// It prevents gtk_is_widget assertion warning probably. +void Export::onRealize() { - gint num = (gint) boost::distance(getSelection()->items()); - if (num >= 2) { - batch_export.set_sensitive(true); - } else { - batch_export.set_active (false); - batch_export.set_sensitive(false); - } - gchar *l = g_strdup_printf(ngettext("B_atch export %d selected object","B_atch export %d selected objects",num), num); - batch_export.set_label(l); - g_free(l); - - //hide_export.set_sensitive (num > 0); -} - -inline void Export::findDefaultSelection() -{ - selection_type key = SELECTION_NUMBER_OF; - - if (getSelection()->isEmpty() == false) { - key = SELECTION_SELECTION; - } - - /* Try using the preferences */ - if (key == SELECTION_NUMBER_OF) { - - int i = SELECTION_NUMBER_OF; - - Glib::ustring what = prefs->getString("/dialogs/export/exportarea/value"); - - if (!what.empty()) { - for (i = 0; i < SELECTION_NUMBER_OF; i++) { - if (what == selection_names[i]) { - break; - } - } - } - - key = (selection_type)i; + if (single_image) { + single_image->setDesktop(getDesktop()); + single_image->setApp(getApp()); + single_image->setup(); } - - if (key == SELECTION_NUMBER_OF) { - key = SELECTION_PAGE; + if (batch_export) { + batch_export->setDesktop(getDesktop()); + batch_export->setApp(getApp()); + batch_export->setup(); } - - current_key = key; - selectiontype_buttons[current_key]->set_active(true); - updateCheckbuttons (); + setDefaultNotebookPage(); } - -/** - * If selection changed and "Export area" is set to "Selection" - * recalculate bounds when the selection changes - */ -void Export::selectionChanged(Inkscape::Selection *selection) +// Set current page based on preference/last visited page +void Export::setDefaultNotebookPage() { - if (manual_key != SELECTION_CUSTOM && selection) { - current_key = SELECTION_SELECTION; - refreshArea(); - } - updateCheckbuttons(); + pages[BATCH_EXPORT] = export_notebook->page_num(*batch_export); + pages[SINGLE_IMAGE] = export_notebook->page_num(*single_image); + export_notebook->set_current_page(pages[SINGLE_IMAGE]); } -void Export::selectionModified (Inkscape::Selection *Sel, guint flags) +void Export::documentReplaced() { - switch (current_key) { - case SELECTION_DRAWING: - if (auto doc = getDocument()) { - Geom::OptRect bbox = doc->getRoot()->desktopVisualBounds(); - if (bbox) { - setArea ( bbox->left(), - bbox->top(), - bbox->right(), - bbox->bottom()); - } - } - break; - case SELECTION_SELECTION: - if (Sel->isEmpty() == false) { - Geom::OptRect bbox = Sel->visualBounds(); - if (bbox) - { - setArea ( bbox->left(), - bbox->top(), - bbox->right(), - bbox->bottom()); - } - } - break; - default: - /* Do nothing for page or for custom */ - break; + if (single_image) { + single_image->setDocument(getDocument()); } - - return; -} - -/// Called when one of the selection buttons was toggled. -void Export::onAreaTypeToggled() { - if (update_flag) { - return; + if (batch_export) { + batch_export->setDocument(getDocument()); } - - /* Find which button is active */ - selection_type key = current_key; - for (int i = 0; i < SELECTION_NUMBER_OF; i++) { - if (selectiontype_buttons[i]->get_active()) { - key = (selection_type)i; - } - } - manual_key = current_key = key; - - refreshArea(); } -/// Called when area needs to be refreshed -/// Area type changed, unit changed, initialization -void Export::refreshArea () -{ - if (auto doc = getDocument()) { - Geom::OptRect bbox; - bbox = Geom::Rect(Geom::Point(0.0, 0.0),Geom::Point(0.0, 0.0)); - - /* Notice how the switch is used to 'fall through' here to get - various backups. If you modify this without noticing you'll - probably screw something up. */ - switch (current_key) { - case SELECTION_SELECTION: - if (!getSelection()->isEmpty()) - { - bbox = getSelection()->visualBounds(); - /* Only if there is a selection that we can set - do we break, otherwise we fall through to the - drawing */ - // std::cout << "Using selection: SELECTION" << std::endl; - current_key = SELECTION_SELECTION; - break; - } - case SELECTION_DRAWING: - if (manual_key == SELECTION_DRAWING || manual_key == SELECTION_SELECTION) { - /** \todo - * This returns wrong values if the document has a viewBox. - */ - bbox = doc->getRoot()->desktopVisualBounds(); - /* If the drawing is valid, then we'll use it and break - otherwise we drop through to the page settings */ - if (bbox) { - // std::cout << "Using selection: DRAWING" << std::endl; - current_key= SELECTION_DRAWING; - break; - } - } - case SELECTION_PAGE: - if (manual_key == SELECTION_PAGE){ - bbox = Geom::Rect(Geom::Point(0.0, 0.0), - Geom::Point(doc->getWidth().value("px"), doc->getHeight().value("px"))); - - // std::cout << "Using selection: PAGE" << std::endl; - current_key= SELECTION_PAGE; - break; - } - case SELECTION_CUSTOM: - current_key = SELECTION_CUSTOM; - default: - break; - } // switch - - // remember area setting - prefs->setString("/dialogs/export/exportarea/value", selection_names[current_key]); - - if ( current_key != SELECTION_CUSTOM && bbox ) { - setArea ( bbox->min()[Geom::X], - bbox->min()[Geom::Y], - bbox->max()[Geom::X], - bbox->max()[Geom::Y]); - } - - } - - if (getDesktop() && !filename_modified) { - - Glib::ustring filename; - float xdpi = 0.0, ydpi = 0.0; - - switch (current_key) { - case SELECTION_PAGE: - case SELECTION_DRAWING: { - SPDocument * doc = getDocument(); - sp_document_get_export_hints (doc, filename, &xdpi, &ydpi); - - if (filename.empty()) { - if (!doc_export_name.empty()) { - filename = doc_export_name; - } - } - break; - } - case SELECTION_SELECTION: - if (!getSelection()->isEmpty()) { - getSelection()->getExportHints(filename, &xdpi, &ydpi); - - /* If we still don't have a filename -- let's build - one that's nice */ - if (filename.empty()) { - const gchar * id = "object"; - auto reprlst = getSelection()->xmlNodes(); - for(auto i=reprlst.begin(); reprlst.end() != i; ++i) { - Inkscape::XML::Node * repr = *i; - if (repr->attribute("id")) { - id = repr->attribute("id"); - break; - } - } - - filename = create_filepath_from_id (id, filename_entry.get_text()); - } - } - break; - case SELECTION_CUSTOM: - default: - break; - } - - if (!filename.empty()) { - original_name = filename; - filename_entry.set_text(filename); - filename_entry.set_position(filename.length()); - } - - if (xdpi != 0.0) { - setValue(xdpi_adj, xdpi); - } - - /* These can't be separate, and setting x sets y, so for - now setting this is disabled. Hopefully it won't be in - the future */ - if (FALSE && ydpi != 0.0) { - setValue(ydpi_adj, ydpi); - } - } - - return; -} // end of sp_export_area_toggled() - -/// Called when dialog is deleted -bool Export::onProgressDelete (GdkEventAny * /*event*/) +void Export::desktopReplaced() { - interrupted = true; - return TRUE; -} // end of sp_export_progress_delete() - - -/// Called when progress is cancelled -void Export::onProgressCancel () -{ - interrupted = true; -} // end of sp_export_progress_cancel() - - -/// Called for every progress iteration -unsigned int Export::onProgressCallback(float value, void *dlg) -{ - auto dlg2 = reinterpret_cast<ExportProgressDialog*>(dlg); - - auto self = dlg2->get_export_panel(); - if (self->interrupted) - return FALSE; - - auto current = dlg2->get_current(); - auto total = dlg2->get_total(); - if (total > 0) { - double completed = current; - completed /= static_cast<double>(total); - - value = completed + (value / static_cast<double>(total)); + if (single_image) { + single_image->setDesktop(getDesktop()); } - - auto prg = dlg2->get_progress(); - prg->set_fraction(value); - - if (self) { - self->_prog.set_fraction(value); - } - - int evtcount = 0; - while ((evtcount < 16) && gdk_events_pending()) { - Gtk::Main::iteration(false); - evtcount += 1; - } - - Gtk::Main::iteration(false); - return TRUE; -} // end of sp_export_progress_callback() - -void Export::setExporting(bool exporting, Glib::ustring const &text) -{ - if (exporting) { - _prog.set_text(text); - _prog.set_fraction(0.0); - _prog.set_sensitive(true); - - export_button.set_sensitive(false); - } else { - _prog.set_text(""); - _prog.set_fraction(0.0); - _prog.set_sensitive(false); - - export_button.set_sensitive(true); + if (batch_export) { + batch_export->setDesktop(getDesktop()); } } -ExportProgressDialog * -Export::create_progress_dialog(Glib::ustring progress_text) -{ - auto dlg = new ExportProgressDialog(_("Export in progress"), true); - dlg->set_transient_for( *(getDesktop()->getToplevel()) ); - - Gtk::ProgressBar *prg = new Gtk::ProgressBar (); - prg->set_text(progress_text); - dlg->set_progress(prg); - auto CA = dlg->get_content_area(); - CA->pack_start(*prg, FALSE, FALSE, 4); - - Gtk::Button* btn = dlg->add_button (_("_Cancel"),Gtk::RESPONSE_CANCEL ); - - btn->signal_clicked().connect( sigc::mem_fun(*this, &Export::onProgressCancel) ); - dlg->signal_delete_event().connect( sigc::mem_fun(*this, &Export::onProgressDelete) ); - - dlg->show_all (); - return dlg; -} - -static std::string absolutize_path_from_document_location(SPDocument *doc, const std::string &filename) +void Export::selectionChanged(Inkscape::Selection *selection) { - std::string path; - //Make relative paths go from the document location, if possible: - if (!Glib::path_is_absolute(filename) && doc->getDocumentFilename()) { - auto dirname = Glib::path_get_dirname(doc->getDocumentFilename()); - if (!dirname.empty()) { - path = Glib::build_filename(dirname, filename); - } + auto current_page = export_notebook->get_current_page(); + if (current_page == pages[SINGLE_IMAGE]) { + single_image->selectionChanged(selection); } - if (path.empty()) { - path = filename; + if (current_page == pages[BATCH_EXPORT]) { + batch_export->selectionChanged(selection); } - return path; -} - -// Called when unit is changed -void Export::onUnitChanged() -{ - refreshArea(); } - -void Export::onHideExceptSelected () +void Export::selectionModified(Inkscape::Selection *selection, guint flags) { - prefs->setBool("/dialogs/export/hideexceptselected/value", hide_export.get_active()); -} - -/// Called when export button is clicked -void Export::onExport () -{ - _export_raster(nullptr); -} - -void Export::_export_raster(Inkscape::Extension::Output *extension) -{ - auto desktop = getDesktop(); - if (!desktop) return; - - SPNamedView *nv = desktop->getNamedView(); - SPDocument *doc = desktop->getDocument(); - - bool exportSuccessful = false; - - bool hide = hide_export.get_active (); - - // Advanced parameters - bool do_interlace = (interlacing.get_active()); - float pHYs = 0; - int zlib = zlib_compression.get_active_row_number() ; - int colortypes[] = {0,0,0,0,0,2,2,4,4,6,6}; //keep in sync with modes_list in Export constructor. values are from libpng doc. - int bitdepths[] = {1,2,4,8,16,8,16,8,16,8,16}; - int color_type = colortypes[bitdepth_cb.get_active_row_number()] ; - int bit_depth = bitdepths[bitdepth_cb.get_active_row_number()] ; - int antialiasing = antialiasing_cb.get_active_row_number(); - - - if (batch_export.get_active ()) { - // Batch export of selected objects - - gint num = (gint) boost::distance(desktop->getSelection()->items()); - gint n = 0; - - if (num < 1) { - desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("No items selected.")); - return; - } - - prog_dlg = create_progress_dialog(Glib::ustring::compose(_("Exporting %1 files"), num)); - prog_dlg->set_export_panel(this); - setExporting(true, Glib::ustring::compose(_("Exporting %1 files"), num)); - - gint export_count = 0; - - auto itemlist= desktop->getSelection()->items(); - for(auto i = itemlist.begin();i!=itemlist.end() && !interrupted ;++i){ - SPItem *item = *i; - - prog_dlg->set_current(n); - prog_dlg->set_total(num); - onProgressCallback(0.0, prog_dlg); - - // retrieve export filename hint - const gchar *filename = item->getRepr()->attribute("inkscape:export-filename"); - std::string path; - if (!filename) { - auto id = item->getId(); - if (!id) { - g_warning("object has no id"); - continue; - } - path = create_filepath_from_id(id, filename_entry.get_text()); - } else { - path = absolutize_path_from_document_location(doc, filename); - } - - // retrieve export dpi hints - const gchar *dpi_hint = item->getRepr()->attribute("inkscape:export-xdpi"); // only xdpi, ydpi is always the same now - gdouble dpi = 0.0; - if (dpi_hint) { - dpi = g_ascii_strtod(dpi_hint, nullptr); - } - if (dpi == 0.0) { - dpi = getValue(xdpi_adj); - } - pHYs = (pHYs_adj->get_value() > 0.01) ? pHYs_adj->get_value() : dpi; - - Geom::OptRect area = item->documentVisualBounds(); - if (area) { - gint width = (gint) (area->width() * dpi / DPI_BASE + 0.5); - gint height = (gint) (area->height() * dpi / DPI_BASE + 0.5); - - if (width > 1 && height > 1) { - // Do export - gchar * safeFile = Inkscape::IO::sanitizeString(path.c_str()); - MessageCleaner msgCleanup(desktop->messageStack()->pushF(Inkscape::IMMEDIATE_MESSAGE, - _("Exporting file <b>%s</b>..."), safeFile), desktop); - MessageCleaner msgFlashCleanup(desktop->messageStack()->flashF(Inkscape::IMMEDIATE_MESSAGE, - _("Exporting file <b>%s</b>..."), safeFile), desktop); - std::vector<SPItem*> x; - std::vector<SPItem*> selected(desktop->getSelection()->items().begin(), desktop->getSelection()->items().end()); - if (!sp_export_png_file (doc, path.c_str(), - *area, width, height, pHYs, pHYs, - nv->pagecolor, - onProgressCallback, (void*)prog_dlg, - TRUE, // overwrite without asking - hide ? selected : x, - do_interlace, color_type, bit_depth, zlib, antialiasing - )) { - gchar * error = g_strdup_printf(_("Could not export to filename %s.\n"), safeFile); - - desktop->messageStack()->flashF(Inkscape::ERROR_MESSAGE, - _("Could not export to filename <b>%s</b>."), safeFile); - - sp_ui_error_dialog(error); - g_free(error); - } else { - ++export_count; // one more item exported successfully - } - g_free(safeFile); - } - } - - n++; - } - - desktop->messageStack()->flashF(Inkscape::INFORMATION_MESSAGE, - _("Successfully exported <b>%d</b> files from <b>%d</b> selected items."), export_count, num); - - setExporting(false); - delete prog_dlg; - prog_dlg = nullptr; - interrupted = false; - exportSuccessful = (export_count > 0); - } else { - Glib::ustring filename = filename_entry.get_text(); - - if (filename.empty()) { - desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("You have to enter a filename.")); - sp_ui_error_dialog(_("You have to enter a filename")); - return; - } - - float const x0 = getValuePx(x0_adj); - float const y0 = getValuePx(y0_adj); - float const x1 = getValuePx(x1_adj); - float const y1 = getValuePx(y1_adj); - float const xdpi = getValue(xdpi_adj); - float const ydpi = getValue(ydpi_adj); - pHYs = (pHYs_adj->get_value() > 0.01) ? pHYs_adj->get_value() : xdpi; - unsigned long int const width = int(getValue(bmwidth_adj) + 0.5); - unsigned long int const height = int(getValue(bmheight_adj) + 0.5); - - if (!((x1 > x0) && (y1 > y0) && (width > 0) && (height > 0))) { - desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("The chosen area to be exported is invalid.")); - sp_ui_error_dialog(_("The chosen area to be exported is invalid")); - return; - } - - std::string path = absolutize_path_from_document_location(doc, Glib::filename_from_utf8(filename)); - - Glib::ustring dirname = Glib::path_get_dirname(path); - if ( dirname.empty() - || !Inkscape::IO::file_test(dirname.c_str(), (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) ) - { - gchar *safeDir = Inkscape::IO::sanitizeString(dirname.c_str()); - gchar *error = g_strdup_printf(_("Directory %s does not exist or is not a directory.\n"), - safeDir); - - desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, error); - sp_ui_error_dialog(error); - - g_free(safeDir); - g_free(error); - return; - } - - auto fn = Glib::path_get_basename(path); - auto area = Geom::Rect(Geom::Point(x0, y0), Geom::Point(x1, y1)) * desktop->dt2doc(); - - // Select a raster output extension if not a png file (manual filename) - if (!extension && !Glib::str_has_suffix(filename, ".png")) { - Inkscape::Extension::DB::OutputList extension_list; - Inkscape::Extension::db.get_output_list(extension_list); - for (auto output_ext : extension_list) { - if (output_ext->deactivated() || !output_ext->is_raster()) - continue; - if(Glib::str_has_suffix(path.c_str(), output_ext->get_extension())) { - // Select the extension - extension = output_ext; - break; - } - } - } - - bool overwrite = false; - auto png_filename = std::string(path.c_str()); - if (extension) { - // Select the extension and set the filename to a temporary file - int tempfd_out = Glib::file_open_tmp(png_filename, "ink_ext_"); - // Do the over-write protection now, since the png is just a temp file. - if (!sp_ui_overwrite_file(filename.c_str())) { - return; - } - overwrite = true; - close(tempfd_out); - } - - /* TRANSLATORS: %1 will be the filename, %2 the width, and %3 the height of the image */ - prog_dlg = create_progress_dialog (Glib::ustring::compose(_("Exporting %1 (%2 x %3)"), fn, width, height)); - prog_dlg->set_export_panel(this); - setExporting(true, Glib::ustring::compose(_("Exporting %1 (%2 x %3)"), fn, width, height)); - - prog_dlg->set_current(0); - prog_dlg->set_total(0); - - /* Do export */ - std::vector<SPItem*> x; - std::vector<SPItem*> selected(desktop->getSelection()->items().begin(), desktop->getSelection()->items().end()); - ExportResult status = sp_export_png_file(desktop->getDocument(), png_filename.c_str(), - area, width, height, pHYs, pHYs, //previously xdpi, ydpi. - nv->pagecolor, - onProgressCallback, (void*)prog_dlg, - overwrite, - hide ? selected : x, - do_interlace, color_type, bit_depth, zlib, antialiasing - ); - if (status == EXPORT_ERROR) { - gchar * safeFile = Inkscape::IO::sanitizeString(path.c_str()); - gchar * error = g_strdup_printf(_("Could not export to filename %s.\n"), safeFile); - - desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, error); - sp_ui_error_dialog(error); - - g_free(safeFile); - g_free(error); - } else if (status == EXPORT_OK) { - - exportSuccessful = true; - if(extension != nullptr) { - // Remove progress dialog before showing prefs dialog. - delete prog_dlg; - prog_dlg = nullptr; - if(extension->prefs()) { - try { - extension->export_raster(doc, png_filename, path.c_str(), false); - } catch (Inkscape::Extension::Output::save_failed &e) { - exportSuccessful = false; - } - } else { - exportSuccessful = false; - } - } - - if (exportSuccessful) { - auto recentmanager = Gtk::RecentManager::get_default(); - if(recentmanager && Glib::path_is_absolute(path)) { - Glib::ustring uri = Glib::filename_to_uri(path); - recentmanager->add_item(uri); - } - - gchar *safeFile = Inkscape::IO::sanitizeString(path.c_str()); - desktop->messageStack()->flashF(Inkscape::INFORMATION_MESSAGE, _("Drawing exported to <b>%s</b>."), safeFile); - g_free(safeFile); - } - } else { - // Extensions have their own error popup, so this only tracks failures in the png step - desktop->messageStack()->flash(Inkscape::INFORMATION_MESSAGE, _("Export aborted.")); - } - if (extension != nullptr) { - unlink(png_filename.c_str()); - } - - /* Reset the filename so that it can be changed again by changing - selections and all that */ - original_name = filename; - filename_modified = false; - - setExporting(false); - if(prog_dlg) { - delete prog_dlg; - prog_dlg = nullptr; - } - interrupted = false; - - /* Setup the values in the document */ - switch (current_key) { - case SELECTION_PAGE: - case SELECTION_DRAWING: { - SPDocument * doc = getDocument(); - Inkscape::XML::Node * repr = doc->getReprRoot(); - bool modified = false; - - bool saved = DocumentUndo::getUndoSensitive(doc); - DocumentUndo::setUndoSensitive(doc, false); - - gchar const *temp_string = repr->attribute("inkscape:export-filename"); - if (temp_string == nullptr || (filename != temp_string)) { - repr->setAttribute("inkscape:export-filename", filename); - modified = true; - } - temp_string = repr->attribute("inkscape:export-xdpi"); - if (temp_string == nullptr || xdpi != g_ascii_strtod(temp_string, nullptr)) { - repr->setAttributeSvgDouble("inkscape:export-xdpi", xdpi); - modified = true; - } - temp_string = repr->attribute("inkscape:export-ydpi"); - if (temp_string == nullptr || ydpi != g_ascii_strtod(temp_string, nullptr)) { - repr->setAttributeSvgDouble("inkscape:export-ydpi", ydpi); - modified = true; - } - DocumentUndo::setUndoSensitive(doc, saved); - - if (modified) { - doc->setModifiedSinceSave(); - } - break; - } - case SELECTION_SELECTION: { - SPDocument * doc = getDocument(); - bool modified = false; - - bool saved = DocumentUndo::getUndoSensitive(doc); - DocumentUndo::setUndoSensitive(doc, false); - auto reprlst = desktop->getSelection()->xmlNodes(); - - for(auto i=reprlst.begin(); reprlst.end() != i; ++i) { - Inkscape::XML::Node * repr = *i; - const gchar * temp_string; - Glib::ustring dir = Glib::path_get_dirname(filename.c_str()); - const gchar* docFilename = doc->getDocumentFilename(); - Glib::ustring docdir; - if (docFilename) - { - docdir = Glib::path_get_dirname(docFilename); - } - temp_string = repr->attribute("inkscape:export-filename"); - if (temp_string == nullptr || (filename != temp_string)) { - repr->setAttribute("inkscape:export-filename", filename); - modified = true; - } - - temp_string = repr->attribute("inkscape:export-xdpi"); - if (temp_string == nullptr || xdpi != g_ascii_strtod(temp_string, nullptr)) { - repr->setAttributeSvgDouble("inkscape:export-xdpi", xdpi); - modified = true; - } - temp_string = repr->attribute("inkscape:export-ydpi"); - if (temp_string == nullptr || ydpi != g_ascii_strtod(temp_string, nullptr)) { - repr->setAttributeSvgDouble("inkscape:export-ydpi", ydpi); - modified = true; - } - } - DocumentUndo::setUndoSensitive(doc, saved); - - if (modified) { - doc->setModifiedSinceSave(); - } - break; - } - default: - break; - } + auto current_page = export_notebook->get_current_page(); + if (current_page == pages[SINGLE_IMAGE]) { + single_image->selectionModified(selection, flags); } - - if (exportSuccessful && closeWhenDone.get_active()) { - for (Gtk::Container *parent = get_parent(); parent; parent = parent->get_parent()) { - DialogNotebook *notebook = dynamic_cast<DialogNotebook *>(parent); - if (notebook) { - notebook->close_tab_callback(); - break; - } - } - } -} // end of Export::onExport() - -/// Called when Browse button is clicked -void Export::onBrowse () -{ - // Create and show the dialog - Gtk::Window *window = getApp()->get_active_window(); - std::string filename = Glib::filename_from_utf8(filename_entry.get_text()); - - if (filename.empty()) { - Glib::ustring tmp; - filename = create_filepath_from_id(tmp, tmp); - } - - Inkscape::UI::Dialog::FileSaveDialog *dialog = Inkscape::UI::Dialog::FileSaveDialog::create( - *window, filename, Inkscape::UI::Dialog::RASTER_TYPES, _("Select a filename for exporting"), "", "", - Inkscape::Extension::FILE_SAVE_METHOD_EXPORT); - - if (dialog->show()) { - auto file = dialog->getFilename(); - filename_entry.set_text(Glib::filename_to_utf8(file)); - filename_entry.set_position(-1); - auto selection_type = dialog->getSelectionType(); - //deleting dialog before exporting is important - //proper delete function should be made for dialog IMO - delete dialog; - _export_raster(dynamic_cast<Inkscape::Extension::Output *>(selection_type)); - }else { - delete dialog; + if (current_page == pages[BATCH_EXPORT]) { + batch_export->selectionModified(selection, flags); } - } -// TODO: Move this to nr-rect-fns.h. -bool Export::bbox_equal(Geom::Rect const &one, Geom::Rect const &two) +void Export::onPageSwitch(Widget *page, guint page_number) { - double const epsilon = pow(10.0, -EXPORT_COORD_PRECISION); - return ( - (fabs(one.min()[Geom::X] - two.min()[Geom::X]) < epsilon) && - (fabs(one.min()[Geom::Y] - two.min()[Geom::Y]) < epsilon) && - (fabs(one.max()[Geom::X] - two.max()[Geom::X]) < epsilon) && - (fabs(one.max()[Geom::Y] - two.max()[Geom::Y]) < epsilon) - ); -} - -/** - *This function is used to detect the current selection setting - * based on the values in the x0, y0, x1 and y0 fields. - * - * One of the most confusing parts of this function is why the array - * is built at the beginning. What needs to happen here is that we - * should always check the current selection to see if it is the valid - * one. While this is a performance improvement it is also a usability - * one during the cases where things like selections and drawings match - * size. This way buttons change less 'randomly' (at least in the eyes - * of the user). To do this an array is built where the current selection - * type is placed first, and then the others in an order from smallest - * to largest (this can be configured by reshuffling \c test_order). - * - * All of the values in this function are rounded to two decimal places - * because that is what is shown to the user. While everything is kept - * more accurate than that, the user can't control more accurate than - * that, so for this to work for them - it needs to check on that level - * of accuracy. - * - * @todo finish writing this up. - */ -void Export::detectSize() { - static const selection_type test_order[SELECTION_NUMBER_OF] = {SELECTION_SELECTION, SELECTION_DRAWING, SELECTION_PAGE, SELECTION_CUSTOM}; - selection_type this_test[SELECTION_NUMBER_OF + 1]; - selection_type key = SELECTION_NUMBER_OF; - - Geom::Point x(getValuePx(x0_adj), - getValuePx(y0_adj)); - Geom::Point y(getValuePx(x1_adj), - getValuePx(y1_adj)); - Geom::Rect current_bbox(x, y); - - this_test[0] = current_key; - for (int i = 0; i < SELECTION_NUMBER_OF; i++) { - this_test[i + 1] = test_order[i]; - } - auto desktop = getDesktop(); - for (int i = 0; - i < SELECTION_NUMBER_OF + 1 && - key == SELECTION_NUMBER_OF && - desktop != nullptr; - i++) { - switch (this_test[i]) { - case SELECTION_SELECTION: - if (getSelection()->isEmpty() == false) { - Geom::OptRect bbox = getSelection()->bounds(SPItem::VISUAL_BBOX); - - if ( bbox && bbox_equal(*bbox,current_bbox)) { - key = SELECTION_SELECTION; - } - } - break; - case SELECTION_DRAWING: { - Geom::OptRect bbox = getDocument()->getRoot()->desktopVisualBounds(); - - if ( bbox && bbox_equal(*bbox,current_bbox) ) { - key = SELECTION_DRAWING; - } - break; - } - - case SELECTION_PAGE: { - auto doc = getDocument(); - - Geom::Point x(0.0, 0.0); - Geom::Point y(doc->getWidth().value("px"), - doc->getHeight().value("px")); - Geom::Rect bbox(x, y); - - if (bbox_equal(bbox,current_bbox)) { - key = SELECTION_PAGE; - } - - break; - } - default: - break; - } - } - // std::cout << std::endl; - - if (key == SELECTION_NUMBER_OF) { - key = SELECTION_CUSTOM; - } - - current_key = key; - selectiontype_buttons[current_key]->set_active(true); - - return; -} /* sp_export_detect_size */ - -/// Called when area x0 value is changed -void Export::areaXChange(Glib::RefPtr<Gtk::Adjustment>& adj) -{ - float x0, x1, xdpi, width; - - if (update_flag) { - return; - } - - update_flag = true; + if (desktop) { + auto selection = desktop->getSelection(); - x0 = getValuePx(x0_adj); - x1 = getValuePx(x1_adj); - xdpi = getValue(xdpi_adj); - - width = floor ((x1 - x0) * xdpi / DPI_BASE + 0.5); - - if (width < SP_EXPORT_MIN_SIZE) { - width = SP_EXPORT_MIN_SIZE; - - if (adj == x1_adj) { - x1 = x0 + width * DPI_BASE / xdpi; - setValuePx(x1_adj, x1); - } else { - x0 = x1 - width * DPI_BASE / xdpi; - setValuePx(x0_adj, x0); + if (page_number == pages[SINGLE_IMAGE]) { + single_image->selectionChanged(selection); } - } - - setValuePx(width_adj, x1 - x0); - setValue(bmwidth_adj, width); - - detectSize(); - - update_flag = false; - - return; -} // end of sp_export_area_x_value_changed() - -/// Called when area y0 value is changed. -void Export::areaYChange(Glib::RefPtr<Gtk::Adjustment>& adj) -{ - float y0, y1, ydpi, height; - - if (update_flag) { - return; - } - - update_flag = true; - - y0 = getValuePx(y0_adj); - y1 = getValuePx(y1_adj); - ydpi = getValue(ydpi_adj); - - height = floor ((y1 - y0) * ydpi / DPI_BASE + 0.5); - - if (height < SP_EXPORT_MIN_SIZE) { - height = SP_EXPORT_MIN_SIZE; - if (adj == y1_adj) { - //if (!strcmp (key, "y0")) { - y1 = y0 + height * DPI_BASE / ydpi; - setValuePx(y1_adj, y1); - } else { - y0 = y1 - height * DPI_BASE / ydpi; - setValuePx(y0_adj, y0); + if (page_number == pages[BATCH_EXPORT]) { + batch_export->selectionChanged(selection); } } - - setValuePx(height_adj, y1 - y0); - setValue(bmheight_adj, height); - - detectSize(); - - update_flag = false; - - return; -} // end of sp_export_area_y_value_changed() - -/// Called when x1-x0 or area width is changed -void Export::onAreaWidthChange() -{ - if (update_flag) { - return; - } - - update_flag = true; - - float x0 = getValuePx(x0_adj); - float xdpi = getValue(xdpi_adj); - float width = getValuePx(width_adj); - float bmwidth = floor(width * xdpi / DPI_BASE + 0.5); - - if (bmwidth < SP_EXPORT_MIN_SIZE) { - - bmwidth = SP_EXPORT_MIN_SIZE; - width = bmwidth * DPI_BASE / xdpi; - setValuePx(width_adj, width); - } - - setValuePx(x1_adj, x0 + width); - setValue(bmwidth_adj, bmwidth); - - update_flag = false; - - return; -} // end of sp_export_area_width_value_changed() - -/// Called when y1-y0 or area height is changed. -void Export::onAreaHeightChange() -{ - if (update_flag) { - return; - } - - update_flag = true; - - float y0 = getValuePx(y0_adj); - //float y1 = sp_export_value_get_px(y1_adj); - float ydpi = getValue(ydpi_adj); - float height = getValuePx(height_adj); - float bmheight = floor (height * ydpi / DPI_BASE + 0.5); - - if (bmheight < SP_EXPORT_MIN_SIZE) { - bmheight = SP_EXPORT_MIN_SIZE; - height = bmheight * DPI_BASE / ydpi; - setValuePx(height_adj, height); - } - - setValuePx(y1_adj, y0 + height); - setValue(bmheight_adj, bmheight); - - update_flag = false; - - return; -} // end of sp_export_area_height_value_changed() - -/** - * A function to set the ydpi. - * @param base The export dialog. - * - * This function grabs all of the y values and then figures out the - * new bitmap size based on the changing dpi value. The dpi value is - * gotten from the xdpi setting as these can not currently be independent. - */ -void Export::setImageY() -{ - float y0, y1, xdpi; - - y0 = getValuePx(y0_adj); - y1 = getValuePx(y1_adj); - xdpi = getValue(xdpi_adj); - - setValue(ydpi_adj, xdpi); - setValue(bmheight_adj, (y1 - y0) * xdpi / DPI_BASE); - - return; -} // end of setImageY() - -/** - * A function to set the xdpi. - * - * This function grabs all of the x values and then figures out the - * new bitmap size based on the changing dpi value. The dpi value is - * gotten from the xdpi setting as these can not currently be independent. - * - */ -void Export::setImageX() -{ - float x0, x1, xdpi; - - x0 = getValuePx(x0_adj); - x1 = getValuePx(x1_adj); - xdpi = getValue(xdpi_adj); - - setValue(ydpi_adj, xdpi); - setValue(bmwidth_adj, (x1 - x0) * xdpi / DPI_BASE); - - return; -} // end of setImageX() - -/// Called when pixel width is changed -void Export::onBitmapWidthChange () -{ - float x0, x1, bmwidth, xdpi; - - if (update_flag) { - return; - } - - update_flag = true; - - x0 = getValuePx(x0_adj); - x1 = getValuePx(x1_adj); - bmwidth = getValue(bmwidth_adj); - - if (bmwidth < SP_EXPORT_MIN_SIZE) { - bmwidth = SP_EXPORT_MIN_SIZE; - setValue(bmwidth_adj, bmwidth); - } - - xdpi = bmwidth * DPI_BASE / (x1 - x0); - setValue(xdpi_adj, xdpi); - - setImageY (); - - update_flag = false; - - return; -} // end of sp_export_bitmap_width_value_changed() - -/// Called when pixel height is changed -void Export::onBitmapHeightChange () -{ - float y0, y1, bmheight, xdpi; - - if (update_flag) { - return; - } - - update_flag = true; - - y0 = getValuePx(y0_adj); - y1 = getValuePx(y1_adj); - bmheight = getValue(bmheight_adj); - - if (bmheight < SP_EXPORT_MIN_SIZE) { - bmheight = SP_EXPORT_MIN_SIZE; - setValue(bmheight_adj, bmheight); - } - - xdpi = bmheight * DPI_BASE / (y1 - y0); - setValue(xdpi_adj, xdpi); - - setImageX (); - - update_flag = false; - - return; -} // end of sp_export_bitmap_width_value_changed() - -/** - * A function to adjust the bitmap width when the xdpi value changes. - * - * The first thing this function checks is to see if we are doing an - * update. If we are, this function just returns because there is another - * instance of it that will handle everything for us. If there is a - * units change, we also assume that everyone is being updated appropriately - * and there is nothing for us to do. - * - * If we're the highest level function, we set the update flag, and - * continue on our way. - * - * All of the values are grabbed using the \c sp_export_value_get functions - * (call to the _pt ones for x0 and x1 but just standard for xdpi). The - * xdpi value is saved in the preferences for the next time the dialog - * is opened. (does the selection dpi need to be set here?) - * - * A check is done to to ensure that we aren't outputting an invalid width, - * this is set by SP_EXPORT_MIN_SIZE. If that is the case the dpi is - * changed to make it valid. - * - * After all of this the bitmap width is changed. - * - * We also change the ydpi. This is a temporary hack as these can not - * currently be independent. This is likely to change in the future. - * - */ -void Export::onExportXdpiChange() -{ - float x0, x1, xdpi, bmwidth; - - if (update_flag) { - return; - } - - update_flag = true; - - x0 = getValuePx(x0_adj); - x1 = getValuePx(x1_adj); - xdpi = getValue(xdpi_adj); - - // remember xdpi setting - prefs->setDouble("/dialogs/export/defaultxdpi/value", xdpi); - - bmwidth = (x1 - x0) * xdpi / DPI_BASE; - - if (bmwidth < SP_EXPORT_MIN_SIZE) { - bmwidth = SP_EXPORT_MIN_SIZE; - if (x1 != x0) - xdpi = bmwidth * DPI_BASE / (x1 - x0); - else - xdpi = DPI_BASE; - setValue(xdpi_adj, xdpi); - } - - setValue(bmwidth_adj, bmwidth); - - setImageY (); - - update_flag = false; - - return; -} // end of sp_export_xdpi_value_changed() - - -/** - * A function to change the area that is used for the exported. - * bitmap. - * - * This function just calls \c sp_export_value_set_px for each of the - * parameters that is passed in. This allows for setting them all in - * one convenient area. - * - * Update is set to suspend all of the other test running while all the - * values are being set up. This allows for a performance increase, but - * it also means that the wrong type won't be detected with only some of - * the values set. After all the values are set everyone is told that - * there has been an update. - * - * @param x0 Horizontal upper left hand corner of the picture in points. - * @param y0 Vertical upper left hand corner of the picture in points. - * @param x1 Horizontal lower right hand corner of the picture in points. - * @param y1 Vertical lower right hand corner of the picture in points. - */ -void Export::setArea( double x0, double y0, double x1, double y1 ) -{ - update_flag = true; - setValuePx(x1_adj, x1); - setValuePx(y1_adj, y1); - setValuePx(x0_adj, x0); - setValuePx(y0_adj, y0); - update_flag = false; - - areaXChange (x1_adj); - areaYChange (y1_adj); - - return; -} - -/** - * Sets the value of an adjustment. - * - * @param adj The adjustment widget - * @param val What value to set it to. - */ -void Export::setValue(Glib::RefPtr<Gtk::Adjustment>& adj, double val ) -{ - if (adj) { - adj->set_value(val); - } -} - -/** - * A function to set a value using the units points. - * - * This function first gets the adjustment for the key that is passed - * in. It then figures out what units are currently being used in the - * dialog. After doing all of that, it then converts the incoming - *value and sets the adjustment. - * - * @param adj The adjustment widget - * @param val What the value should be in points. - */ -void Export::setValuePx(Glib::RefPtr<Gtk::Adjustment>& adj, double val) -{ - Unit const *unit = unit_selector.getUnit(); - - setValue(adj, Inkscape::Util::Quantity::convert(val, "px", unit)); - - return; -} - -/** - * Get the value of an adjustment in the export dialog. - * - * This function gets the adjustment from the data field in the export - * dialog. It then grabs the value from the adjustment. - * - * @param adj The adjustment widget - * - * @return The value in the specified adjustment. - */ -float Export::getValue(Glib::RefPtr<Gtk::Adjustment>& adj) -{ - if (!adj) { - g_message("sp_export_value_get : adj is NULL"); - return 0.0; - } - return adj->get_value(); } -/** - * Grabs a value in the export dialog and converts the unit - * to points. - * - * This function, at its most basic, is a call to \c sp_export_value_get - * to get the value of the adjustment. It then finds the units that - * are being used by looking at the "units" attribute of the export - * dialog. Using that it converts the returned value into points. - * - * @param adj The adjustment widget - * - * @return The value in the adjustment in points. - */ -float Export::getValuePx(Glib::RefPtr<Gtk::Adjustment>& adj) -{ - float value = getValue( adj); - Unit const *unit = unit_selector.getUnit(); - - return Inkscape::Util::Quantity::convert(value, unit, "px"); -} // end of sp_export_value_get_px() - -/** - * This function is called when the filename is changed by - * anyone. It resets the virgin bit. - * - * This function gets called when the text area is modified. It is - * looking for the case where the text area is modified from its - * original value. In that case it sets the "filename-modified" bit - * to TRUE. If the text dialog returns back to the original text, the - * bit gets reset. This should stop simple mistakes. - */ -void Export::onFilenameModified() -{ - if (original_name == filename_entry.get_text()) { - filename_modified = false; - } else { - filename_modified = true; - } - - return; -} // end sp_export_filename_modified - -} -} -} +} // namespace Dialog +} // namespace UI +} // namespace Inkscape /* Local Variables: @@ -1862,3 +195,4 @@ void Export::onFilenameModified() End: */ // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : \ No newline at end of file diff --git a/src/ui/dialog/export.h b/src/ui/dialog/export.h index cd2f40bdd4a5cd1e63187385c0cbe6aa64c31839..cc8c0e4d2a8db31e8692ec6f3e8d596934cfc994 100644 --- a/src/ui/dialog/export.h +++ b/src/ui/dialog/export.h @@ -3,8 +3,9 @@ * Lauris Kaplinski <lauris@kaplinski.com> * bulia byak <buliabyak@users.sf.net> * Johan Engelen <j.b.c.engelen@ewi.utwente.nl> + * Anshudhar Kumar Singh <anshudhar2001@gmail.com> * - * Copyright (C) 1999-2007 Authors + * Copyright (C) 1999-2007, 2021 Authors * Copyright (C) 2001-2002 Ximian, Inc. * * Released under GNU GPL v2+, read the file 'COPYING' for more information. @@ -13,340 +14,64 @@ #ifndef SP_EXPORT_H #define SP_EXPORT_H -#include <gtkmm/checkbutton.h> -#include <gtkmm/comboboxtext.h> -#include <gtkmm/expander.h> -#include <gtkmm/grid.h> -#include <gtkmm/progressbar.h> -#include <gtkmm/radiobutton.h> -#include <gtkmm/spinbutton.h> +#include <gtkmm.h> +#include "export-batch.h" +#include "export-helper.h" +#include "export-single.h" #include "extension/output.h" #include "ui/dialog/dialog-base.h" #include "ui/widget/scrollprotected.h" +#include "ui/widget/unit-menu.h" namespace Inkscape { namespace UI { namespace Dialog { -class ExportProgressDialog; - -/** What type of button is being pressed */ -enum selection_type { - SELECTION_PAGE = 0, /**< Export the whole page */ - SELECTION_DRAWING, /**< Export everything drawn on the page */ - SELECTION_SELECTION, /**< Export everything that is selected */ - SELECTION_CUSTOM, /**< Allows the user to set the region exported */ - SELECTION_NUMBER_OF /**< A counter for the number of these guys */ +enum notebook_page +{ + SINGLE_IMAGE = 0, + BATCH_EXPORT }; -/** - * A dialog widget to export to various image formats such as bitmap and png. - * - * Creates a dialog window for exporting an image to a bitmap if one doesn't already exist and - * shows it to the user. If the dialog has already been created, it simply shows the window. - * - */ class Export : public DialogBase { public: - Export (); - ~Export () override; + Export(); + ~Export() override; - static Export &getInstance() { - return *new Export(); - } + static Export &getInstance() { return *new Export(); } private: + Glib::RefPtr<Gtk::Builder> builder; + Gtk::Box *container = nullptr; // Main Container + Gtk::Notebook *export_notebook = nullptr; // Notebook Container for single and batch export - void documentReplaced() override; - void selectionChanged(Inkscape::Selection *selection) override; - void selectionModified(Inkscape::Selection *selection, guint flags) override; - - /** - * A function to set the xdpi. - * - * This function grabs all of the x values and then figures out the - * new bitmap size based on the changing dpi value. The dpi value is - * gotten from the xdpi setting as these can not currently be independent. - * - */ - void setImageX(); - - /** - * A function to set the ydpi. - * - * This function grabs all of the y values and then figures out the - * new bitmap size based on the changing dpi value. The dpi value is - * gotten from the xdpi setting as these can not currently be independent. - */ - void setImageY(); - bool bbox_equal(Geom::Rect const &one, Geom::Rect const &two); - void updateCheckbuttons (); - inline void findDefaultSelection(); - void detectSize(); - void setArea ( double x0, double y0, double x1, double y1); - /* - * Getter/setter style functions for the spinbuttons - */ - void setValue(Glib::RefPtr<Gtk::Adjustment>& adj, double val); - void setValuePx(Glib::RefPtr<Gtk::Adjustment>& adj, double val); - float getValue(Glib::RefPtr<Gtk::Adjustment>& adj); - float getValuePx(Glib::RefPtr<Gtk::Adjustment>& adj); - - /** - * Helper function to create, style and pack spinbuttons for the export dialog. - * - * Creates a new spin button for the export dialog. - * @param key The name of the spin button - * @param val A default value for the spin button - * @param min Minimum value for the spin button - * @param max Maximum value for the spin button - * @param step The step size for the spin button - * @param page Size of the page increment - * @param t Table to put the spin button in - * @param x X location in the table \c t to start with - * @param y Y location in the table \c t to start with - * @param ll Text to put on the left side of the spin button (optional) - * @param lr Text to put on the right side of the spin button (optional) - * @param digits Number of digits to display after the decimal - * @param sensitive Whether the spin button is sensitive or not - * @param cb Callback for when this spin button is changed (optional) - * - * No unit_selector is stored in the created spinbutton, relies on external unit management - */ - Glib::RefPtr<Gtk::Adjustment> createSpinbutton( gchar const *key, - double val, double min, double max, double step, double page, - Gtk::Grid *t, int x, int y, - const Glib::ustring& ll, const Glib::ustring& lr, - int digits, unsigned int sensitive, - void (Export::*cb)() ); - - std::string create_filepath_from_id(Glib::ustring, const Glib::ustring &); - - /** - * One of the area select radio buttons was pressed - */ - void onAreaTypeToggled(); - void refreshArea(); - - /** - * Export button callback - */ - void onExport (); - void _export_raster(Inkscape::Extension::Output *extension); - - /** - * File Browse button callback - */ - void onBrowse (); - - /** - * Area X value changed callback - */ - void onAreaX0Change() { - areaXChange(x0_adj); - } ; - void onAreaX1Change() { - areaXChange(x1_adj); - } ; - void areaXChange(Glib::RefPtr<Gtk::Adjustment>& adj); - - /** - * Area Y value changed callback - */ - void onAreaY0Change() { - areaYChange(y0_adj); - } ; - void onAreaY1Change() { - areaYChange(y1_adj); - } ; - void areaYChange(Glib::RefPtr<Gtk::Adjustment>& adj); - - /** - * Unit changed callback - */ - void onUnitChanged(); - - /** - * Hide except selected callback - */ - void onHideExceptSelected (); - - /** - * Area width value changed callback - */ - void onAreaWidthChange (); - - /** - * Area height value changed callback - */ - void onAreaHeightChange (); - - /** - * Bitmap width value changed callback - */ - void onBitmapWidthChange (); - - /** - * Bitmap height value changed callback - */ - void onBitmapHeightChange (); - - /** - * Export xdpi value changed callback - */ - void onExportXdpiChange (); - - /** - * Batch export callback - */ - void onBatchClicked (); - - /** - * Filename modified callback - */ - void onFilenameModified (); - - /** - * Creates progress dialog for batch exporting. - * - * @param progress_text Text to be shown in the progress bar - */ - ExportProgressDialog * create_progress_dialog(Glib::ustring progress_text); - - /** - * Callback to be used in for loop to update the progress bar. - * - * @param value number between 0 and 1 indicating the fraction of progress (0.17 = 17 % progress) - * @param dlg void pointer to the Gtk::Dialog progress dialog - */ - static unsigned int onProgressCallback(float value, void *dlg); - - /** - * Callback for pressing the cancel button. - */ - void onProgressCancel (); - - /** - * Callback invoked on closing the progress dialog. - */ - bool onProgressDelete (GdkEventAny *event); - - /** - * Handles state changes as exporting starts or stops. - */ - void setExporting(bool exporting, Glib::ustring const &text = ""); - - /* - * Utility filename and path functions - */ - void set_default_filename (const gchar *doc_filename); - - /* - * Currently selected export area type - * can be changed by code - */ - selection_type current_key; - /* - * Manually selected export area type(only changed by buttons) - */ - selection_type manual_key; - /* - * Original name for the export object - */ - Glib::ustring original_name; - Glib::ustring doc_export_name; - /* - * Was the Original name modified - */ - bool filename_modified; - - /* - * Flag to stop simultaneous updates - */ - bool update_flag; - - /* Area selection radio buttons */ - Gtk::Box togglebox; - Gtk::RadioButton *selectiontype_buttons[SELECTION_NUMBER_OF]; - - Gtk::Box area_box; - Gtk::Box singleexport_box; - - /* Custom size widgets */ - Glib::RefPtr<Gtk::Adjustment> x0_adj; - Glib::RefPtr<Gtk::Adjustment> x1_adj; - Glib::RefPtr<Gtk::Adjustment> y0_adj; - Glib::RefPtr<Gtk::Adjustment> y1_adj; - Glib::RefPtr<Gtk::Adjustment> width_adj; - Glib::RefPtr<Gtk::Adjustment> height_adj; - - /* Bitmap size widgets */ - Glib::RefPtr<Gtk::Adjustment> bmwidth_adj; - Glib::RefPtr<Gtk::Adjustment> bmheight_adj; - Glib::RefPtr<Gtk::Adjustment> xdpi_adj; - Glib::RefPtr<Gtk::Adjustment> ydpi_adj; - - Gtk::Box size_box; - Gtk::Label* bm_label; - - Gtk::Box file_box; - Gtk::Label *flabel; - Gtk::Entry filename_entry; - - /* Unit selector widgets */ - Gtk::Box unitbox; - Inkscape::UI::Widget::UnitMenu unit_selector; - Gtk::Label units_label; - - /* Filename widgets */ - Gtk::Box filename_box; - Gtk::Button browse_button; - Gtk::Label browse_label; - Gtk::Image browse_image; - - Gtk::Box batch_box; - Gtk::CheckButton batch_export; - - Gtk::Box hide_box; - Gtk::CheckButton hide_export; - - Gtk::CheckButton closeWhenDone; - - /* Advanced */ - Gtk::Expander expander; - Gtk::CheckButton interlacing; - Gtk::Label bitdepth_label; - Inkscape::UI::Widget::ScrollProtected<Gtk::ComboBoxText> bitdepth_cb; - Gtk::Label zlib_label; - Inkscape::UI::Widget::ScrollProtected<Gtk::ComboBoxText> zlib_compression; - Gtk::Label pHYs_label; - Glib::RefPtr<Gtk::Adjustment> pHYs_adj; - Inkscape::UI::Widget::ScrollProtected<Gtk::SpinButton> pHYs_sb; - Gtk::Label antialiasing_label; - Inkscape::UI::Widget::ScrollProtected<Gtk::ComboBoxText> antialiasing_cb; - - /* Export Button widgets */ - Gtk::Box button_box; - Gtk::Button export_button; +private: + SingleExport *single_image = nullptr; + BatchExport *batch_export = nullptr; - Gtk::ProgressBar _prog; +private: + Inkscape::Preferences *prefs = nullptr; - ExportProgressDialog *prog_dlg; - bool interrupted; // indicates whether export needs to be interrupted (read: user pressed cancel in the progress dialog) + // setup default values of widgets + void setDefaultNotebookPage(); + std::map<notebook_page, int> pages; - Inkscape::Preferences *prefs; - sigc::connection selectChangedConn; - sigc::connection subselChangedConn; - sigc::connection selectModifiedConn; - sigc::connection unitChangedConn; +private: + // signals callback + void onRealize(); + void onPageSwitch(Widget *page, guint page_number); +private: + void documentReplaced() override; + void desktopReplaced() override; + void selectionChanged(Inkscape::Selection *selection) override; + void selectionModified(Inkscape::Selection *selection, guint flags) override; }; - -} -} -} +} // namespace Dialog +} // namespace UI +} // namespace Inkscape #endif /* @@ -358,4 +83,4 @@ private: fill-column:99 End: */ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : \ No newline at end of file diff --git a/src/ui/dialog/icon-preview.cpp b/src/ui/dialog/icon-preview.cpp index 99a4f933c865f4f3a99a7451acc46e128d53834d..d0b61a988f9417b82dd24d4f3d0fc7030272e90b 100644 --- a/src/ui/dialog/icon-preview.cpp +++ b/src/ui/dialog/icon-preview.cpp @@ -15,10 +15,11 @@ * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ +#include "icon-preview.h" + #include <glibmm/i18n.h> -#include <glibmm/timer.h> #include <glibmm/main.h> - +#include <glibmm/timer.h> #include <gtkmm/buttonbox.h> #include <gtkmm/checkbutton.h> #include <gtkmm/frame.h> @@ -28,22 +29,15 @@ #include "inkscape.h" #include "display/cairo-utils.h" -#include "display/drawing.h" #include "display/drawing-context.h" - +#include "display/drawing.h" +#include "document.h" +#include "inkscape.h" #include "object/sp-namedview.h" #include "object/sp-root.h" - -#include "icon-preview.h" - +#include "preview-util.h" #include "ui/widget/frame.h" - -extern "C" { -// takes doc, drawing, icon, and icon name to produce pixels -guchar * -sp_icon_doc_icon( SPDocument *doc, Inkscape::Drawing &drawing, - const gchar *name, unsigned int psize, unsigned &stride); -} +#include "verbs.h" #define noICON_VERBOSE 1 @@ -51,7 +45,6 @@ namespace Inkscape { namespace UI { namespace Dialog { - IconPreviewPanel &IconPreviewPanel::getInstance() { IconPreviewPanel *instance = new IconPreviewPanel(); @@ -67,8 +60,8 @@ IconPreviewPanel &IconPreviewPanel::getInstance() void IconPreviewPanel::on_button_clicked(int which) { - if ( hot != which ) { - buttons[hot]->set_active( false ); + if (hot != which) { + buttons[hot]->set_active(false); hot = which; updateMagnify(); @@ -76,9 +69,6 @@ void IconPreviewPanel::on_button_clicked(int which) } } - - - //######################################################################### //## C O N S T R U C T O R / D E S T R U C T O R //######################################################################### @@ -107,7 +97,7 @@ IconPreviewPanel::IconPreviewPanel() std::vector<Glib::ustring> pref_sizes = prefs->getAllDirs("/iconpreview/sizes/default"); std::vector<int> rawSizes; - for (auto & pref_size : pref_sizes) { + for (auto &pref_size : pref_sizes) { if (prefs->getBool(pref_size + "/show", true)) { int sizeVal = prefs->getInt(pref_size + "/value", -1); if (sizeVal > 0) { @@ -116,17 +106,16 @@ IconPreviewPanel::IconPreviewPanel() } } - if ( !rawSizes.empty() ) { + if (!rawSizes.empty()) { numEntries = rawSizes.size(); sizes = new int[numEntries]; int i = 0; - for ( std::vector<int>::iterator it = rawSizes.begin(); it != rawSizes.end(); ++it, ++i ) { + for (std::vector<int>::iterator it = rawSizes.begin(); it != rawSizes.end(); ++it, ++i) { sizes[i] = *it; } } - if ( numEntries < 1 ) - { + if (numEntries < 1) { numEntries = 5; sizes = new int[numEntries]; sizes[0] = 16; @@ -136,13 +125,12 @@ IconPreviewPanel::IconPreviewPanel() sizes[4] = 128; } - pixMem = new guchar*[numEntries]; - images = new Gtk::Image*[numEntries]; - labels = new Glib::ustring*[numEntries]; - buttons = new Gtk::ToggleToolButton*[numEntries]; - + pixMem = new guchar *[numEntries]; + images = new Gtk::Image *[numEntries]; + labels = new Glib::ustring *[numEntries]; + buttons = new Gtk::ToggleToolButton *[numEntries]; - for ( int i = 0; i < numEntries; i++ ) { + for (int i = 0; i < numEntries; i++) { char *label = g_strdup_printf(_("%d x %d"), sizes[i], sizes[i]); labels[i] = new Glib::ustring(label); g_free(label); @@ -150,33 +138,31 @@ IconPreviewPanel::IconPreviewPanel() images[i] = nullptr; } + magLabel.set_label(*labels[hot]); - magLabel.set_label( *labels[hot] ); - - Gtk::Box* magBox = new Gtk::Box(Gtk::ORIENTATION_VERTICAL); + Gtk::Box *magBox = new Gtk::Box(Gtk::ORIENTATION_VERTICAL); UI::Widget::Frame *magFrame = Gtk::manage(new UI::Widget::Frame(_("Magnified:"))); - magFrame->add( magnified ); - - magBox->pack_start( *magFrame, Gtk::PACK_EXPAND_WIDGET ); - magBox->pack_start( magLabel, Gtk::PACK_SHRINK ); + magFrame->add(magnified); + magBox->pack_start(*magFrame, Gtk::PACK_EXPAND_WIDGET); + magBox->pack_start(magLabel, Gtk::PACK_SHRINK); Gtk::Box *verts = new Gtk::Box(Gtk::ORIENTATION_VERTICAL); Gtk::Box *horiz = nullptr; int previous = 0; int avail = 0; - for ( int i = numEntries - 1; i >= 0; --i ) { + for (int i = numEntries - 1; i >= 0; --i) { int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, sizes[i]); pixMem[i] = new guchar[sizes[i] * stride]; - memset( pixMem[i], 0x00, sizes[i] * stride ); + memset(pixMem[i], 0x00, sizes[i] * stride); auto pb = Gdk::Pixbuf::create_from_data(pixMem[i], Gdk::COLORSPACE_RGB, true, 8, sizes[i], sizes[i], stride); images[i] = Gtk::make_managed<Gtk::Image>(pb); Glib::ustring label(*labels[i]); buttons[i] = new Gtk::ToggleToolButton(label); - buttons[i]->set_active( i == hot ); - if ( prefs->getBool("/iconpreview/showFrames", true) ) { + buttons[i]->set_active(i == hot); + if (prefs->getBool("/iconpreview/showFrames", true)) { Gtk::Frame *frame = new Gtk::Frame(); frame->set_shadow_type(Gtk::SHADOW_ETCHED_IN); frame->add(*images[i]); @@ -187,12 +173,13 @@ IconPreviewPanel::IconPreviewPanel() buttons[i]->set_tooltip_text(label); - buttons[i]->signal_clicked().connect( sigc::bind<int>( sigc::mem_fun(*this, &IconPreviewPanel::on_button_clicked), i) ); + buttons[i]->signal_clicked().connect( + sigc::bind<int>(sigc::mem_fun(*this, &IconPreviewPanel::on_button_clicked), i)); buttons[i]->set_halign(Gtk::ALIGN_CENTER); buttons[i]->set_valign(Gtk::ALIGN_CENTER); - if ( !pack || ( (avail == 0) && (previous == 0) ) ) { + if (!pack || ((avail == 0) && (previous == 0))) { verts->pack_end(*(buttons[i]), Gtk::PACK_SHRINK); previous = sizes[i]; avail = sizes[i]; @@ -221,20 +208,22 @@ IconPreviewPanel::IconPreviewPanel() } iconBox.pack_start(splitter); - splitter.pack1( *magBox, true, false ); - UI::Widget::Frame *actuals = Gtk::manage(new UI::Widget::Frame (_("Actual Size:"))); + splitter.pack1(*magBox, true, false); + UI::Widget::Frame *actuals = Gtk::manage(new UI::Widget::Frame(_("Actual Size:"))); actuals->set_border_width(4); actuals->add(*verts); - splitter.pack2( *actuals, false, false ); - + splitter.pack2(*actuals, false, false); - selectionButton = new Gtk::CheckButton(C_("Icon preview window", "Sele_ction"), true);//selectionButton = (Gtk::ToggleButton*) gtk_check_button_new_with_mnemonic(_("_Selection")); // , GTK_RESPONSE_APPLY - magBox->pack_start( *selectionButton, Gtk::PACK_SHRINK ); + selectionButton = + new Gtk::CheckButton(C_("Icon preview window", "Sele_ction"), + true); // selectionButton = (Gtk::ToggleButton*) + // gtk_check_button_new_with_mnemonic(_("_Selection")); // , GTK_RESPONSE_APPLY + magBox->pack_start(*selectionButton, Gtk::PACK_SHRINK); selectionButton->set_tooltip_text(_("Selection only or whole document")); - selectionButton->signal_clicked().connect( sigc::mem_fun(*this, &IconPreviewPanel::modeToggled) ); + selectionButton->signal_clicked().connect(sigc::mem_fun(*this, &IconPreviewPanel::modeToggled)); gint val = prefs->getBool("/iconpreview/selectionOnly"); - selectionButton->set_active( val != 0 ); + selectionButton->set_active(val != 0); pack_start(iconBox, Gtk::PACK_SHRINK); @@ -255,7 +244,7 @@ IconPreviewPanel::~IconPreviewPanel() delete timer; timer = nullptr; } - if ( renderTimer ) { + if (renderTimer) { renderTimer->stop(); delete renderTimer; renderTimer = nullptr; @@ -268,7 +257,6 @@ IconPreviewPanel::~IconPreviewPanel() //## M E T H O D S //######################################################################### - #if ICON_VERBOSE static Glib::ustring getTimestr() { @@ -316,20 +304,19 @@ void IconPreviewPanel::refreshPreview() } if (timer->elapsed() < minDelay) { #if ICON_VERBOSE - g_message( "%s Deferring refresh as too soon. calling queueRefresh()", getTimestr().c_str() ); -#endif //ICON_VERBOSE - // Do not refresh too quickly + g_message("%s Deferring refresh as too soon. calling queueRefresh()", getTimestr().c_str()); +#endif // ICON_VERBOSE + // Do not refresh too quickly queueRefresh(); } else if (document) { #if ICON_VERBOSE - g_message( "%s Refreshing preview.", getTimestr().c_str() ); + g_message("%s Refreshing preview.", getTimestr().c_str()); #endif // ICON_VERBOSE bool hold = Inkscape::Preferences::get()->getBool("/iconpreview/selectionHold", true); SPObject *target = nullptr; - if ( selectionButton && selectionButton->get_active() ) - { - target = (hold && !targetId.empty()) ? document->getObjectById( targetId.c_str() ) : nullptr; - if ( !target ) { + if (selectionButton && selectionButton->get_active()) { + target = (hold && !targetId.empty()) ? document->getObjectById(targetId.c_str()) : nullptr; + if (!target) { targetId.clear(); if (auto selection = getSelection()) { for (auto item : selection->items()) { @@ -347,7 +334,7 @@ void IconPreviewPanel::refreshPreview() renderPreview(target); } #if ICON_VERBOSE - g_message( "%s resetting timer", getTimestr().c_str() ); + g_message("%s resetting timer", getTimestr().c_str()); #endif // ICON_VERBOSE timer->reset(); } @@ -359,14 +346,14 @@ bool IconPreviewPanel::refreshCB() if (!timer) { timer = new Glib::Timer(); } - if ( timer->elapsed() > minDelay ) { + if (timer->elapsed() > minDelay) { #if ICON_VERBOSE - g_message( "%s refreshCB() timer has progressed", getTimestr().c_str() ); + g_message("%s refreshCB() timer has progressed", getTimestr().c_str()); #endif // ICON_VERBOSE callAgain = false; refreshPreview(); #if ICON_VERBOSE - g_message( "%s refreshCB() setting pending false", getTimestr().c_str() ); + g_message("%s refreshCB() setting pending false", getTimestr().c_str()); #endif // ICON_VERBOSE pending = false; } @@ -378,12 +365,12 @@ void IconPreviewPanel::queueRefresh() if (!pending) { pending = true; #if ICON_VERBOSE - g_message( "%s queueRefresh() Setting pending true", getTimestr().c_str() ); + g_message("%s queueRefresh() Setting pending true", getTimestr().c_str()); #endif // ICON_VERBOSE if (!timer) { timer = new Glib::Timer(); } - Glib::signal_idle().connect( sigc::mem_fun(this, &IconPreviewPanel::refreshCB), Glib::PRIORITY_DEFAULT_IDLE ); + Glib::signal_idle().connect(sigc::mem_fun(this, &IconPreviewPanel::refreshCB), Glib::PRIORITY_DEFAULT_IDLE); } } @@ -392,229 +379,48 @@ void IconPreviewPanel::modeToggled() Inkscape::Preferences *prefs = Inkscape::Preferences::get(); bool selectionOnly = (selectionButton && selectionButton->get_active()); prefs->setBool("/iconpreview/selectionOnly", selectionOnly); - if ( !selectionOnly ) { + if (!selectionOnly) { targetId.clear(); } refreshPreview(); } -void overlayPixels(guchar *px, int width, int height, int stride, - unsigned r, unsigned g, unsigned b) -{ - int bytesPerPixel = 4; - int spacing = 4; - for ( int y = 0; y < height; y += spacing ) { - guchar *ptr = px + y * stride; - for ( int x = 0; x < width; x += spacing ) { - *(ptr++) = r; - *(ptr++) = g; - *(ptr++) = b; - *(ptr++) = 0xff; - - ptr += bytesPerPixel * (spacing - 1); - } - } - - if ( width > 1 && height > 1 ) { - // point at the last pixel - guchar *ptr = px + ((height-1) * stride) + ((width - 1) * bytesPerPixel); - - if ( width > 2 ) { - px[4] = r; - px[5] = g; - px[6] = b; - px[7] = 0xff; - - ptr[-12] = r; - ptr[-11] = g; - ptr[-10] = b; - ptr[-9] = 0xff; - } - - ptr[-4] = r; - ptr[-3] = g; - ptr[-2] = b; - ptr[-1] = 0xff; - - px[0 + stride] = r; - px[1 + stride] = g; - px[2 + stride] = b; - px[3 + stride] = 0xff; - - ptr[0 - stride] = r; - ptr[1 - stride] = g; - ptr[2 - stride] = b; - ptr[3 - stride] = 0xff; - - if ( height > 2 ) { - ptr[0 - stride * 3] = r; - ptr[1 - stride * 3] = g; - ptr[2 - stride * 3] = b; - ptr[3 - stride * 3] = 0xff; - } - } -} - -// takes doc, drawing, icon, and icon name to produce pixels -extern "C" guchar * -sp_icon_doc_icon( SPDocument *doc, Inkscape::Drawing &drawing, - gchar const *name, unsigned psize, - unsigned &stride) -{ - bool const dump = Inkscape::Preferences::get()->getBool("/debug/icons/dumpSvg"); - guchar *px = nullptr; - - if (doc) { - SPObject *object = doc->getObjectById(name); - if (object && SP_IS_ITEM(object)) { - SPItem *item = SP_ITEM(object); - // Find bbox in document - Geom::OptRect dbox = item->documentVisualBounds(); - - if ( object->parent == nullptr ) - { - dbox = Geom::Rect(Geom::Point(0, 0), - Geom::Point(doc->getWidth().value("px"), doc->getHeight().value("px"))); - } - - /* This is in document coordinates, i.e. pixels */ - if ( dbox ) { - /* Update to renderable state */ - double sf = 1.0; - drawing.root()->setTransform(Geom::Scale(sf)); - drawing.update(); - /* Item integer bbox in points */ - // NOTE: previously, each rect coordinate was rounded using floor(c + 0.5) - Geom::IntRect ibox = dbox->roundOutwards(); - - if ( dump ) { - g_message( " box --'%s' (%f,%f)-(%f,%f)", name, (double)ibox.left(), (double)ibox.top(), (double)ibox.right(), (double)ibox.bottom() ); - } - - /* Find button visible area */ - int width = ibox.width(); - int height = ibox.height(); - - if ( dump ) { - g_message( " vis --'%s' (%d,%d)", name, width, height ); - } - - { - int block = std::max(width, height); - if (block != static_cast<int>(psize) ) { - if ( dump ) { - g_message(" resizing" ); - } - sf = (double)psize / (double)block; - - drawing.root()->setTransform(Geom::Scale(sf)); - drawing.update(); - - auto scaled_box = *dbox * Geom::Scale(sf); - ibox = scaled_box.roundOutwards(); - if ( dump ) { - g_message( " box2 --'%s' (%f,%f)-(%f,%f)", name, (double)ibox.left(), (double)ibox.top(), (double)ibox.right(), (double)ibox.bottom() ); - } - - /* Find button visible area */ - width = ibox.width(); - height = ibox.height(); - if ( dump ) { - g_message( " vis2 --'%s' (%d,%d)", name, width, height ); - } - } - } - - Geom::IntPoint pdim(psize, psize); - int dx, dy; - //dx = (psize - width) / 2; - //dy = (psize - height) / 2; - dx=dy=psize; - dx=(dx-width)/2; // watch out for psize, since 'unsigned'-'signed' can cause problems if the result is negative - dy=(dy-height)/2; - Geom::IntRect area = Geom::IntRect::from_xywh(ibox.min() - Geom::IntPoint(dx,dy), pdim); - /* Actual renderable area */ - Geom::IntRect ua = *Geom::intersect(ibox, area); - - if ( dump ) { - g_message( " area --'%s' (%f,%f)-(%f,%f)", name, (double)area.left(), (double)area.top(), (double)area.right(), (double)area.bottom() ); - g_message( " ua --'%s' (%f,%f)-(%f,%f)", name, (double)ua.left(), (double)ua.top(), (double)ua.right(), (double)ua.bottom() ); - } - - stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, psize); - - /* Set up pixblock */ - px = g_new(guchar, stride * psize); - memset(px, 0x00, stride * psize); - - /* Render */ - cairo_surface_t *s = cairo_image_surface_create_for_data(px, - CAIRO_FORMAT_ARGB32, psize, psize, stride); - Inkscape::DrawingContext dc(s, ua.min()); - - SPNamedView *nv = sp_document_namedview(doc, nullptr); - float bg_r = SP_RGBA32_R_F(nv->pagecolor); - float bg_g = SP_RGBA32_G_F(nv->pagecolor); - float bg_b = SP_RGBA32_B_F(nv->pagecolor); - float bg_a = SP_RGBA32_A_F(nv->pagecolor); - - cairo_t *cr = cairo_create(s); - cairo_set_source_rgba(cr, bg_r, bg_g, bg_b, bg_a); - cairo_rectangle(cr, 0, 0, psize, psize); - cairo_fill(cr); - cairo_save(cr); - cairo_destroy(cr); - - drawing.render(dc, ua); - cairo_surface_destroy(s); - - // convert to GdkPixbuf format - convert_pixels_argb32_to_pixbuf(px, psize, psize, stride); - - if ( Inkscape::Preferences::get()->getBool("/debug/icons/overlaySvg") ) { - overlayPixels( px, psize, psize, stride, 0x00, 0x00, 0xff ); - } - } - } - } - - return px; -} // end of sp_icon_doc_icon() - - -void IconPreviewPanel::renderPreview( SPObject* obj ) +void IconPreviewPanel::renderPreview(SPObject *obj) { - SPDocument * doc = obj->document; - gchar const * id = obj->getId(); - if ( !renderTimer ) { + SPDocument *doc = obj->document; + gchar const *id = obj->getId(); + if (!renderTimer) { renderTimer = new Glib::Timer(); } renderTimer->reset(); #if ICON_VERBOSE - g_message("%s setting up to render '%s' as the icon", getTimestr().c_str(), id ); + g_message("%s setting up to render '%s' as the icon", getTimestr().c_str(), id); #endif // ICON_VERBOSE - for ( int i = 0; i < numEntries; i++ ) { + for (int i = 0; i < numEntries; i++) { unsigned unused; int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, sizes[i]); - guchar *px = sp_icon_doc_icon(doc, *drawing, id, sizes[i], unused); -// g_message( " size %d %s", sizes[i], (px ? "worked" : "failed") ); - if ( px ) { - memcpy( pixMem[i], px, sizes[i] * stride ); - g_free( px ); + guchar *px = Inkscape::UI::PREVIEW::sp_icon_doc_icon(doc, *drawing, id, sizes[i], unused); + // g_message( " size %d %s", sizes[i], (px ? "worked" : "failed") ); + if (px) { + memcpy(pixMem[i], px, sizes[i] * stride); + g_free(px); px = nullptr; } else { - memset( pixMem[i], 0, sizes[i] * stride ); + memset(pixMem[i], 0, sizes[i] * stride); } images[i]->set(images[i]->get_pixbuf()); // images[i]->queue_draw(); } + + std::cout << "Image Rendered" << std::endl; + updateMagnify(); renderTimer->stop(); - minDelay = std::max( 0.1, renderTimer->elapsed() * 3.0 ); + minDelay = std::max(0.1, renderTimer->elapsed() * 3.0); #if ICON_VERBOSE g_message(" render took %f seconds.", renderTimer->elapsed()); #endif // ICON_VERBOSE @@ -622,16 +428,16 @@ void IconPreviewPanel::renderPreview( SPObject* obj ) void IconPreviewPanel::updateMagnify() { - Glib::RefPtr<Gdk::Pixbuf> buf = images[hot]->get_pixbuf()->scale_simple( 128, 128, Gdk::INTERP_NEAREST ); - magLabel.set_label( *labels[hot] ); - magnified.set( buf ); + Glib::RefPtr<Gdk::Pixbuf> buf = images[hot]->get_pixbuf()->scale_simple(128, 128, Gdk::INTERP_NEAREST); + magLabel.set_label(*labels[hot]); + magnified.set(buf); // magnified.queue_draw(); // magnified.get_parent()->queue_draw(); } -} //namespace Dialogs -} //namespace UI -} //namespace Inkscape +} // namespace Dialog +} // namespace UI +} // namespace Inkscape /* Local Variables: diff --git a/src/ui/dialog/preview-util.cpp b/src/ui/dialog/preview-util.cpp new file mode 100644 index 0000000000000000000000000000000000000000..42ec5de0a141b3087acff1f6654bfab016483ad7 --- /dev/null +++ b/src/ui/dialog/preview-util.cpp @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * @file + * Utility functions for previewing icon representation. + */ +/* Authors: + * Jon A. Cruz + * Bob Jamison + * Other dudes from The Inkscape Organization + * Abhishek Sharma + * Anshudhar Kumar Singh <anshudhar2001@gmail.com> + * + * Copyright (C) 2004 Bob Jamison + * Copyright (C) 2005,2010 Jon A. Cruz + * Copyright (C) 2021 Anshudhar Kumar Singh + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "preview-util.h" + +#include "display/cairo-utils.h" +#include "display/drawing-context.h" +#include "object/sp-namedview.h" +#include "object/sp-root.h" + +namespace Inkscape { +namespace UI { +namespace PREVIEW { + +guchar *sp_icon_doc_icon(SPDocument *doc, Inkscape::Drawing &drawing, gchar const *name, unsigned psize, + unsigned &stride, Geom::OptRect *dboxIn) +{ + // If no doc then return nullptr. Beyond this point we assume there is valid doc + if (!doc) { + return nullptr; + } + + SPObject *object = nullptr; + + if (name) { + // If name is provided and valid, object will not be nullptr beyond this + object = doc->getObjectById(name); + } + + bool const dump = Inkscape::Preferences::get()->getBool("/debug/icons/dumpSvg"); + guchar *px = nullptr; + + Geom::OptRect dbox; + if (dboxIn) { + // We have dbox from function call and hence will use it + dbox = *dboxIn; + + } else if (object && SP_ITEM(object)) { + // Else we will check if object is provided and will use its bounding box if its doesnt have parent. + // Dont know why we check for parent and then use page coordinates. Will probably change it to document after + // discussing + if (object->parent == nullptr) { + dbox = + Geom::Rect(Geom::Point(0, 0), Geom::Point(doc->getWidth().value("px"), doc->getHeight().value("px"))); + } else { + SPItem *item = SP_ITEM(object); + dbox = item->documentVisualBounds(); + } + } else if (doc->getRoot()) { + // If we still dont have a dbox we will use document coordinates. + dbox = doc->getRoot()->documentVisualBounds(); + } + + if (!dbox) { + // If we still dont have anything to render then return nullptr + return nullptr; + } + + /* This is in document coordinates, i.e. pixels */ + + /* Update to renderable state */ + double sf = 1.0; + drawing.root()->setTransform(Geom::Scale(sf)); + drawing.update(); + /* Item integer bbox in points */ + // NOTE: previously, each rect coordinate was rounded using floor(c + 0.5) + Geom::IntRect ibox = dbox->roundOutwards(); + + if (dump) { + g_message(" box --'%s' (%f,%f)-(%f,%f)", name, (double)ibox.left(), (double)ibox.top(), + (double)ibox.right(), (double)ibox.bottom()); + } + + /* Find button visible area */ + int width = ibox.width(); + int height = ibox.height(); + + if (dump) { + g_message(" vis --'%s' (%d,%d)", name, width, height); + } + + { + int block = std::max(width, height); + if (block != static_cast<int>(psize)) { + if (dump) { + g_message(" resizing"); + } + sf = (double)psize / (double)block; + + drawing.root()->setTransform(Geom::Scale(sf)); + drawing.update(); + + auto scaled_box = *dbox * Geom::Scale(sf); + ibox = scaled_box.roundOutwards(); + if (dump) { + g_message(" box2 --'%s' (%f,%f)-(%f,%f)", name, (double)ibox.left(), (double)ibox.top(), + (double)ibox.right(), (double)ibox.bottom()); + } + + /* Find button visible area */ + width = ibox.width(); + height = ibox.height(); + if (dump) { + g_message(" vis2 --'%s' (%d,%d)", name, width, height); + } + } + } + + Geom::IntPoint pdim(psize, psize); + int dx, dy; + // dx = (psize - width) / 2; + // dy = (psize - height) / 2; + dx = dy = psize; + // watch out for psize, since 'unsigned'-'signed' can cause problems if the result is negative + dx = (dx - width) / 2; + dy = (dy - height) / 2; + Geom::IntRect area = Geom::IntRect::from_xywh(ibox.min() - Geom::IntPoint(dx, dy), pdim); + + /* Actual renderable area */ + Geom::IntRect ua = *Geom::intersect(ibox, area); + + if (dump) { + g_message(" area --'%s' (%f,%f)-(%f,%f)", name, (double)area.left(), (double)area.top(), + (double)area.right(), (double)area.bottom()); + g_message(" ua --'%s' (%f,%f)-(%f,%f)", name, (double)ua.left(), (double)ua.top(), (double)ua.right(), + (double)ua.bottom()); + } + + stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, psize); + + /* Set up pixblock */ + px = g_new(guchar, stride * psize); + memset(px, 0x00, stride * psize); + + /* Render */ + cairo_surface_t *s = cairo_image_surface_create_for_data(px, CAIRO_FORMAT_ARGB32, psize, psize, stride); + Inkscape::DrawingContext dc(s, ua.min()); + + SPNamedView *nv = sp_document_namedview(doc, nullptr); + float bg_r = SP_RGBA32_R_F(nv->pagecolor); + float bg_g = SP_RGBA32_G_F(nv->pagecolor); + float bg_b = SP_RGBA32_B_F(nv->pagecolor); + float bg_a = SP_RGBA32_A_F(nv->pagecolor); + + cairo_t *cr = cairo_create(s); + cairo_set_source_rgba(cr, bg_r, bg_g, bg_b, bg_a); + cairo_rectangle(cr, 0, 0, psize, psize); + cairo_fill(cr); + cairo_save(cr); + cairo_destroy(cr); + + drawing.render(dc, ua); + cairo_surface_destroy(s); + + // convert to GdkPixbuf format + convert_pixels_argb32_to_pixbuf(px, psize, psize, stride); + + if (Inkscape::Preferences::get()->getBool("/debug/icons/overlaySvg")) { + overlayPixels(px, psize, psize, stride, 0x00, 0x00, 0xff); + } + + return px; +} // end of sp_icon_doc_icon() + +void overlayPixels(guchar *px, int width, int height, int stride, unsigned r, unsigned g, unsigned b) +{ + int bytesPerPixel = 4; + int spacing = 4; + for (int y = 0; y < height; y += spacing) { + guchar *ptr = px + y * stride; + for (int x = 0; x < width; x += spacing) { + *(ptr++) = r; + *(ptr++) = g; + *(ptr++) = b; + *(ptr++) = 0xff; + + ptr += bytesPerPixel * (spacing - 1); + } + } + + if (width > 1 && height > 1) { + // point at the last pixel + guchar *ptr = px + ((height - 1) * stride) + ((width - 1) * bytesPerPixel); + + if (width > 2) { + px[4] = r; + px[5] = g; + px[6] = b; + px[7] = 0xff; + + ptr[-12] = r; + ptr[-11] = g; + ptr[-10] = b; + ptr[-9] = 0xff; + } + + ptr[-4] = r; + ptr[-3] = g; + ptr[-2] = b; + ptr[-1] = 0xff; + + px[0 + stride] = r; + px[1 + stride] = g; + px[2 + stride] = b; + px[3 + stride] = 0xff; + + ptr[0 - stride] = r; + ptr[1 - stride] = g; + ptr[2 - stride] = b; + ptr[3 - stride] = 0xff; + + if (height > 2) { + ptr[0 - stride * 3] = r; + ptr[1 - stride * 3] = g; + ptr[2 - stride * 3] = b; + ptr[3 - stride * 3] = 0xff; + } + } +} + +} // namespace PREVIEW +} // namespace UI +} // namespace Inkscape \ No newline at end of file diff --git a/src/ui/dialog/preview-util.h b/src/ui/dialog/preview-util.h new file mode 100644 index 0000000000000000000000000000000000000000..2bcd755fa4f4e3d263676f6800d380d91fa06314 --- /dev/null +++ b/src/ui/dialog/preview-util.h @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * @file + * Utility functions for previewing icon representation. + */ +/* Authors: + * Jon A. Cruz + * Bob Jamison + * Other dudes from The Inkscape Organization + * Abhishek Sharma + * Anshudhar Kumar Singh <anshudhar2001@gmail.com> + * + * Copyright (C) 2004 Bob Jamison + * Copyright (C) 2005,2010 Jon A. Cruz + * Copyright (C) 2021 Anshudhar Kumar Singh + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SP_PREVIEW_UTIL_H +#define SP_PREVIEW_UTIL_H + +#include <glibmm/main.h> + +#include "desktop.h" +#include "display/drawing.h" +#include "document.h" + +namespace Inkscape { +namespace UI { +namespace PREVIEW { + +// takes doc, drawing, icon, and icon name to produce pixels +guchar *sp_icon_doc_icon(SPDocument *doc, Inkscape::Drawing &drawing, const gchar *name, unsigned int psize, + unsigned &stride, Geom::OptRect *dboxIn = nullptr); + +void overlayPixels(guchar *px, int width, int height, int stride, unsigned r, unsigned g, unsigned b); + +} // namespace PREVIEW +} // namespace UI +} // namespace Inkscape + +#endif \ No newline at end of file diff --git a/src/ui/widget/scrollprotected.h b/src/ui/widget/scrollprotected.h index 7dfb5a036fac1d0ae114d39ed712c6abe15773c3..7e4a123aac2ad2c48d57951ca02f82868ceeead0 100644 --- a/src/ui/widget/scrollprotected.h +++ b/src/ui/widget/scrollprotected.h @@ -4,12 +4,15 @@ /* Authors: * Thomas Holder + * Anshudhar Kumar Singh <anshudhar2001@gmail.com> * - * Copyright (C) 2020 authors + * Copyright (C) 2020-2021 Authors * * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ +#include <gtkmm.h> + #include "scroll-utils.h" namespace Inkscape { @@ -31,6 +34,13 @@ class ScrollProtected : public Base { public: using Base::Base; + using typename Base::BaseObjectType; + ScrollProtected() + : Base() + {} + ScrollProtected(BaseObjectType *cobject, const Glib::RefPtr<Gtk::Builder> &refGlade) + : Base(cobject){}; + ~ScrollProtected() override{}; protected: /** @@ -53,6 +63,53 @@ protected: } }; +/** + * A class decorator for scroll widgets like scrolled window to transfer scroll to + * any ancestor which is is a scrollable window when scroll reached end. + * + * For custom scroll event handlers, derived classes must implement + * on_safe_scroll_event instead of on_scroll_event. Directly connecting to + * signal_scroll_event() will bypass the scroll protection. + * + * @tparam Base A subclass of Gtk::Widget + */ +template <typename Base> +class ScrollTransfer : public Base +{ +public: + using Base::Base; + using typename Base::BaseObjectType; + ScrollTransfer() + : Base() + {} + ScrollTransfer(BaseObjectType *cobject, const Glib::RefPtr<Gtk::Builder> &refGlade) + : Base(cobject){}; + ~ScrollTransfer() override{}; + +protected: + /** + * Event handler for "safe" scroll events + */ + virtual bool on_safe_scroll_event(GdkEventScroll *event) + { // + return Base::on_scroll_event(event); + } + + bool on_scroll_event(GdkEventScroll *event) final + { + auto scrollable = dynamic_cast<Gtk::Widget *>(Inkscape::UI::Widget::get_scrollable_ancestor(this)); + auto adj = this->get_vadjustment(); + auto before = adj->get_value(); + bool result = on_safe_scroll_event(event); + auto after = adj->get_value(); + if (scrollable && before == after) { + return false; + } + + return result; + } +}; + } // namespace Widget } // namespace UI } // namespace Inkscape diff --git a/src/ui/widget/unit-menu.h b/src/ui/widget/unit-menu.h index b8e3ab7fab7f90e9c8c71daf6299a934c5649d6b..d3fc497a248862dcfc711cfa2b7cc1381edda381 100644 --- a/src/ui/widget/unit-menu.h +++ b/src/ui/widget/unit-menu.h @@ -12,6 +12,7 @@ #define INKSCAPE_UI_WIDGET_UNIT_H #include <gtkmm/comboboxtext.h> +#include <gtkmm.h> #include "util/units.h" using namespace Inkscape::Util; @@ -31,7 +32,9 @@ public: * Construct a UnitMenu */ UnitMenu(); - + UnitMenu(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& refGlade):Gtk::ComboBoxText(cobject){ + UnitMenu(); + }; ~UnitMenu() override; /**